Commit 77a6b9ea authored by allai5's avatar allai5
Browse files

new docs dump

parent cbbc4fa0
theme: jekyll-theme-cayman
baseurl: /Scotty3D
theme: just-the-docs
#baseurl: /Scotty3D
plugins:
- jekyll-sitemap
#logo: "/assets/test_cow.jpeg"
---
layout: default
title: "Animation Overview"
title: "A4: Animation"
permalink: /animation/
nav_order: 7
has_children: true
has_toc: false
---
# Animation Overview
......@@ -10,12 +13,12 @@ There are four primary components that must be implemented to support Animation
**A4.0**
- [(Task 1) Spline Interpolation](splines.md)
- [(Task 2) Skeleton Kinematics](skeleton_kinematics.md)
- [(Task 1) Spline Interpolation](splines)
- [(Task 2) Skeleton Kinematics](skeleton_kinematics)
**A4.5**
- [(Task 3) Linear Blend Skinning](skinning.md)
- [(Task 4) Particle Simulation]() (coming soon)
- [(Task 3) Linear Blend Skinning](skinning)
- [(Task 4) Particle Simulation](particles)
Each task is described at the linked page.
......
---
layout: default
title: Particles
parent: "A4: Animation"
nav_order: 4
permalink: /animation/particles
---
# Particle Simulation
And now for something completely different: physics simulation for particles.
## Ray traced physics
---
layout: default
title: "Skeleton Kinematics"
title: Skeleton Kinematics
parent: "A4: Animation"
nav_order: 2
permalink: /animation/skeleton_kinematics
---
......@@ -8,28 +10,28 @@ permalink: /animation/skeleton_kinematics
A `Skeleton`(defined in `scene/skeleton.h`) is what we use to drive our animation. You can think of them like the set of bones we have in our own bodies and joints that connect these bones. For convenience, we have merged the bones and joints into the `Joint` class which holds the orientation of the joint relative to its parent as euler angle in its `pose`, and `extent` representing the direction and length of the bone with respect to its parent `Joint`. Each `Mesh` has an associated `Skeleton` class which holds a rooted tree of `Joint`s, where each `Joint` can have an arbitrary number of children.
All of our joints are ball `Joint`s which have a set of 3 rotations around the <img src="task2_media/0027.png" height="20">, <img src="task2_media/0028.png" height="20">, and <img src="task2_media/0029.png" height="20"> axes, called _Euler angles_. Whenever you deal with angles in this way, a fixed order of operations must be enforced, otherwise the same set of angles will not represent the same rotation. In order to get the full rotational transformation matrix, <img src="task2_media/0030.png" height="20">, we can create individual rotation matrices around the<img src="task2_media/0031.png" height="20">, <img src="task2_media/0032.png" height="20">, and <img src="task2_media/0033.png" height="20"> axes, which we call <img src="task2_media/0034.png" height="20">, <img src="task2_media/0035.png" height="20">, and <img src="task2_media/0036.png" height="20"> respectively. The particular order of operations that we adopted for this assignment is that <img src="task2_media/0037.png" height="20">.
All of our joints are ball `Joint`s which have a set of 3 rotations around the <img src="task2_media/0027.png" style="height:14px">, <img src="task2_media/0028.png" style="height: 16px">, and <img src="task2_media/0029.png" style="height: 16px"> axes, called _Euler angles_. Whenever you deal with angles in this way, a fixed order of operations must be enforced, otherwise the same set of angles will not represent the same rotation. In order to get the full rotational transformation matrix, <img src="task2_media/0030.png" style="height:16px">, we can create individual rotation matrices around the <img src="task2_media/0031.png" style="height:16px">, <img src="task2_media/0032.png" style="height:16px">, and <img src="task2_media/0033.png" style="height:16px"> axes, which we call <img src="task2_media/0034.png" style="height:20px">, <img src="task2_media/0035.png" style="height:20px">, and <img src="task2_media/0036.png" style="height:20px"> respectively. The particular order of operations that we adopted for this assignment is that <img src="task2_media/0037.png" style="height:20px">.
### Forward Kinematics
_Note: These diagrams are in 2D for visual clarity, but we will work with a 3D kinematic skeleton._
When a joint's parent is rotated, that transformation should be propagated down to all of its children. In the diagram below, <img src="task2_media/0038.png" height="20"> is the parent of <img src="task2_media/0039.png" height="20"> and <img src="task2_media/0040.png" height="20"> is the parent of <img src="task2_media/0041.png" height="20">. When a translation of <img src="task2_media/0042.png" height="20"> and rotation of <img src="task2_media/0043.png" height="20"> is applied to <img src="task2_media/0044.png" height="20">, all of the descendants are affected by this transformation as well. Then, <img src="task2_media/0045.png" height="20"> is rotated by <img src="task2_media/0046.png" height="20"> which affects itself and <img src="task2_media/0047.png" height="20">. Finally, when rotation of <img src="task2_media/0048.png" height="20"> is applied to <img src="task2_media/0049.png" height="20">, it only affects itself because it has no children.
When a joint's parent is rotated, that transformation should be propagated down to all of its children. In the diagram below, <img src="task2_media/0038.png" style="height:18px"> is the parent of <img src="task2_media/0039.png" style="height:18px"> and <img src="task2_media/0040.png" style="height:18px"> is the parent of <img src="task2_media/0041.png" style="height:18px">. When a translation of <img src="task2_media/0042.png" style="height:18px"> and rotation of <img src="task2_media/0043.png" style="height:18px"> is applied to <img src="task2_media/0044.png" style="height:18px">, all of the descendants are affected by this transformation as well. Then, <img src="task2_media/0045.png" style="height:18px"> is rotated by <img src="task2_media/0046.png" style="height:18px"> which affects itself and <img src="task2_media/0047.png" style="height:18px">. Finally, when rotation of <img src="task2_media/0048.png" style="height:18px"> is applied to <img src="task2_media/0049.png" style="height:18px">, it only affects itself because it has no children.
<img src="task2_media/forward_kinematic_diagram.jpg">
<center><img src="task2_media/forward_kinematic_diagram.jpg" style="height:480px"></center>
You need to implement these routines in `student/skeleton.cpp` for forward kinematics.
* `Joint::joint_to_bind`
* `Joint::joint_to_bind`
Rreturn a matrix transforming points in the space of this joint
to points in mesh space in bind position up to the base of this joint (end of its parent joint). You should traverse upwards from this joint's parent all the way up to the root joint and accumulate their transformations.
to points in mesh space in bind position up to the base of this joint (end of its parent joint). You should traverse upwards from this joint's parent all the way up to the root joint and accumulate their transformations.
* `Joint::joint_to_posed`
Return a matrix transforming points in the space of this joint to points in mesh space, taking into account joint poses. Again, you should traverse upwards from this joint's parent to the root joint.
* `Skeleton::end_of`
Returns the end position of the joint in world coordinate frame, and you should take into account the base position of the skeleton (`Skeleton::base_pos`).
* `Skeleton::posed_end_of`
Returns the end position of the joint in world coordinate frame with poses, and you should take into account `Skeleton::base_pos`.
* `Skeleton::joint_to_bind`
* `Skeleton::joint_to_bind`
Rreturn a matrix transforming points in the space of this joint
to points in mesh space in bind position but with the base position of the skeleton taken in to account. Hint: use some function that you have implemented wisely!
* `Skeleton::joint_to_posed`
......@@ -37,68 +39,64 @@ You need to implement these routines in `student/skeleton.cpp` for forward kinem
Once you have implemented these basic kinematics, you should be able to define skeletons, set their positions at a collection of keyframes, and watch the skeleton smoothly interpolate the motion (see the [user guide](../guide/animate.md) for an explanation of the interface). The gif below shows a very hasty demo defining a few joints and interpolating their motion.
![gif1](task2_media/gif1.gif)
![gif1](task2_media/gif2.gif)
<center><img src="task2_media/gif1.gif"></center>
<center><img src="task2_media/gif2.gif"></center>
Note that the skeleton does not yet influence the geometry of the cube in this scene -- that will come in Task 3!
### Task 2b - Inverse Kinematics
#### Single Target IK
Now that we have a logical way to move joints around, we can implement Inverse Kinematics, which will move the joints around in order to reach a target point. There are a few different ways we can do this, but for this assignment we'll implement an iterative method called gradient descent in order to find the minimum of a function. For a function <img src="task2_media/0050.png" height="20">, we'll have the update scheme:
### Single Target IK
<img src="task2_media/0051.png" height="20">
Now that we have a logical way to move joints around, we can implement Inverse Kinematics, which will move the joints around in order to reach a target point. There are a few different ways we can do this, but for this assignment we'll implement an iterative method called gradient descent in order to find the minimum of a function. For a function <img src="task2_media/0050.png" style="height:18px">, we'll have the update scheme:
Where <img src="task2_media/0052.png" height="9"> is a small timestep. For this task, we'll be using gradient descent to find the minimum of the cost function:
<center><img src="task2_media/0051.png" style="height:26px"></center>
<img src="task2_media/0053.png">
Where <img src="task2_media/0052.png" style="height:14px"> is a small timestep. For this task, we'll be using gradient descent to find the minimum of the cost function:
Where <img src="task2_media/0054.png" height="20"> is the position in world space of the target joint, and <img src="task2_media/0055.png" height="20"> is the position in world space of the target point.
<center><img src="task2_media/0053.png" style="height:50px"></center>
More specifically, we'll be using a technique called Jacobian Transpose, which relies on the assumption that:
Where <img src="task2_media/0054.png" style="height:24px"> is the position in world space of the target joint, and <img src="task2_media/0055.png" style="height:18px"> is the position in world space of the target point. More specifically, we'll be using a technique called Jacobian Transpose, which relies on the assumption:
<img src="task2_media/0056.png" height="20">
<center><img src="task2_media/0056.png" style="height:30px"></center>
Where:
* <img src="task2_media/0057.png" height="14"> (n x 1) is the function <img src="task2_media/0058.png" height="19">, where <img src="task2_media/0059.png" height="19"> is the angle of joint <img src="task2_media/0060.png" height="14"> around the axis of rotation
* <img src="task2_media/0061.png" height="9"> is a constant
* <img src="task2_media/0062.png" height="16"> (3 x n) is the Jacobian of <img src="task2_media/0063.png" height="14">
Note that here <img src="task2_media/0064.png" height="9"> refers to the number of joints in the skeleton. Although in reality this can be reduced to just the number of joints between the target joint and the root, inclusive, because all joints not on that path should stay where they are, so their columns in <img src="task2_media/0065.png" height="16"> will be 0\. So <img src="task2_media/0066.png" height="9"> can just be the number of joints between the target and the root, inclusive. Additionally note that since this will get multiplied by <img src="task2_media/0067.png" height="9"> anyways, you can ignore the value of <img src="task2_media/0068.png" height="9">, and just consider the timestep as <img src="task2_media/0069.png" height="18">.
* <img src="task2_media/0057.png" style="height:20px"> (n x 1) is the function <img src="task2_media/0058.png" style="height:22px">, where <img src="task2_media/0059.png" style="height:22px"> is the angle of joint <img src="task2_media/0060.png" style="height:18px"> around the axis of rotation
* <img src="task2_media/0061.png" style="height:16px"> is a constant
* <img src="task2_media/0062.png" style="height:22px"> (3 x n) is the Jacobian of <img src="task2_media/0063.png" style="height:18px">
Now we just need a way to calcluate the Jacobian of <img src="task2_media/0070.png" height="14">. For this, we can use the fact that:
Note that here <img src="task2_media/0064.png" style="height:14px"> refers to the number of joints in the skeleton. Although in reality this can be reduced to just the number of joints between the target joint and the root, inclusive, because all joints not on that path should stay where they are, so their columns in <img src="task2_media/0065.png" style="height:20px"> will be 0\. So <img src="task2_media/0066.png" style="height:14px"> can just be the number of joints between the target and the root, inclusive. Additionally note that since this will get multiplied by <img src="task2_media/0067.png" style="height:16px"> anyways, you can ignore the value of <img src="task2_media/0068.png" style="height:14px">, and just consider the timestep as <img src="task2_media/0069.png" style="height:16px">.
<img src="task2_media/0071.png" height="20">
Now we just need a way to calcluate the Jacobian of <img src="task2_media/0070.png" style="height:16px">. For this, we can use the fact that:
<center><img src="task2_media/0071.png" style="height:34px"></center>
Where:
* <img src="task2_media/0072.png" height="16"> is the <img src="task2_media/0073.png" height="16"> column of <img src="task2_media/0074.png" height="19">
* <img src="task2_media/0075.png" height="14"> is the axis of rotation
* <img src="task2_media/0076.png" height="16"> is the vector from the base of joint <img src="task2_media/0077.png" height="14"> to the end point of the target joint
* <img src="task2_media/0072.png" style="height:24px"> is the <img src="task2_media/0073.png" style="height:24px"> column of <img src="task2_media/0074.png" style="height:24px">
* <img src="task2_media/0075.png" style="height:24px"> is the axis of rotation
* <img src="task2_media/0076.png" style="height:24px"> is the vector from the base of joint <img src="task2_media/0077.png" style="height:24px"> to the end point of the target joint
For a more in-depth derivation of Jacobian transpose (and a look into other inverse kinematics algorithms), please check out [this presentation](https://web.archive.org/web/20190501035728/https://autorob.org/lectures/autorob_11_ik_jacobian.pdf). (Pages 45-56 in particular)
Now, all of this will work for updating the angle along a single axis, but we have 3 axes to deal with. Luckily, extending it to 3 dimensions isn't very difficult, we just need to update the angle along each axis independently.
#### Multi-Target
### Multi-Target
We'll extend this so we can have multiple targets, which will then use the function to minimize:
<img src="task2_media/0078.png">
<center><img src="task2_media/0078.png"></center>
which is a simple extension actually. Since each term is independent and added together, we can get the gradient of this new cost function just by summing the gradients of each of the constituent cost functions!
You should implement multi-target IK, which will take a `vector` of `IK_Handle*`s called `active_handles` which stores the information a target point for a joint. See `scene/skeleton.h` for the definition of `IK_Handle` structure.
In order to implement this, you should update `Joint::compute_gradient` and `Skeleton::step_ik`. `Joint::compute_gradient` should calculate the gradient of <img src="task2_media/0079.png" height="14"> in the x,y, and z directions, and add them to `Joint::angle_gradient` for all relevant joints. `Skeleton::step_ik` should actually do the gradient descent calculations and update the `pose` of each joint. In this function, you should probably use a very small timestep, but do several iterations (say, 10s to 100s) of gradient descent in order to speed things up. For even faster and better results, you can also implement a variable timestep instead of just using a fixed one. Note also that the root joint should never be updated.
In order to implement this, you should update `Joint::compute_gradient` and `Skeleton::step_ik`. `Joint::compute_gradient` should calculate the gradient of <img src="task2_media/0079.png" style="height:18px"> in the x,y, and z directions, and add them to `Joint::angle_gradient` for all relevant joints. `Skeleton::step_ik` should actually do the gradient descent calculations and update the `pose` of each joint. In this function, you should probably use a very small timestep, but do several iterations (say, 10s to 100s) of gradient descent in order to speed things up. For even faster and better results, you can also implement a variable timestep instead of just using a fixed one. Note also that the root joint should never be updated.
A key thing for this part is to _remember what coordinate frame you're in_, because if you calculate the gradients in the wrong coordinate frame or use the axis of rotation in the wrong coordinate frame your answers will come out very wrong!
#### Using your IK!
### Using your IK!
Once you have IK implemented, you should be able to create a series of joints, and get a particular joint to move to the desired final position you have selected.
---
layout: default
title: "Animation Overview"
title: Skinning
parent: "A4: Animation"
nav_order: 3
permalink: /animation/skinning
---
......@@ -8,7 +10,7 @@ permalink: /animation/skinning
Now that we have a skeleton set up, we need to link the skeleton to the mesh in order to get the mesh to follow the movements of the skeleton. We will implement linear blend skinning using the following functions: `Skeleton::skin()`, `Skeleton::find_joints()`, and `closest_on_line_segment`.
The easiest way to do this is to update each of mesh vertices' positions in relation to the bones (Joints) in the skeleton. There are 3 types of coordinate spaces: bind, joint, and pose. Bind is the initial coordinate frame of the vertices of where they are bound to relative to the mesh. Joint is the position of the vertex relative to a given joint. Pose is the world-spacce position after the joint transforms have been applied. You'll want to compute transforms that take vertices in bind space and convert them to posed space (Hint: `joint_to_bind`, `joint_to_posed`, and `inverse()` will come in handy.)
The easiest way to do this is to update each of mesh vertices' positions in relation to the bones (Joints) in the skeleton. There are 3 types of coordinate spaces: bind, joint, and pose. Bind is the initial coordinate frame of the vertices of where they are bound to relative to the mesh. Joint is the position of the vertex relative to a given joint. Pose is the world-space position after the joint transforms have been applied. You'll want to compute transforms that take vertices in bind space and convert them to posed space (Hint: `joint_to_bind`, `joint_to_posed`, and `inverse()` will come in handy.)
Your implementation should have the following basic steps for each vertex:
......@@ -16,14 +18,19 @@ Your implementation should have the following basic steps for each vertex:
- Compute the vertex's position with respect to each joint j in the skeleton in j's coordinate frame when no transformations have been applied to the skeleton (bind pose, vertex bind position).
- Find where this vertex would end up (in world coordinates) if it were transformed along with bone j.
- Find the closest point on joint j's bone segment (axis) and compute the distance to this closest point (Hint: `closest_on_line_segment` might come in handy).
- Diagram of `closest_on_line_segment`:
<center><img src="task3_media/closest_on_line_segment.png" style="height:280px"></center>
- Compute the resulting position of the vertex by doing a weighted average of the bind-to-posed transforms from each bone and applying it to the vertex. The weights for the weighted average should be the inverse distance to the joint, so closer bones have a stronger influence.
Below we have an equation representation. The ith vertex v is the new vertex position. The weight w is the weight metric computed as the inverse of distance between the ith vertex and the closest point on joint j. We multiply this term with the position of the ith vertex v with respect to joint j after joint's transformations has been applied.
![skinnning_equations](task3_media/skinning_equations.png)
<!--# TODO: fix this-->
<!--![skinnning_equations](task3_media/skinning_equations.png)-->
<center><img src="task3_media/skinning_eqn1.png" style="height:100px">
<img src="task3_media/skinning_eqn2.png" style="height:120px"></center>
In Scotty3D, the `Skeleton::skin()` function gets called on every frame draw iteration, recomputing all skinning related quantities. In this function, you should read vertices from `input.verts()` and indices from `input.indices()`, and write the resulting positions and norms to `v.pos` and `v.norm` for every vertex in the input vertices list.
In Scotty3D, the `Skeleton::skin()` function gets called on every frame draw iteration, recomputing all skinning related quantities. In this function, you should read vertices from `input.verts()` and indices from `input.indices()`, and write the resulting positions and norms to `v.pos` and `v.norm` for every vertex in the input vertices list.
You will be implementing a Capsul-Radius Linear Blend Skin method, which only moves vertices with a joint if they lie in the joint's radius. The `Skeleton::skin()` function also takes in a `map` of vertex index to relevant joints that you must compute the above distance/transformation metrics on. You are also responsible for creating this `map`, which is done so in `Skeleton::find_joints()`. Don't worry about calling this function, it is called automatically before skin is called, populating the `map` field and sending it over to the `skin()` function. Your `Skeleton::find_joints()` implementation should iterate over all the vertices and add joint j to vertex index i in the map if the distance between the vertex and joint is less than `j->radius` (remember make sure they're both in the same coordinate frame.)
You will be implementing a Capsule-Radius Linear Blend Skin method, which only moves vertices with a joint if they lie in the joint's radius. The `Skeleton::skin()` function also takes in a `map` of vertex index to relevant joints that you must compute the above distance/transformation metrics on. You are also responsible for creating this `map`, which is done so in `Skeleton::find_joints()`. Don't worry about calling this function, it is called automatically before skin is called, populating the `map` field and sending it over to the `skin()` function. Your `Skeleton::find_joints()` implementation should iterate over all the vertices and add joint j to vertex index i in the map if the distance between the vertex and joint is less than `j->radius` (remember make sure they're both in the same coordinate frame.)
![skinning](task3_media/skinning.gif)
\ No newline at end of file
<center><img src="task3_media/skinning.gif" style="height:240px"></center>
---
layout: default
title: "Animation Overview"
title: Splines
parent: "A4: Animation"
nav_order: 1
permalink: /animation/splines
---
# Spline Interpolation
As we discussed in class, data points in time can be interpolated by constructing an approximating piecewise polynomial or spline. In this assignment you will implement a particular kind of spline, called a Catmull-Rom spline. A Catmull-Rom spline is a piecewise cubic spline defined purely in terms of the points it interpolates. It is a popular choice in real animation systems, because the animator does not need to define additional data like tangents, etc. (However, your code may still need to numerically evaluate these tangents after the fact; more on this point later.) All of the methods relevant to spline interpolation can be found in `spline.h` with implementations in `spline.inl`.
### Task 1a - Hermite Curve over the Unit Interval
Recall that a cubic polynomial is a function of the form
Recall that a cubic polynomial is a function of the form:
<img src="task1_media/0000.png" height="30">
<center><img src="task1_media/0000.png" style="height:30px"></center>
where <img src="task1_media/0001.png" height="20">, and <img src="task1_media/0002.png" height="20"> are fixed coefficients. However, there are many different ways of specifying a cubic polynomial. In particular, rather than specifying the coefficients directly, we can specify the endpoints and tangents we wish to interpolate. This construction is called the "Hermite form" of the polynomial. In particular, the Hermite form is given by
where <img src="task1_media/0001.png" style="height:24px">, and <img src="task1_media/0002.png" style="height:20px"> are fixed coefficients. However, there are many different ways of specifying a cubic polynomial. In particular, rather than specifying the coefficients directly, we can specify the endpoints and tangents we wish to interpolate. This construction is called the "Hermite form" of the polynomial. In particular, the Hermite form is given by
<img src="task1_media/0003.png" height="30">
<center><img src="task1_media/0003.png" style="height:28px"></center>
where <img src="task1_media/0004.png" height="20"> are the endpoint positions, <img src="task1_media/0005.png" height="20"> are the endpoint tangents, and <img src="task1_media/0006.png" height="20"> are the Hermite bases
where <img src="task1_media/0004.png" style="height:16px"> are endpoint positions, <img src="task1_media/0005.png" style="height:16px"> are endpoint tangents, and <img src="task1_media/0006.png" style="height:24px"> are the Hermite bases
<img src="task1_media/0007.png" height="30"> <br/>
<center><img src="task1_media/0007.png" style="height:30px"> <br/></center>
<center><img src="task1_media/0008.png" style="height:30px"> <br/></center>
<center><img src="task1_media/0009.png" style="height:30px"> <br/></center>
<center><img src="task1_media/0010.png" style="height:30px"> <br/></center>
<img src="task1_media/0008.png" height="30"> <br/>
<img src="task1_media/0009.png" height="30"> <br/>
<img src="task1_media/0010.png" height="30"> <br/>
Your first task is to implement the method `Spline::cubic_unit_spline()`, which evaluates a spline defined over the time interval <img src="task1_media/0011.png" height="20"> given a pair of endpoints and tangents at endpoints.
Your first task is to implement the method `Spline::cubic_unit_spline()`, which evaluates a spline defined over the time interval <img src="task1_media/0011.png" style="height:24px"> given a pair of endpoints and tangents at endpoints.
Your basic strategy for implementing this routine should be:
* Evaluate the time, its square, and its cube (for readability, you may want to make a local copy).
* Using these values, as well as the position and tangent values, compute the four basis functions <img src="task1_media/0012.png" height="20"><img src="task1_media/0013.png" height="20"> of a cubic polynomial in Hermite form.
* Using these values, as well as the position and tangent values, compute the four basis functions <img src="task1_media/0012.png" style="height:20px"><img src="task1_media/0013.png" style="height:20px"> of a cubic polynomial in Hermite form.
* Finally, combine the endpoint and tangent data using the evaluated bases, and return the result.
Notice that this function is templated on a type T. In C++, a templated class can operate on data of a variable type. In the case of a spline, for instance, we want to be able to interpolate all sorts of data: angles, vectors, colors, etc. So it wouldn't make sense to rewrite our spline class once for each of these types; instead, we use templates. In terms of implementation, your code will look no different than if you were operating on a basic type (e.g., doubles). However, the compiler will complain if you try to interpolate a type for which interpolation doesn't make sense! For instance, if you tried to interpolate `Skeleton` objects, the compiler would likely complain that there is no definition for the sum of two skeletons (via a + operator). In general, our spline interpolation will only make sense for data that comes from a vector space, since we need to add T values and take scalar multiples.
......@@ -42,32 +42,34 @@ The routine from part 1A just defines the interpolated spline between two points
The basic idea behind Catmull-Rom is that for a given time t, we first find the four closest knots at times
<img src="task1_media/0014.png" height="30">
<center><img src="task1_media/0014.png" style="height:20px"></center>
We then use t1 and t2 as the endpoints of our cubic "piece," and for tangents we use the values
<img src="task1_media/0015.png" height="30"> <br/>
<img src="task1_media/0016.png" height="30"> <br/>
<center><img src="task1_media/0015.png" style="height:24px"> <br/></center>
<center><img src="task1_media/0016.png" style="height:24px"> <br/></center>
In other words, a reasonable guess for the tangent is given by the difference between neighboring points. (See the Wikipedia and our course slides for more details.)
<img src="task1_media/spline_diagram.jpg"> <br/>
<center><img src="task1_media/spline_diagram.jpg" style="height:240px"> <br/></center>
This scheme works great if we have two well-defined knots on either side of the query time t. But what happens if we get a query time near the beginning or end of the spline? Or what if the spline contains fewer than four knots? We still have to somehow come up with a reasonable definition for the positions and tangents of the curve at these times. For this assignment, your Catmull-Rom spline interpolation should satisfy the following properties:
* If there are no knots at all in the spline, interpolation should return the default value for the interpolated type. This value can be computed by simply calling the constructor for the type: T(). For instance, if the spline is interpolating Vector3D objects, then the default value will be <img src="task1_media/0017.png" height="20">.
* If there are no knots at all in the spline, interpolation should return the default value for the interpolated type. This value can be computed by simply calling the constructor for the type: T(). For instance, if the spline is interpolating Vector3D objects, then the default value will be <img src="task1_media/0017.png" style="height:20px">.
* If there is only one knot in the spline, interpolation should always return the value of that knot (independent of the time). In other words, we simply have a constant interpolant.
* If the query time is less than or equal to the initial knot, return the initial knot's value.
* If the query time is greater than or equal to the final knot, return the final knot's value.
Once we have two or more knots, interpolation can be handled using general-purpose code. In particular, we can adopt the following "mirroring" strategy to obtain the four knots used in our computation:
* Any query time between the first and last knot will have at least one knot "to the left" <img src="task1_media/0018.png" height="20"> and one "to the right" <img src="task1_media/0019.png" height="20">.
* Suppose we don't have a knot "two to the left" <img src="task1_media/0020.png" height="20">. Then we will define a "virtual" knot <img src="task1_media/0021.png" height="20">. In other words, we will "mirror" the difference be observe between <img src="task1_media/0022.png" height="20"> and <img src="task1_media/0023.png" height="20"> to the other side of <img src="task1_media/0024.png" height="20">.
* Likewise, if we don't have a knot "two to the right" <img src="task1_media/0025.png" height="20">), then we will "mirror" the difference to get a "virtual" knot <img src="task1_media/0026.png" height="20">.
* Any query time between the first and last knot will have at least one knot "to the left" <img src="task1_media/0018.png" style="height:22px"> and one "to the right" <img src="task1_media/0019.png" style="height:22px">.
* Suppose we don't have a knot "two to the left" <img src="task1_media/0020.png" style="height:22px">. Then we will define a "virtual" knot <img src="task1_media/0021.png" style="height:22px">. In other words, we will "mirror" the difference be observe between <img src="task1_media/0022.png" style="height:22px"> and <img src="task1_media/0023.png" style="height:22px"> to the other side of <img src="task1_media/0024.png" style="height:22px">.
* Likewise, if we don't have a knot "two to the right" <img src="task1_media/0025.png" style="height:22px">), then we will "mirror" the difference to get a "virtual" knot <img src="task1_media/0026.png" style="height:22px">.
* At this point, we have four valid knot values (whether "real" or "virtual"), and can compute our tangents and positions as usual.
* These values are then handed off to our subroutine that computes cubic interpolation over the unit interval.
<center><img src="task1_media/evaluate_catmull_rom_spline.png" style="height:300px"> <br/></center>
An important thing to keep in mind is that `Spline::cubic_unit_spline()` assumes that the time value t is between 0 and 1, whereas the distance between two knots on our Catmull-Rom spline can be arbitrary. Therefore, when calling this subroutine you will have to normalize t such that it is between 0 and 1, i.e., you will have to divide by the length of the current interval over which you are interpolating. You should think very carefully about how this normalization affects the value computed by the subroutine, in comparison to the values we want to return. A transformation is necessary for both the tangents that you feed in to specify the unit spline.
Internally, a Spline object stores its data in an STL map that maps knot times to knot values. A nice thing about an STL map is that it automatically keeps knots in sorted order. Therefore, we can quickly access the knot closest to a given time using the method `map::upper_bound()`, which returns an iterator to knot with the smallest time greater than the given query time (you can find out more about this method via online documentation for the Standard Template Library).
......@@ -81,4 +83,4 @@ Once you have implemented the functions in `spline.cpp`, you should be able to m
<img src="task1_media/animate_cow.gif">
<center><img src="task1_media/animate_cow.gif"></center>
---
layout: default
title: "Building Scotty3D"
nav_order: 3
permalink: /build/
---
......@@ -12,7 +13,7 @@ To get a copy of the codebase, see [Git Setup](git).
Note: the first build on any platform will be very slow, as it must compile most dependencies. Subsequent builds will only need to re-compile your edited Scotty3D code.
### Linux
### Linux
The following packages (ubuntu/debian) are required, as well as CMake and either gcc or clang:
```
......@@ -29,7 +30,7 @@ Finally, to build the project:
```
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
make -j4
```
......@@ -46,7 +47,7 @@ The windows build is easiest to set up using the Visual Studio compiler (for now
You can download CMake for windows [here](https://cmake.org/download/).
Once the Visual Studio compiler (MSVC) is installed, you can access it by running "Developer Command Prompt for VS 2019," which opens a terminal with the utilities in scope. The compiler is called ``cl``. You can also import these utilities in any terminal session by running the script installed at ``C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat``.
Once the Visual Studio compiler (MSVC) is installed, you can access it by running "Developer Command Prompt for VS 2019," which opens a terminal with the utilities in scope. The compiler is called ``cl``. You can also import these utilities in any terminal session by running the script installed at ``C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat``.
We also provide a simple script, ``build_win.bat``, that will automatically import the compiler and build the project. You should be able to simply run it in the project root to build. ``Scotty3D.exe`` will be generated under ``build/RelWithDebInfo/``.
......@@ -73,7 +74,7 @@ To build the project:
```
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
make -j4
```
......
---
layout: default
title: "GitHub Setup"
nav_order: 2
permalink: /git/
---
# Github Setup
Please do not use a public github fork of this repository! We do not want solutions to be public. You should work in your own private repo.
We recommended creating a mirrored private repository with multiple remotes. The following steps go over how to achieve this.
......@@ -20,7 +22,7 @@ The easiest (but not recommended) way is to download a zip from GitHub and make
- When you clone a git repository, the default remote is named 'origin' and set to the URL you cloned from.
- We will set the `origin` of our local clone to point to `MyScotty3D.git`, but also have a remote called `sourcerepo` for the public `Scotty3D` repository.
4. Now go back to your clone of Scotty3D. This is how we add the private remote:
4. Now go back to your clone of Scotty3D. This is how we add the private remote:
- Since we cloned from the `CMU-Graphics/Scotty3D.git` repository, the current value of `origin` should be `https://github.com/CMU-Graphics/Scotty3D.git`
- You can check this using `git remote -v`, which should show:
```
......@@ -37,7 +39,7 @@ The easiest (but not recommended) way is to download a zip from GitHub and make
5. Congratulations! you have successfully _mirrored_ a git repository with all past commits intact. Let's see a case where this becomes very useful: we start doing an assignment and commit regularly to our private repo (our `origin`). Then the 15-462 staff push some new changes to the Scotty3D skeleton code. We now want to pull the changes from our `sourcerepo`. But, we don't want to mess up the changes we've added to our private copy. Here's where git comes to the rescue:
- First commit all current changes to your `origin`
- Run `git pull sourcerepo master` - this pulls all the changes from `sourcerepo` into your local folder
- If there are files that differ in your `origin` and in the `sourcerepo`, git will attempt to automatically merge the changes. Git may create a "merge" commit for this.
- If there are files that differ in your `origin` and in the `sourcerepo`, git will attempt to automatically merge the changes. Git may create a "merge" commit for this.
- Unfortunately, there may be merge conflicts. Git will handle as many merges as it can, and then will then tell you which files have conflicts that need manual resolution. You can resolve those conflicts in your text editor and create a new commit to complete the `merge` process.
- After you have completed the merge, you now have all the updates locally. Push to your private origin to include the changes there too:
- `git push origin master`
......
---
layout: default
title: "Animate"
permalink: /guide/animate/
permalink: /guide/animate_mode/
parent: User Guide
---
# Animate
When you select the Animate tab, a timeline window will show up at the bottom of your screen.
Animation is performed by creating **keyframes** and **interpolating** between them.
### Keyframes
A keyframe associates an object's pose with a specific frame in the timeline.
Keyframes can be associated with all the objects (including the camera and rig joints associated with an object) in the scene.
A keyframe associates an object's pose and properties with a specific frame in the timeline.
Keyframes can be associated with all objects (including the camera and individual joints associated with an object) in the scene.
To create a keyframe for an object:
- Select a frame location along that objects timeline by clicking on the timeline. Remember to do this *before* editing the pose.
- Change the pose of the selected object and press `Set`. To associate keyframes with all the objects, use `Set All`. Note that for rig joints, only joint rotation can be performed.
- Click on a keyframe in the timeline, and press `Clear` to remove it.
- Change the pose or properties of the selected object and press `Set`. To set a keyframe for every object in the scene, use `Set All`. Note that only the poses (rotations) of joints can be animated, not their extents.
To remove a keyframe in the timeline, click on it and press `Clear` to remove it. Press `Clear All` to clear the current keyframe of every object in the scene.
![animating-cow](../animation/task1_media/animate_cow.gif)
![animating-cow](../../animation/task1_media/animate_cow.gif)
To see your animation, press `Play [space]` . Once you've implemented **spline interpolation**, intermediate frames are generated by interpolating object poses between keyframes.
Check `Draw Splines` to visualize the spline along which objects are animated.
![view-spline](./animate-mode/guide-animate-spline.png)
![view-spline](../animate_mode/guide-animate-spline.png)
`Add Frames` inserts 90 empty frames into the timeline. `Crop End` deletes frames from the selected location to the end of the timeline.
### Posing
Once you have [rigged](../rig) an object with a skeleton, it can now be posed by selecting a joint and changing its pose i.e., rotating the joint. This is called Forward Kinematics.
Joint poses can also be indirectly changed by using the IK (Inverse Kinematics) handles to provide target positions.
Note that IK handles need to be explicitly enabled using the checkbox.
Joint poses can also be indirectly changed by using the IK (Inverse Kinematics) handles to provide target positions.
Note that IK handles need to be explicitly enabled using the checkbox.
Once you've implemented **forward kinematics**, **inverse kinematics** and **skinning**, as you change the pose, the mesh will deform.
Once you've implemented **forward kinematics**, **inverse kinematics** and **skinning**, as you change the pose, the mesh will deform.
Different poses can be set as keyframes to animate the object.
<video src="{{ site.baseurl }}/guide/animate-mode/guide-posing-rig.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/animate_mode/guide-posing-rig.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
---
layout: default
title: "User Guide"
title: User Guide
permalink: /guide/
nav_order: 4
has_children: true
has_toc: false
---
# User Guide
......@@ -10,8 +13,8 @@ permalink: /guide/
The basic paradigm in Scotty3D is that there are six different _modes_, each
of which lets you perform certain class of actions. For instance, in `Model` mode, you
can perform actions associated with modeling, such as moving mesh elements and performing global mesh operations.
When in `Animate` mode, you can perform actions associated with animation. Etc.
can perform actions associated with modeling, such as moving mesh elements and performing global mesh operations.
When in `Animate` mode, you can perform actions associated with animation. Etc.
Within a given mode, you can
switch between actions by hitting the appropriate key; keyboard commands are
listed below for each mode. Note that the input scheme may change depending on
......@@ -23,7 +26,7 @@ are are detailed in the left sidebar. Note that some actions are only available
## Global Navigation
In all modes, you can move the camera around and select scene elements. Information about your selection will be shown in the left sidebar.
In all modes, you can move the camera around and select scene elements. Information about your selection will be shown in the left sidebar.
The camera can be manipulated in three ways:
- Rotate: holding shift, left-clicking, and dragging will orbit the camera about the scene. Holding middle click and dragging has the same effect.
......@@ -33,19 +36,8 @@ The camera can be manipulated in three ways:
## Global Preferences
You can open the preferences window from the edit option in the menu bar.
- Multisampling: this controls how many samples are used for MSAA when rendering scene objects in the Scotty3D interface. If your computer struggles to render complex scenes, try changing this to `1`.
- Multisampling: this controls how many samples are used for MSAA when rendering scene objects in the Scotty3D interface. If your computer struggles to render complex scenes, try changing this to `1`.
## Global Undo
As is typical, all operations on scene objects, meshes, etc. are un and re-doable using Control/Command-Z to undo and Control/Command-Y to redo. These actions are also available from the `Edit` option in the menu bar.
## Specific Modes
- [Layout](layout)
- [Model](model)
- [Render](render)
- [Rig](rig)
- [Animate](animate)
- [Simulate](simulate)
---
layout: default
title: "Layout"
permalink: /guide/layout/
permalink: /guide/layout_mode/
parent: User Guide
---
# Layout
This is the main scene editing mode in Scotty3D, and does not contain tasks for the student to implement.
This is the main scene editing mode in Scotty3D, and does not contain tasks for the student to implement.
This mode allows you to load full scenes from disk, create or load new objects, export your scene (COLLADA format), and edit transformations that place each object into your scene.
## Creating Objects
......@@ -32,9 +33,9 @@ Scotty3D only supports exporting scenes to COLLADA.
## Managing Objects
Left clicking on or enabling the check box of your object under `Select an Object` will select it. Information about that object's transformation will appear under `Edit Object` beneath the "Select an Object" options.
Left clicking on or enabling the check box of your object under `Select an Object` will select it. Information about that object's transformation will appear under `Edit Object` beneath the "Select an Object" options.
Under `Edit Object`, you may directly edit the values of the object's position, rotation (X->Y->Z Euler angles), and scale. Note that clicking and dragging on the values will smoothly scale them, and Control/Command-clicking on the value will let you edit it as text.
Under `Edit Object`, you may directly edit the values of the object's position, rotation (X->Y->Z Euler angles), and scale. Note that clicking and dragging on the values will smoothly scale them, and Control/Command-clicking on the value will let you edit it as text.
You can also edit the transformation using the `Move`, `Rotate`, and `Scale` tools. One of these options is always active. This determines the transformation widgets that appear at the origin of the object model.
- `Move`: click and drag on the red (X), green (Y), or blue (Z) arrow to move the object along the X/Y/Z axis. Click and drag on the red (YZ), green (XZ), or blue (XY) squares to move the object in the YZ/XZ/XY plane.
......@@ -54,4 +55,4 @@ Finally, you may remove the object from the scene by pressing `Delete` or hittin
## Demo
<video src="{{ site.baseurl }}/guide/layout.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/layout_mode/layout.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
---
layout: default
title: "Model"
permalink: /guide/model/
permalink: /guide/model_mode/
parent: User Guide
---
# Model
......@@ -12,7 +13,7 @@ starting with a simple cube, you can add progressively more detail to produce
interesting 3D shapes. You can also use _subdivision_ to get smooth
approximations of these shapes.
MeshEdit supports four basic actions on mesh elements (move, rotate, scale, and bevel),
MeshEdit supports four basic actions on mesh elements (move, rotate, scale, and bevel),
plus a collection of local and global mesh editing commands.
Note that MeshEdit (and more broadly, Scotty3D) will only operate on meshes that
......@@ -58,21 +59,21 @@ There are three possible types of bevels:
vertices are connected to the edges originally incident on _v_. The new face is
inset (i.e., shunken or expanded) by a user-controllable amount.
<video src="{{ site.baseurl }}/guide/vertex_bevel.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/vertex_bevel.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Edge Bevel: The selected edge _e_ is replaced by a face _f_ whose
vertices are connected to the edges originally incident on the endpoints of _e_.
The new face is inset and offset by some user-controllable amount, as with the
vertex bevel.
<video src="{{ site.baseurl }}/guide/edge_bevel.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/edge_bevel.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Face Bevel: The selected face _f_ is replaced by a new face _g_, as well
as a ring of faces around _g_, such that the vertices of _g_ connect to the
original vertices of _f_. The new face is inset and offset by some
user-controllable amount.
<video src="{{ site.baseurl }}/guide/face_bevel.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/face_bevel.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
### Local Connectivity Editing
......@@ -85,13 +86,13 @@ appropriate key, as listed below. Local mesh editing operations include:
and faces will be replaced with a single face _f_, that is the union of all
faces originally incident on _v_.
<video src="{{ site.baseurl }}/guide/erase_vertex.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/erase_vertex.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Erase Edge: The selected edge _e_ will be replaced with the union of the
faces containing it, producing a new face _e_ (if _e_ is a boundary edge,
nothing happens).
<video src="{{ site.baseurl }}/guide/erase_edge.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/erase_edge.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Edge Collapse: The selected edge _e_ is replaced by a single vertex _v_.
This vertex is connected by edges to all vertices previously connected to either
......@@ -99,25 +100,25 @@ endpoint of _e_. Moreover, if either of the polygons containing _e_ was a
triangle, it will be replaced by an edge (rather than a degenerate polygon with
only two edges).
<video src="{{ site.baseurl }}/guide/collapse_edge.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/collapse_edge.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Face Collapse: The selected face _f_ is replaced by a single vertex _v_.
All edges previously connected to vertices of _f_ are now connected directly to
_v_.
<video src="{{ site.baseurl }}/guide/collapse_face.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/collapse_face.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Edge Flip: The selected edge _e_ is "rotated" around the face, in the
sense that each endpoint moves to the next vertex (in counter-clockwise order)
along the boundary of the two polygons containing _e_.
<video src="{{ site.baseurl }}/guide/edge_flip.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/edge_flip.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Edge Split: [Note: this method is for triangle meshes only!] The
selected edge _e_ is split at its midpoint, and the new vertex _v_ is connected
to the two opposite vertices (or one in the case of a surface with boundary).
<video src="{{ site.baseurl }}/guide/edge_split.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/edge_split.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
### Global Mesh Processing
......@@ -128,43 +129,43 @@ the path tracer), this command will be applied only to the selected mesh.
- Triangulate: Each polygon is split into triangles.
<video src="{{ site.baseurl }}/guide/triangulate.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/triangulate.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Linear Subdivision: Each polygon in the selected mesh is split into
quadrilaterals by inserting a vertex at the midpoint and connecting it to the
midpoint of all edges. New vertices are placed at the average of old vertices so
that, e.g., flat faces stay flat, and old vertices remain where they were.
<video src="{{ site.baseurl }}/guide/linear_subd.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/linear_subd.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Catmull-Clark Subdivision: _[Note: this method is for meshes without boundary only!]_
- Catmull-Clark Subdivision: _[Note: this method is for meshes without boundary only!]_
Just as with linear subdivision, each
polygon is split into quadrilaterals, but this time the vertex positions are
updated according to the [Catmull-Clark subdivision
rules](https://en.wikipedia.org/wiki/Catmull_Clark_subdivision_surface),
ultimately generating a nice rounded surface.
<video src="{{ site.baseurl }}/guide/catmull_subd.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/catmull_subd.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Loop Subdivision: _[Note: this method is for triangle meshes without boundary only!]_
Each triangle is split into four by connecting the edge midpoints. Vertex
positions are updated according to the [Loop subdivision
rules](https://en.wikipedia.org/wiki/Loop_subdivision_surface).
<video src="{{ site.baseurl }}/guide/loop_subd.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/loop_subd.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Isotropic Remeshing: _[Note: this method is for triangle meshes only!]_
The mesh is resampled so that triangles all have roughly the same size and
shape, and vertex valence is close to regular (i.e., about six edges incident on
every vertex).
<video src="{{ site.baseurl }}/guide/remesh.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/remesh.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
- Simplification _[Note: this method is for triangle meshes only!]_ The
number of triangles in the mesh is reduced by a factor of about four, aiming to
preserve the appearance of the original mesh as closely as possible.
<video src="{{ site.baseurl }}/guide/simplify.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
<video src="{{ site.baseurl }}/guide/model_mode/simplify.mp4" controls preload muted loop style="max-width: 100%; margin: 0 auto;"></video>
### Key Bindings
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment