|
Notes for Tuesday April 7 class -- Making shapes with splines, part 1
Review of previous notes
First we reviewed the notes from April 2.
Organizing vector functions into a static object
Then, starting with the code from March 31, spent the rest of the class we made some modifications.
The first modification was to create a static object
V3
to hold operations on length 3 vectors, such as
cross()
and
normalize() :
let V3 = {
cross : (a, b) => [ a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0] ],
normalize : v => { let s = Math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
return [ v[0] / s, v[1] / s, v[2] / s ]; },
}
Implementing a spline basis matrix
Then we added a
transform()
method to the
Matrix
class, to transform vectors of length 3 or 4.
We will need this operation in order to create and use
an Hermite or Bezier basis matrix:
this.transform = function(v) {
let m = value, x = v[0], y = v[1], z = v[2], w = v[3] === undefined ? 1 : v[3];
let X = m[0] * x + m[4] * y + m[ 8] * z + m[12] * w,
Y = m[1] * x + m[5] * y + m[ 9] * z + m[13] * w,
Z = m[2] * x + m[6] * y + m[10] * z + m[14] * w,
W = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
return v[3] === undefined ? [X, Y, Z] : [X, Y, Z, W];
}
Note that we made sure to handle the special case where the argument is a vector of length 3,
treating it like a position vector [x,y,z,1].
Then we defined
hermiteBasis
and
bezierBasis
as instances of
Matrix :
let hermiteBasis = new Matrix();
hermiteBasis.setValue([2,-3,0,1, -2,3,0,0, 1,-2,1,0, 1,-1,0,0]);
let bezierBasis = new Matrix();
bezierBasis.setValue([-1,3,-3,1, 3,-6,3,0, -3,3,0,0, 1,0,0,0]);
Evaluating a spline
After that, we added a function to the library that lets us
evaluate a multi-segment Bezier spline at a particular parametric value 0.0 ≤ t ≤ 1.0,
given an array of keys:
let evalBezier = (keys, t) => {
t = Math.max(0, Math.min(t, .9999));
let n = Math.floor(keys.length / 3); // FIND NUMBER OF KEYS
let i = Math.floor(n * t) * 3; // FIND INDEX OF FIRST KEY IN SEGMENT
let f = n * t % 1; // FIND FRACTION WITHIN SEGMENT
let C = bezierBasis.transform([ keys[i],keys[i+1],keys[i+2],keys[i+3] ]);
// return f*f*f*C[0] + f*f*C[1] + f*C[2] + C[3]; // LESS EFFICIENT
return f * (f * (f * C[0] + C[1]) + C[2]) + C[3]; // MORE EFFICIENT
}
Surface of revolution
This gave us what we needed for a first implementation of the Lathe object,
for creating surfaces of revolution.
In the library, we defined:
let uvToLathe = (u,v,keys) => {
let theta =2 * Math.PI * u;
let r = evalBezier(keys, v);
return [ r * Math.cos(theta), r * Math.sin(theta), 2 * v - 1 ];
}
The problem with that implementation is that it doesn't let us create
surfaces of revolution with rounded tips at the end.
In order to do that, we need to pass in not one but two
keys
arrays,
one to control radius
r , and the other to control coordinate
z .
After making that adjustment, here was our full implementation:
let uvToLathe = (u,v,keys) => {
let theta =2 * Math.PI * u, r, z;
if (! Array.isArray(keys[0])) {
r = evalBezier(keys, v);
z = 2 * v - 1;
}
else {
r = evalBezier(keys[0], v);
z = evalBezier(keys[1], v);
}
return [ r * Math.cos(theta), r * Math.sin(theta), z ];
}
Then in
index.html
we created an example of a surface of revolution:
let myLathe = createTriangleMesh(uvToLathe, 20, 20,
[
[ 0,2/3, 1/6,1/4,1/3, 1/3,0], // r
[-1,-1, 1,1] // z
]
);
Sampling a spline path
Finally, we showed how you can sample a spline path,
then we visualized that path by stringing small
spheres along the path.
First, in the library we defined:
let sampleBezierPath = (X,Y,Z,n) => {
let P = [];
for (let i = 0 ; i <= n ; i++)
P.push([evalBezier(X, i/n),
evalBezier(Y, i/n),
evalBezier(Z, i/n)]);
return P;
}
Then in
index.html
we created an example of a sampled spline path:
let path = sampleBezierPath( [-1,-1/3, 1/3, 1], // X keys
[ 1,-1, 1 ,-1], // Y keys
[ 1,-1, -1 , 1], // Z keys
30); // number of samples
for (let n = 0 ; n < path.length ; n++) {
test.add(sphere, blue_plastic)
.translate(path[n][0], path[n][1], path[n][2])
.scale(.1);
}
| |