Notes for Nov 18 class Surface of revolution Creating a surface of revolution ```Given a spline curve S(v) that defines a radius from v=0 to v=1, we can define a surface of revolution as follows: For any (ui,vj), the vertex position is: positioni,j = [ S(vj) * cos(2πui) , S(vj) * sin(2πui) , 2 * vj - 1 ] To compute the surface normal at this vertex: dx = cos(2πui) dy = sin(2πui) dz = (S(vj) - S(vj+1)) / 2(vj+1 - vj) normali,j = normalize( [ dx , dy, dz ] ) ``` Using a surface of revolution ```The surface of revolution as defined above will be along the z axis, from z = -1 to z = +1. You need to translate, rotate and scale it to create a shape of desired length and orientation. ``` Orientation of ribbons To create a consistent orientation for a ribbon, you need to follow along the path of the ribbon from beginning to end, at each step creating an iterative update to the ribbon orientation so that the orientation remains consistent, without unwanted twists. One way to do this is in two stages. First, compute the path from beginning to end, together with orientation. Then, use that path to build the actual triangle strip geometry. The general idea is as follows: (1) Building the path: ```At each step along the path, we use a spline function to compute point Pi along the center of the ribbon. At each step along the path, we can compute direction along the path Wi - Pi+1 - Pi,k as well as two mutually perpendicular cross vectors Ui and Vi. We will do successive cross products to compute Ui and Vi at each step, while avoiding twists. Given initial ribbon direction W0 = normalize(P1 - P0), choose an initial cross vector U0 perpendicular to W0. Compute V0 = W0 ⨯ U0 For i = 1 to n along path: Compute Wi = normalize(Pi+1 - Pi) Compute Ui = normalize(Vi-1 ⨯ Wi) Compute Vi = normalize(Wi ⨯ Ui) ``` (2) Building the ribbon: ```To build a ribbon with width 2 * r: For i = 0 to n along path: The two vertices at this step along the path are at Pi - r * Ui and Pi + r * Ui. ``` Bump mapping Bump mapping is an inexpensive way, created by Jim Blinn in 1977, to create the illusion of a highly detailed model, without the expense of using many triangles. The trick is to use a texture to modify the surface normal at each pixel, before applying a shading algorithm such as Phong shading. The result will be that the object appears to have bumps and other surface irregularities, even though the actual geometry is smooth and featureless. There are several parts to bump mapping: Adding a tangent direction to each vertex in your vertex buffer; Creating a bump map image; Rendering the bump map texture. (1) Adding a tangent direction to each vertex: In order to support bump mapping, you need to add a tangent vector `[tx,ty,tz]` to each vertex in your shapes (cubes, spheres, cylinders, etc). So instead of storing `VERTEX_SIZE = 8` values per vertex in your vertex buffer, you will need to store `VERTEX_SIZE = 11` values: ``` vertex = [ x,y,z, nx,ny,nz, tx,ty,tz, u,v ] ``` You can compute the tangent at each vertex by taking successive differences between vertex positions: ``` tangenti,j = normalize(Pi+1,j - Pi,j) ``` where Pi,j is the position of the vertex at (ui,vj). (2) Creating a bump map image: You need to use the red,green and blue channels of your texture image to encode the variations in u,v and w of the normal, where u is the direction tangent to the surface along texture paramater u, w is the direction of your surface normal, and v is the binormal, which is perpendicular to both the normal and the tangent, is the direction tangent to the surface along texture paramater v. For each of r,g,b, a pixel value between 0 and 255 encodes a value between -1.0 and +1.0. (3) Rendering the bump map texture: To render the bump map, you need to do some work in both your vertex shader and in your fragment shader. In the vertex shader, after you have transformed the normal and tangent vectors, you need to compute the binormal: ``` vBinormal = normalize(cross(vNormal, vTangent)); ``` In the fragment shader, you need to retrieve the values r,g,b from the texture map, and then use those values to vary the surface normal, before doing Phong shading: ``` vec4 b = texture2D(uSampler[1], vUV) - 0.5; // We are assuming here that the bump map is texture[1]. vec3 normal = normalize(b.r * vTangent + b.g * vBinormal + b.b * vNormal); ``` Concept of locking for multi-user VR We will be giving you the software infrastructure next Monday to do the locking required for interaction between multiple users. Meanwhile, just so you can get used to the idea, here is what that interface will look like: ``` // CREATING A NEW LOCK let lock1 = client.createLock(); ... // DEFINE ACTION FOR THE PLAYER WHO SUCCESSFULLY GRABS THE LOCK. // playerID IS THE PLAYER WHO SUCCEEDED IN GRABBING THE LOCK. // RETURN true TO CONTINUE HOLDING THE LOCK. // OTHERWISE THE LOCK IS RELEASED AFTER THIS CALL. lock1.onLock = playerID => { ... } ... // REQUEST A LOCK. IF SUCCESSFUL, YOUR onLock() CALLBACK WILL BE CALLED. lock1.requestLock(); ``` Two link IK When building animated characters, you sometimes want to connect a shoulder to a wrist, by figuring out the position of an elbow, or connect a hip to an ankle, by figuring out the position of a knee. This process is called inverse kinematics, or IK for short. In particular, when there is only a single intermediate joint to be computed, the process is called "two link IK". High Level Concept: The elbow is going to be at some point on a sphere centered at the shoulder with radius `a`, where `a` is the length of the upper arm. It is also going to be at some point on a sphere centered at the wrist with radius `b`, where `b` is the length of the lower arm. The intersection of these two spheres is a circle. If you give a hint as to the direction the elbow is pointing, that will allow you to compute the point along this circle which is nearest to your hint direction, and you can place the elbow there. Implementation: Given a dot product function `dot(a,b)`, and assuming the shoulder is at the origin, the wrist is at point `C`, and the hint direction is placed into vector `D` before calling the function below, the correct elbow direction will be placed into `D` and also returned as the value of the function: ```function ik(a, b, C, D) { let cc = dot(C,C), x = (1 + (a*a - b*b)/cc) / 2, y = dot(C,D)/cc; for (let i = 0 ; i < 3 ; i++) D[i] -= y * C[i]; y = sqrt(max(0,a*a - cc*x*x) / dot(D,D)); for (let i = 0 ; i < 3 ; i++) D[i] = x * C[i] + y * D[i]; return D; } ``` Cool video we saw At the end of class we watched the 2005 short film Carlitopolis.