Code Samples

/* 
    Generates an array of body group weights, one per vertex in the mesh.
    A "body group weight" is a normalized Vector4, structured as the following:
        x: percentage influence of "head" bones on the vertex
        y: percentage influence of "torso" bones on the vertex
        z: percentage influence of "arm" bones on the vertex
        w: percentage influence of "leg" bones on the vertex
    For example, a vertex that's part of the avatar's head would probably have body group weight of (1, 0, 0, 0),
    while neck vertices may have a weight of (.5, .5, 0, 0).
    renderer is the skinned mesh renderer with bone information
    mesh is the source for vertex information
*/
static private Vector4[] GetBodyGroupWeights(SkinnedMeshRenderer renderer, Mesh mesh)
{
    // Initialize the array of body group weights, one per vertex
    Vector4[] bodyGroupWeights = new Vector4[mesh.vertexCount];

    // Get bone weights
    NativeArray boneWeights = mesh.GetAllBoneWeights();
    NativeArray bonesPerVertex = mesh.GetBonesPerVertex();

    // Get the actual bones
    Transform[] bones = renderer.bones;

    // Iterate over the vertices to get body group weights for each vertex
    int weightIndex = 0;
    for (int vertIndex = 0; vertIndex < mesh.vertexCount; vertIndex++)
    {
        // Remember body group weight for this vertex
        Vector4 bodyGroupWeight = Vector4.zero;
        float totalWeight = 0;

        // For each vertex, iterate over its bone weights
        for (int i = 0; i < bonesPerVertex[vertIndex]; i++)
        {
            // Get the bone weight, and the corresponding bone
            BoneWeight1 boneWeight = boneWeights[weightIndex];
            Transform bone = bones[boneWeight.boneIndex];

            // Get bone name without prefix (get last item after a _)
            string[] split = bone.name.Split("_");
            string boneName = split[split.Length - 1];

            // Ensure the bone name has a corresponding body group
            if (boneName != null && boneNameToBodyGroup.ContainsKey(boneName))
            {
                // Add the weight of the bone to the proper weight for the bone's body group
                switch (boneNameToBodyGroup[boneName])
                {
                    case (HumanoidKit.BodyGroup.Head):
                        bodyGroupWeight.x += boneWeight.weight;
                        break;
                    case (HumanoidKit.BodyGroup.Torso):
                        bodyGroupWeight.y += boneWeight.weight;
                        break;
                    case (HumanoidKit.BodyGroup.Arms):
                        bodyGroupWeight.z += boneWeight.weight;
                        break;
                    case (HumanoidKit.BodyGroup.Legs):
                        bodyGroupWeight.w += boneWeight.weight;
                        break;
                }
                // Add weight to the total weight for this vertex
                totalWeight += boneWeight.weight;
            }
            // Increment bone weight index
            weightIndex++;
        }
        // Remember weights for this vector
        bodyGroupWeights[vertIndex] = bodyGroupWeight / totalWeight;
    }

    return bodyGroupWeights;
}

/* 
    Creates and saves a mask texture, where each pixel's value corresponds to the body weights for that point of the mesh.
    mesh is the source for UV coordinates (and should be the same mesh used to generate body group weights)
    bodyGroupWeights is a array of weights, one per vertex in the mesh
    size is the desired size of the mask texture
*/
static private Texture2D GenerateMaskTexture(Mesh mesh, Vector4[] bodyGroupWeights, int size)
{
    // Get UV coordinates for vertices
    List uvs = new List();
    mesh.GetUVs(0, uvs);

    // Create the texture
    Texture2D mask = new Texture2D(size, size, TextureFormat.RGBA32, true, true);
    Color32[] pixels = mask.GetPixels32();

    // Create queue for flood fill
    LinkedList points = new LinkedList();
    // The weight values for each point that has been visited
    Dictionary weights = new Dictionary(); 

    // Seed flood fill with uvs
    for (int i = 0; i < uvs.Count; i++)
    {
        Vector2Int point = Vector2Int.RoundToInt(uvs[i] * size);

        points.AddLast(point);
        weights[point] = bodyGroupWeights[i];
    }

    // Flood fill
    while (points.Count > 0)
    {
        Vector2Int point = points.First.Value;
        points.RemoveFirst();

        ProcessPoint(point, new Vector2Int(-1, 0), size, points, weights);
        ProcessPoint(point, new Vector2Int(1, 0), size, points, weights);
        ProcessPoint(point, new Vector2Int(0, -1), size, points, weights);
        ProcessPoint(point, new Vector2Int(0, 1), size, points, weights);
    }

    // Gather pixel colors
    for (int y = 0; y < size; y++)
    {
        for (int x = 0; x < size; x++)
        {
            int index = x + size * y;
            Color32 color = (Color)weights[new Vector2Int(x, y)];
            pixels[index] = color;
        }
    }

    // Set pixel colors
    mask.SetPixels32(pixels);
    mask.Apply();

    return mask;
}

// Processes the given point for the texture flood fill
static private void ProcessPoint(Vector2Int parent, Vector2Int offset, int size, LinkedList points, Dictionary weights)
{
    Vector2Int point = parent + offset;

    // Only process the point if it's within the texture's bounds
    if (point.x >= 0 && point.x < size && point.y >= 0 && point.y < size)
    {
        // If this point hasn't already been filled, flood it with the weight values from its parent
        if (!weights.ContainsKey(point))
        {
            points.AddLast(point);
            weights[point] = weights[parent];
        }
    }
}

Body Part Classification for Skinned Avatars

C#
Texture mask generation for skinned avatar meshes in Unity, where each pixel's value corresponds to a body part.

This script was created for a VR project in which the user embodies a fully-modeled first-person avatar. Depending on the player's pose, certain parts of the avatar's body should be hidden. For example, when standing, the avatar's arms are visible (so the player knows where their hands are), but the head isn't, as it would obstruct the player's vision. When sitting, the legs become visible, to remind the user they are sitting. This approach works well for avatars composed of several meshes, as it is easy to enable or disable parts of the avatar's body. However, when the avatars are a single skinned mesh, this becomes much more challenging.

I solved this problem by creating a texture mask, in which each pixel corresponds to a body part. For example, each pixel UV mapped to the avatar's head has a color of (1, 0, 0, 0), and every pixel mapped to the torso is (0, 0, 0, 1). These "body group weights" can also be interpolated, so a neck pixel may have a value close to (.5, .5, 0, 0). I generate these textures by first going through every vertex in the avatar mesh, getting the bone weights for that vertex, then summing the body groups (i.e. head, torso, arm, and leg) for every bone influencing the vertex. This leaves me with a vector for every vertex which I call a "body group weight". Next, I create an empty texture. For every vertex, I set the color at its UV coordinate to its body group weight, then perform a simple flood fill to color the rest of the pixels. The resulting texture, coupled with a custom alpha clipping shader, allows me to show and hide parts of a single-mesh avatar on the fly.

-- A version of dijkstra with multiple starting points
-- graph is the graph to pathind on
-- starts is a list of nodes to start from, these nodes should exist in the given graph
-- Avoid is a bool, whether or not to avoid nodes used by already-generated paths
function Pathfind:generate(graph, starts, avoid)
  local queue = Heap:new() -- The queue of nodes to visit
  local visited = {} -- A list of visited nodes
  local dist = {} -- Distance scores for each node
  self.pred = {} -- The predecessors of each node
  -- Iterate through every node in the given graph, setting the initial values for each
  for _, v in pairs(graph:getNodes()) do
    visited[v] = false
    dist[v] = math.huge
    self.pred[v] = nil
  end
  -- Iterate through all the starting node, setting their distance to 0, and adding them to the queue
  for _, v in ipairs(starts) do
    dist[v] = 0
    queue:insert(v, 0)
  end
  -- Iterate through every node in the queue
  while not queue:isEmpty() do
    -- Take the node with the lowest dist as the current node
    local node = queue:remove()
    -- Make sure it has not been visited yet before continuing
    if not visited[node] then
      -- Mark the current node as visited
      visited[node] = true
      -- Iterate through the current node's neighbors
      for _, v in pairs(graph:getEdges(node)) do
        -- Calculate the new distance for this neighbor, coming from the current node
        -- Calculate the weight for this edge
        local weight = v.weight
        -- If avoidance is in use, and the neightboring node has an avoidance value, add it to the weight
        if avoid and self.avoidance[v.node] then
          weight = weight + self.avoidance[v.node]
        end
        local newDist = dist[node] + weight
        -- If the new distance is less than the current distance, update it
        if newDist < dist[v.node] then
          queue:insert(v.node, newDist) -- Add the node to the queue (only way of updating a priority queue)
          dist[v.node] = newDist -- Remember the new distance
          self.pred[v.node] = node -- Set the predecessors of the neighbor to the current node
        end
      end
    end
  end
end

-- Using a list of predecessors, return the path from the given node to a start
function Pathfind:path(node, avoid)
  local path = {} -- Start the path off as empty
  local step = node -- Start at the given node, and work backwards
  if self.pred[step] then
    -- Until we reach a starting node (which have a predecessor of nil) continue adding steps to the path
    while step do
      -- Check if avoidance is in use
      if avoid then
        -- If this step's node already has an avoidance value, add to it
        if self.avoidance[step] then
          self.avoidance[step] = self.avoidance[step] + self.avoidWeight
        else -- Otherwise, simply set it to the avoidance weight
          self.avoidance[step] = self.avoidWeight
        end
      end
      path[#path+1] = step -- Add the step to the path
      step = self.pred[step] -- Get the predecessor of the current step
    end
  end
  -- Return the generated path
  return path
end

-- Reduces the avoidance value for all nodes on a path
-- If start is given, all nodes before step are ignored
function Pathfind:reducePathAvoidance(path, start)
  -- Iterate through all nodes in the path
  for step = start or 1, #path do
    -- Reduce the node's avoidance
    self:reduceNodeAvoidance(path[step])
  end
end

-- Reduces the avoidance value for a node
function Pathfind:reduceNodeAvoidance(node)
  -- Make sure an avoidance value for the node exists
  if self.avoidance[node] then
    -- Reduce the value by the avoidance weight
    self.avoidance[node] = self.avoidance[node] - self.avoidWeight
    -- If the value is now zero or less, clear it
    if self.avoidance[node] <= 0 then
      self.avoidance[node] = nil
    end
  end
end

-- Clears the avoidance value for the given node
function Pathfind:clearNodeAvoidance(node)
  self.avoidance[node] = nil
end

Efficient, Multi-Destination Pathfinding with Avoidance

Lua
Enemy pathfinding for a multiplayer game, where enemies can path to any player, and should avoid crowded paths.

I created this custom pathfinding algorithm for a cooperative multiplayer game where players are pitted against hordes of AI-controlled enemies. Along with static nodes representing the environment, I treat players and enemies as nodes in a pathfinding graph. I modified Dijkstra's algorithm to allow for multiple source nodes, one for each player. Once the algorithm runs, enemies can easily find the shortest path from their node to the nearest player node by following a standard backtracing procedure. On rare occasions, my modification to the algorithm does not find a shortest path, but it's still more efficient than running Dijkstra's algorithm once per player, so the tradeoff is worthwhile.

I also integrated avoidance logic into my enemy pathfinding. Whenever an enemy picks a new path to follow, they add in a temporary avoidance value to the weight of the edges along the path. Likewise, when an enemy finishes traversing an edge, they reduce its avoidance value. When the pathfinding algorithm is re-run, edges with high avoidance values are deprioritized in favor of less-costly edges, due to the nature of the algorithm. This simple approach works well, and prevents enemies from clumping up along the same paths.

override fun doWork(): Result {
        // Get contacts
        val contactManager = ContactManager()
        contactManager.loadContacts(applicationContext)

        // Keep track of number of notifications
        var notificationCount = 0

        // Construct and register the notification channel
        val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, CHANNEL_IMPORTANCE)
        val notificationManager: NotificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)

        // Iterate through contacts
        for (i in contactManager.contacts.indices) {
            val contact = contactManager.contacts[i]

            // Check if we should notify for this contact
            if (
                contact.notify &&
                contactManager.notifyPercent(contact) >= 1 &&
                contact.lastNotified + Duration.ofHours(REPEAT_INTERVAL) < Instant.now(
            )) {
                // Create intent for when notification is clicked
                val intent = Intent(Intent.ACTION_VIEW, Uri.fromParts("sms", contact.numbers[0], null))
                val pendingIntent: PendingIntent = PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_IMMUTABLE)

                // Construct notification
                val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
                    .setSmallIcon(R.mipmap.ic_notification)
                    .setContentTitle("${applicationContext.getString(R.string.notification_title)} ${contact.name}")
                    .setContentText(applicationContext.getString(R.string.notification_body))
                    .setContentIntent(pendingIntent)
                    .setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setGroup(GROUP_ID)
                    .setAutoCancel(true)
                    .build()

                // Notify
                with(NotificationManagerCompat.from(applicationContext)) {
                    notify(i, notification)
                }

                notificationCount += 1

                // Remember that contact was notified
                contact.lastNotified = Instant.now()
            }
        }

        // If at least two notifications were sent, group them under a summary notification
        if (notificationCount > 1) {
            val summaryNotification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
                .setSmallIcon(R.mipmap.ic_notification)
                .setGroup(GROUP_ID)
                .setGroupSummary(true)
                .build()

            with(NotificationManagerCompat.from(applicationContext)) {
                notify(-1, summaryNotification)
            }
        }

        // Save last contacted times
        contactManager.saveContactData(applicationContext)

        return Result.success()
    }

Android Reminder Notifications

Kotlin
Push notifications to remind you to reach out to friends.

This function was created for my Intuch project. The app allows users to look through their phone contacts, and choose which ones they want to receive reminder notifications for, if they haven't reached out in a while. For Intuch, I wanted to be able to send notifications when the app is closed, even though Android apps can normally only send notifications while they are running. I solved this issue by creating a custom Worker class, which can run in the background even if the app itself isn't.

This particular Worker runs in the background once every day or so, looking through the app's saved list of contacts, checking the last time the user texted them, and checking if they want a reminder to reach out. If so, it creates a reminder notification for that contact and pushes it to the device. If several contacts require a notification, they are grouped together nicely under a summary notification.

Whenever a user clicks a notification, their text conversation with the corresponding contact opens up, making it easy for the user to reach out. Additionally, the app records the last time it sent a notification for each contact and doesn't send a new one until a pre-set interval has passed, to prevent overwhelming the user with notifications.

// Scrolls to the given element, offsetting for the header if requested
function ScrollToElement(id, addHeaderOffset = false) {    
    // Get the element to we want to scroll to
    let element = document.getElementById(id);

    // Initialize offset
    let offset = {
        left: 0,
        top: 0,
    }

    // Aggregate offsets of the element, recursing through parents
    while(element != null){        
        offset.left += element.offsetLeft;        
        offset.top += element.offsetTop;        
        element = element.offsetParent;        
    }

    // Offset scroll by the header's height, if requested
    if (addHeaderOffset) {
        const header = document.getElementById('header');
        offset.top -= header.offsetHeight;
    }

    // Start the scroll, smoothly
    window.scrollTo({...offset, behavior: 'smooth'});    
}

Scroll to Element

JavaScript
Javascript function that dynamically scrolls to the given element's position on the page.

I wrote this function for use within this website. Whenever you click a "continue" button (denoted by down arrow), this function is called to scroll the window to the next section. While other methods (such as the scrollIntoView() function, or focusing an element by adding it to the page url) do exist, they do not have the full functionality I was looking for. Namely, they don't support (or have spotty support for) smooth scrolling, and they don't support adding a custom offset (i.e., scrolling so that the next section starts directly below the header, rather than at the top of the page).

My solution takes advantage of the HTML DOM hierarchy, taking an element and recursively adding its ancestor's position offsets to find the element's total offset on the page. This approach also allows me to find the header element and add its height to the total y offset, essentially treating the bottom of the header as the top of the page. I then use the window.scrollTo() method to scroll by the total offset amount. I pass in the "behavior: 'smooth'" parameter to scroll smoothly.