Project 4

[Logo]

Project 4: Skinning and Mesh Interactivity


A reference implementation of the virtual mannequin, showing a model in one sculpted pose.
[Solution]

In this project, you will implement the first part of a skinning system in WebGL -- a way to animate a 3D model by manipulating "bones" embedded inside the model. To deform the model using skinning, you must implement a basic mouse-based bone selection and rotation interface, which is the focus of this assignment.

Installing TypeScript

As with Assignment 3, you will need to have TypeScript and HTTP-server installed to run the project in your browser. The first two steps do not need to be repeated if you completed them for Assignment 3, but you will need to install on additional package to handle offscreencanvas:
  1. Install the NPM package manager. You can get it here.
  2. Install the TypeScript compiler tsc, and http-server. If NPM is properly installed, you should be able to do this installation using the following commands (might require an Administrator Mode command line on Windows):
    npm install -g typescript
    npm install -g http-server
  3. Install the additional packages for the TypeScript definitions for Off Screen Canvas:
    npm install --save @types/offscreencanvas

Starter Code

As before, we have provided you with starter code, written in TypeScript and using WebGL and several helper libraries (primarily ThreeJS) as a starting point for implementing this project. We have also included code for reading and writing images to JPEG files (this requires a nontrivial amount of low-level parsing and byte manipulation). The starter code is a working example of how to set up WebGL and a shader program to draw a checkerboard floor, and move around the world using an orbital and FPS camera (as per assignment 2).

Assets are in the COLLADA file format (.dae extension), which is an open standard XML schema maintained by the Khronos Group for 3D models and animations. We have included several models for you to test your skinning algorithm on, but feel free to create additional .dae assets and add them to the assets/skinning/ directory. You will also want to add in additional keybinds for loading the model in GUI.ts.

Building the Starter Code

You will use the script make-skinning.py to invoke the tsc compiler on the TypeScript source files in src/. This does some post-processing to combine the Javascript files with static files necessary for the web site to function. The complete web package is installed in dist/. Do not directly edit the files in dist/. All development should take place in the source files in src/. Thus, to compile the code, simply run make-skinning.py from the folder that contains it (the project root folder), and check that your updated file structure is correct.

Running the Starter Code

To run the starter code, first, you must launch the HTTP server. From the project root directory (containing dist/), run:

http-server dist -c-1

If successful, you should get a confirmation message that the server is now running. You can then launch the app by pointing your browser to http://127.0.0.1:8080.

Required Features

Bone Picking

As the user moves the mouse around the screen, you should highlight nearby bones; these highlighted bones are then the ones the user will rotate by clicking and dragging the mouse. Implement the functionality that determines, whenever the user moves the mouse, which bone (if any) should be highlighted. Most of this code will be handled in GUI.ts, but you'll also need to create a RenderPass in App.ts for the highlighting along with custom shader code in Shaders.ts. Notes:

There are three parts to bone picking:

  1. Ray generation.

    Implement a ScreenToWorld function that generates a world space point given screen space (x, y). There are several ways to do this:

    1. Use the camera matrix:
      1. calculate the width and height (w, h) of the image plane in world coordinates (you need to know the field of view)
      2. calculate ndc.x, ndc.y
      3. use (ndc.x, ndc.y), (w, h), look, up, tangent to get the world space coordinates
    2. Use the projection matrix:
      1. unproject (x, y, 0) to get the world space coordinates
      2. Once you have this, subtract the eye position to get a world space ray.
    3. Another method is:
      1. let q be unproject (x, y, 0)
      2. let p be unproject (x, y, 1)
      3. take the ray through the two points p and q
  2. Cylinder intersection.

    Implement a cylinder/ray intersection routine where the cylinder points straight up and is centered at the origin (or lying on its side), has radius R, and height H. Transform the ray into the cylinder's coordinates. Use the bone's orientation to do this.

    Turn this problem into a 2D problem. Project the line onto the (x, z) plane and intersect a line with a circle of radius R. If the ray misses the circle, reject the ray. Else check the intersection times t0 and t1 and see if they are good. Compute the intersection points and take the closest good one. A point is good if it has a y-value between 0 and H. This algorithm does not handle intersection at the caps.

  3. Cylinder drawing.

    Generate a nxn two-dimensional grip of lines. Use a model transformation (which is the bone transformation with a scale along the y-axis applied) to stretch the cylinder out, and a scale in the x-z plane to widen the cylinder. The bone transformation won't contain a scale, since it has to be purely a rotation.

    The vertex shader can wrap the line mesh around (0,0) using (cos(theta), sin(theta)).

Updating the Skeleton

All data related to a character is stored in the .dae file. This file contains the model itself - represented as a triangle mesh along with some information about the materials and textures to use for that mesh - as well as a skeleton rig and blending weights specifying how moving the bones affects the different parts of the model.

We have provided code for parsing and rendering the model mesh. AnimationFileLoader.ts handles loading the COLLADA data, and the mesh and skeleton data is already being rendered to the screen in App.ts. You will find some initial data structures to help you get started in Scene.ts. Please look over these classes to get an idea of how the data is stored, but you are welcome to change or modify these classes as you see fit.

MeshGeometry contains both the mesh information (such as vertex positions/normals) as well as the skinning information (which bones have what weight). Bone stores the information about the bones in world space. We will be working in world space for this assignment, so your main task will be to update the position of the bone joints (in world space) based on rotations applied to that bone by the animator. You will perform these updated within the Mesh class, which handles both the MeshGeometry and Bone data.

When the user clicks and drags the mouse outside of the mesh, the camera rotates as in assignment 2. Clicking and dragging the mouse on a selected bone rotates that bone and all child bones attached to that bone.

Linear Blend Skinning

The data for the blend weights per mesh vertex is already loaded in MeshGeometry, but in order for the mesh to deform based on the skeleton's updated position/orientation, you will need to modify the sceneVSText shader. Currently the mesh is positioned based on the mesh's resting pose (passed in as vertPosition). Since we want the final vertex positions to be controlled based on the weights of associated bone, this world space position should be calculated based on the translations/rotations of these bones. These translations/rotations are being passed in via jTrans and jRots.

Note the Attributes in MeshGeometry: v0 through v3. These tell us the vertex's position in the coordinate system of the bone i corresponds to skinIndex[i] (i.e. v0 corresponds to skinIndex[0], v1 corresponds to skinIndex[1], etc). Thus to apply the linear blend skinning, we sum the weight of each bone times the vertex position within that bone's coordinate system.

Miscellaneous

Add a shader to handle texture mapping for models. You will also need to update the RenderPass for the model to do so. mapped_cube.dae is a good scene to check that texture mapping is working.

Extra Credit

Apply dual quaternion blend skinning instead of linear blend skinning. Only small adjustments to the math are required to apply dual quaternion blend skinning versus linear blend skinning, but you will want to write a couple helper functions to apply the dual to the vertices.

Design Document

Create a design document exploring how you will implement the math and code architecture necessary for Assignment 4. This design document is intended to provide you enough of a scaffold that you can begin implementing your solution in an organized, coherent way.

This design document should demonstrate your understanding of the existing code base, as well as the required functionality and steps needed to create an animated model. This means you should look carefully through the provided code base (including the shaders) before starting on this document. This is especially true if you are new to asynchronous programming, since a lot of the design is built around those principles.

This also means you should take time to understand the purpose of all the math we covered in class. It's easy to lose sight of the forest (i.e. creating an animated 3D model) when we're focusing on the leaves (i.e. affine transformations), but my experience is that students who are able to understand how the math relates to the tangible goals of the project have an easier time completing the assignment than those who are rotely going through the list of required features.

To accomplish all of the above, your design document should explain the following:

  1. Existing class and functions: Look through all of the existing functions and classes in main body of the skeleton code (App.ts, GUI.ts, Shaders.ts, and Scene.ts) and document what they do and how they do it.
  2. Bone/joint hierarchy: Determine the sorts of class structures you could implement to store the necessary data to create an animation hierarchy. This is your first approximation, so you may get it wrong, but document both the fields and their data types and methods and their signatures along with a brief explanation of how you'll use those fields/methods if it's not clear from context.
  3. Additional functionality: Document the high level functionality of what you'll be implementing. This should be high enough level that it's clear what you're trying to achieve, but low enough level that you can understand the specific functionality you'll need to put into place.
    Once this is in place, describe both the math these functionalities will require and write out expected methods (and where to include them) in order to implement this math. This should include exploring how you will modify the shaders to allow for animation.
  4. Questions and Uncertainties: Include a section listing off any unanswered questions you have, or concerns about how to implement. These should be questions that were raised while writing the document, but that you were unable to answer even after taking time to think through the problem.

A4 Milestone

For your A4 milestone, submit a Gitlab link to the project on a branch called a4-milestone. This should include the stable code you've written so far along with progress report as a .pdf document stating what has been accomplished (and by whom if working with a partner), and what the plan is for completing the assignment on time. Include some image artifacts of what's been implemented as well.

As a guideline of what we're expecting, you should have all of the bone-picking functionality completed. You should also be making good progress into mouse controls and skeleton rotation. Note that we aren't grading you on the quality of your controls, but you should have reasonable interactions to make grading easier.

In terms of your plan, please try to be specific -- this document is also intended to help you think through what needs to be done and what a reasonable timeline should be. Therefore, you should include:

Submission Instructions

Submit your project via GitLab and provide a link to it via Canvas. Make sure to keep your repository private and give access to me (thesharkcs) and the TA (TA GitLab usernames are posted to Announcements on Canvas). We will be grading based on a branch called a4-code-freeze, so please make sure you have a working final version of the project on that branch. If commits are made after the deadline, we will treat that as a late submission, and we will deduct late slips accordingly and/or apply the late submission penalty as per the syllabus.

In addition to your complete source code, the project should include:

Grading

You'll be graded on how well you met the requirements of the assignment. D quality or below work has few or none of the features implemented/working. C quality work has implemented some or most of the features, but the features are built in a way that is not fully working and/or the logic is not well-considered. B quality work has implemented most or all of the features in a way that works but may have some issues and/or the logic is not fully considered. A quality work has implemented all of the features in robust, polished way that demonstrates your understanding of the assignment, the math, and the provided code base. Note that a well-written report can help us understand your thought process, which is a large part of what we are grading you on in these assignments.

Reminder about Collaboration Policy and Academic Honesty

You are free to talk to other students about the algorithms and theory involved in this assignment, or to get help debugging, but all code you (and your partner if you have one) submit (except for the starter code, and the external libraries included in the starter code) must be your own. Please see the syllabus for the complete collaboration policy.
Last modified: 01/10/24 by Sarah Abraham theshark@cs.utexas.edu