mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-09-01 20:33:34 +02:00
full regeneration
This commit is contained in:
@@ -4,6 +4,7 @@ import { project, projectXY, projectXZ, projectYZ } from "./projection.js";
|
||||
let d, cube;
|
||||
|
||||
setup() {
|
||||
// step 1: let's define a cube to show our curve "in"
|
||||
d = this.width/2 + 25;
|
||||
cube = [
|
||||
{x:0, y:0, z:0},
|
||||
@@ -15,16 +16,25 @@ setup() {
|
||||
{x:d, y:d, z:d},
|
||||
{x:0, y:d, z:d}
|
||||
].map(p => project(p));
|
||||
|
||||
// step 2: let's also define our 3D curve
|
||||
const points = this.points = [
|
||||
{x:120, y: 0, z: 0},
|
||||
{x:120, y:220, z: 0},
|
||||
{x: 30, y: 0, z: 30},
|
||||
{x: 0, y: 0, z:200}
|
||||
];
|
||||
|
||||
// step 3: to draw this curve to the screen, we need to project the
|
||||
// coordinates from 3D to 2D, for which we use what is called
|
||||
// a "cabinet projection".
|
||||
this.curve = new Bezier(this, points.map(p => project(p)));
|
||||
|
||||
// We also construct handy projections on just the X/Y, X/Z, and Y/Z planes.
|
||||
this.cxy = new Bezier(this, points.map(p => projectXY(p)));
|
||||
this.cxz = new Bezier(this, points.map(p => projectXZ(p)));
|
||||
this.cyz = new Bezier(this, points.map(p => projectYZ(p)));
|
||||
|
||||
setSlider(`.slide-control`, `position`, 0);
|
||||
}
|
||||
|
||||
@@ -33,21 +43,32 @@ draw() {
|
||||
translate(this.width/2 - 60, this.height/2 + 75);
|
||||
const curve = this.curve;
|
||||
|
||||
// Draw all our planar curve projections first
|
||||
this.drawCurveProjections();
|
||||
|
||||
// And the "back" side of our cube
|
||||
this.drawCubeBack();
|
||||
|
||||
// Then, we draw the real curve
|
||||
curve.drawCurve(`grey`);
|
||||
setStroke(`grey`)
|
||||
line(curve.points[0].x, curve.points[0].y, curve.points[1].x, curve.points[1].y);
|
||||
line(curve.points[2].x, curve.points[2].y, curve.points[3].x, curve.points[3].y);
|
||||
curve.points.forEach(p => circle(p.x, p.y, 2));
|
||||
|
||||
// And the current point on that curve
|
||||
this.drawPoint(this.position);
|
||||
|
||||
// and then we can add the "front" of the cube.
|
||||
this.drawCubeFront();
|
||||
}
|
||||
|
||||
drawCurveProjections() {
|
||||
this.cxy.drawCurve(`#EEF`);
|
||||
this.cxz.drawCurve(`#EEF`);
|
||||
this.cyz.drawCurve(`#EEF`);
|
||||
}
|
||||
|
||||
drawCubeBack() {
|
||||
const c = cube;
|
||||
|
||||
@@ -64,12 +85,6 @@ drawCubeBack() {
|
||||
line(c[0].x, c[0].y, c[4].x, c[4].y);
|
||||
}
|
||||
|
||||
drawCurveProjections() {
|
||||
this.cxy.drawCurve(`#EEF`);
|
||||
this.cxz.drawCurve(`#EEF`);
|
||||
this.cyz.drawCurve(`#EEF`);
|
||||
}
|
||||
|
||||
drawPoint(t) {
|
||||
const {o, r, n, dt} = this.getFrenetVectors(t, this.points);
|
||||
|
||||
@@ -78,8 +93,13 @@ drawPoint(t) {
|
||||
const p = project(o);
|
||||
circle(p.x, p.y, 3);
|
||||
|
||||
// Draw our axis of rotation,
|
||||
this.drawVector(p, vec.normalize(r), 40, `blue`, `r`);
|
||||
|
||||
// our normal,
|
||||
this.drawVector(p, vec.normalize(n), 40, `red`, `n`);
|
||||
|
||||
// and our derivative.
|
||||
this.drawVector(p, vec.normalize(dt), 40, `green`, `t′`);
|
||||
|
||||
setFill(`black`)
|
||||
@@ -88,8 +108,6 @@ drawPoint(t) {
|
||||
|
||||
drawCubeFront() {
|
||||
const c = cube;
|
||||
|
||||
// rest of the cube
|
||||
setStroke("lightgrey");
|
||||
line(c[1].x, c[1].y, c[2].x, c[2].y);
|
||||
line(c[2].x, c[2].y, c[3].x, c[3].y);
|
||||
@@ -103,13 +121,20 @@ drawCubeFront() {
|
||||
}
|
||||
|
||||
getFrenetVectors(t, originalPoints) {
|
||||
// The frenet vectors are based on the (unprojected) curve,
|
||||
// and its derivative curve.
|
||||
const curve = new Bezier(this, originalPoints);
|
||||
const d1curve = new Bezier(this, curve.dpoints[0]);
|
||||
const o = curve.get(t);
|
||||
const dt = d1curve.get(t);
|
||||
const ddt = d1curve.derivative(t);
|
||||
const o = curve.get(t);
|
||||
const b = vec.plus(dt, ddt);
|
||||
const r = vec.cross(b, dt);
|
||||
// project the derivative into the future
|
||||
const f = vec.plus(dt, ddt);
|
||||
// and then find the axis of rotation wrt the plane
|
||||
// spanned by the currented and projected derivative
|
||||
const r = vec.cross(f, dt);
|
||||
// after which the normal is found by rotating the
|
||||
// tangent in that plane.
|
||||
const n = vec.normalize(vec.cross(r, dt));
|
||||
return { o, dt, r, n };
|
||||
}
|
||||
|
@@ -1,23 +1,25 @@
|
||||
/**
|
||||
* A cabinet projection utility
|
||||
* A cabinet projection utility library
|
||||
*/
|
||||
|
||||
// Universal projector function
|
||||
// Universal projector function:
|
||||
|
||||
function project(point3d, offset = { x: 0, y: 0 }, phi = -Math.PI / 6) {
|
||||
// what they rarely tell you: if you want Z to "go up",
|
||||
// X to "come out of the screen", and Y to be the "left/right",
|
||||
// we need to switch some coordinates around:
|
||||
const x = point3d.y,
|
||||
y = -point3d.z,
|
||||
z = -point3d.x;
|
||||
const a = point3d.y,
|
||||
b = -point3d.z,
|
||||
c = -point3d.x / 2;
|
||||
|
||||
return {
|
||||
x: offset.x + x + (z / 2) * Math.cos(phi),
|
||||
y: offset.y + y + (z / 2) * Math.sin(phi),
|
||||
x: offset.x + a + c * Math.cos(phi),
|
||||
y: offset.y + b + c * Math.sin(phi),
|
||||
};
|
||||
}
|
||||
|
||||
// and some rebuilt planar projectors
|
||||
// and some planar projectors:
|
||||
|
||||
function projectXY(p, offset, phi) {
|
||||
return project({ x: p.x, y: p.y, z: 0 }, offset, phi);
|
||||
}
|
||||
@@ -30,6 +32,4 @@ function projectYZ(p, offset, phi) {
|
||||
return project({ x: 0, y: p.y, z: p.z }, offset, phi);
|
||||
}
|
||||
|
||||
|
||||
export { project, projectXY, projectXZ, projectYZ }
|
||||
|
||||
export { project, projectXY, projectXZ, projectYZ };
|
||||
|
@@ -4,6 +4,7 @@ import { project, projectXY, projectXZ, projectYZ } from "./projection.js";
|
||||
let d, cube;
|
||||
|
||||
setup() {
|
||||
// We have the same setup as for the previous graphic
|
||||
d = this.width/2 + 25;
|
||||
cube = [
|
||||
{x:0, y:0, z:0},
|
||||
@@ -71,6 +72,7 @@ drawCurveProjections() {
|
||||
}
|
||||
|
||||
drawPoint(t) {
|
||||
// The only thing different compared to the previous graphic is this call:
|
||||
const {o, r, n, dt } = this.getRMF(t, this.points);
|
||||
|
||||
setStroke(`red`);
|
||||
@@ -102,75 +104,6 @@ drawCubeFront() {
|
||||
line(c[7].x, c[7].y, c[4].x, c[4].y);
|
||||
}
|
||||
|
||||
getRMF(t, originalPoints) {
|
||||
const curve = new Bezier(this, originalPoints);
|
||||
const d1curve = new Bezier(this, curve.dpoints[0]);
|
||||
|
||||
if (!this.rmf_LUT) {
|
||||
this.rmf_LUT = this.generateRMF(originalPoints, curve, d1curve);
|
||||
}
|
||||
|
||||
// find the frame for "t".
|
||||
const last = this.rmf_LUT.length - 1;
|
||||
const f = t * last;
|
||||
const i = Math.floor(f);
|
||||
|
||||
// intenger index, or last index: we're done.
|
||||
if (f === i) return this.rmf_LUT[i];
|
||||
|
||||
// no integer index: interpolate based on the adjacent frames.
|
||||
const j = i + 1, ti = i/last, tj = j/last, ratio = (t - ti) / (tj - ti);
|
||||
return this.lerpFrames(ratio, this.rmf_LUT[i], this.rmf_LUT[j]);
|
||||
}
|
||||
|
||||
generateRMF(originalPoints, curve, d1curve) {
|
||||
const frames = []
|
||||
frames.push(this.getFrenetVectors(0, originalPoints));
|
||||
|
||||
for(let i=0, steps=24; i<steps; i++) {
|
||||
const x0 = frames[i],
|
||||
// get the next frame
|
||||
t = (i+1)/steps,
|
||||
x1 = {
|
||||
o: curve.get(t),
|
||||
dt: d1curve.get(t)
|
||||
},
|
||||
// then mirror the rotational axis and tangent
|
||||
v1 = vec.minus(x1.o, x0.o),
|
||||
c1 = vec.dot(v1, v1),
|
||||
riL = vec.minus(x0.r, vec.scale(v1, 2/c1 * vec.dot(v1, x0.r))),
|
||||
dtiL = vec.minus(x0.dt, vec.scale(v1, 2/c1 * vec.dot(v1, x0.dt))),
|
||||
// and use those to compute a more stable rotational axis
|
||||
v2 = vec.minus(x1.dt, dtiL),
|
||||
c2 = vec.dot(v2, v2);
|
||||
x1.r = vec.minus(riL, vec.scale(v2, 2/c2 * vec.dot(v2, riL)));
|
||||
// and with that stable axis, a new normal.
|
||||
x1.n = vec.cross(x1.r, x1.dt);
|
||||
frames.push(x1);
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
getFrenetVectors(t, originalPoints) {
|
||||
const curve = new Bezier(this, originalPoints);
|
||||
const d1curve = new Bezier(this, curve.dpoints[0]);
|
||||
const dt = d1curve.get(t);
|
||||
const ddt = d1curve.derivative(t);
|
||||
const o = curve.get(t);
|
||||
const b = vec.normalize(vec.plus(dt, ddt));
|
||||
const r = vec.normalize(vec.cross(b, dt));
|
||||
const n = vec.normalize(vec.cross(r, dt));
|
||||
return { o, dt, r, n };
|
||||
}
|
||||
|
||||
|
||||
lerpFrames(t, f1, f2) {
|
||||
var frame = {};
|
||||
[`o`, `dt`, `r`, `n`].forEach(type => frame[type] = vec.lerp(t, f1[type], f2[type]));
|
||||
return frame;
|
||||
}
|
||||
|
||||
drawVector(from, vec, length, color, label) {
|
||||
setStroke(color);
|
||||
setFill(`black`);
|
||||
@@ -189,3 +122,78 @@ drawVector(from, vec, length, color, label) {
|
||||
});
|
||||
text(label, from.x + txt.x, from.y + txt.y);
|
||||
}
|
||||
|
||||
// This is where things are... rather different
|
||||
|
||||
getRMF(t, originalPoints) {
|
||||
// If we don't have a rotation-minimizing lookup table, build it.
|
||||
if (!this.rmf_LUT) {
|
||||
const curve = new Bezier(this, originalPoints);
|
||||
const d1curve = new Bezier(this, curve.dpoints[0]);
|
||||
this.rmf_LUT = this.generateRMF(originalPoints, curve, d1curve);
|
||||
}
|
||||
|
||||
// find the frame for "t":
|
||||
const last = this.rmf_LUT.length - 1;
|
||||
const f = t * last;
|
||||
const i = Math.floor(f);
|
||||
|
||||
// If we're looking at an integer index, we're done.
|
||||
if (f === i) return this.rmf_LUT[i];
|
||||
|
||||
// If we're not, we need to interpolate the adjacent frames
|
||||
const j = i + 1, ti = i/last, tj = j/last, ratio = (t - ti) / (tj - ti);
|
||||
return this.lerpFrames(ratio, this.rmf_LUT[i], this.rmf_LUT[j]);
|
||||
}
|
||||
|
||||
generateRMF(originalPoints, curve, d1curve) {
|
||||
// Start with the frenet frame just before t=0 and shift it to t=0
|
||||
const first = this.getFrenetVectors(-0.001, originalPoints);
|
||||
first.o = curve.get(0);
|
||||
|
||||
// Then we construct each next rotation-minimizing fame by reflecting
|
||||
// the previous frame and correcting the resulting vectors.
|
||||
const frames = [first];
|
||||
for(let i=0, steps=24; i<steps; i++) {
|
||||
const x0 = frames[i],
|
||||
// get the next frame
|
||||
t = (i+1)/steps,
|
||||
x1 = {
|
||||
o: curve.get(t),
|
||||
dt: d1curve.get(t)
|
||||
},
|
||||
// then mirror the rotational axis and tangent
|
||||
v1 = vec.minus(x1.o, x0.o),
|
||||
c1 = vec.dot(v1, v1),
|
||||
riL = vec.minus(x0.r, vec.scale(v1, 2/c1 * vec.dot(v1, x0.r))),
|
||||
dtiL = vec.minus(x0.dt, vec.scale(v1, 2/c1 * vec.dot(v1, x0.dt))),
|
||||
// then use those to compute a more stable rotational axis
|
||||
v2 = vec.minus(x1.dt, dtiL),
|
||||
c2 = vec.dot(v2, v2);
|
||||
// Fix the axis of rotation vector...
|
||||
x1.r = vec.minus(riL, vec.scale(v2, 2/c2 * vec.dot(v2, riL)));
|
||||
// ... and then compute the normal as usual
|
||||
x1.n = vec.cross(x1.r, x1.dt);
|
||||
frames.push(x1);
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
getFrenetVectors(t, originalPoints) {
|
||||
const curve = new Bezier(this, originalPoints),
|
||||
d1curve = new Bezier(this, curve.dpoints[0]),
|
||||
dt = d1curve.get(t),
|
||||
ddt = d1curve.derivative(t),
|
||||
o = curve.get(t),
|
||||
b = vec.normalize(vec.plus(dt, ddt)),
|
||||
r = vec.normalize(vec.cross(b, dt)),
|
||||
n = vec.normalize(vec.cross(r, dt));
|
||||
return { o, dt, r, n };
|
||||
}
|
||||
|
||||
lerpFrames(t, f1, f2) {
|
||||
var frame = {};
|
||||
[`o`, `dt`, `r`, `n`].forEach(type => frame[type] = vec.lerp(t, f1[type], f2[type]));
|
||||
return frame;
|
||||
}
|
||||
|
@@ -1,71 +1,70 @@
|
||||
function normalize(v) {
|
||||
let z = v.z || 0;
|
||||
var d = Math.sqrt(v.x*v.x + v.y*v.y + z*z);
|
||||
let r = { x:v.x/d, y:v.y/d };
|
||||
if (v.z !== undefined) r.z = z/d;
|
||||
return r;
|
||||
let z = v.z || 0;
|
||||
var d = Math.sqrt(v.x * v.x + v.y * v.y + z * z);
|
||||
let r = { x: v.x / d, y: v.y / d };
|
||||
if (v.z !== undefined) r.z = z / d;
|
||||
return r;
|
||||
}
|
||||
|
||||
function dot(v1, v2) {
|
||||
let z1 = v1.z || 0;
|
||||
let z2 = v2.z || 0;
|
||||
return v1.x * v2.x + v1.y * v2.y + z1 * z2;
|
||||
let z1 = v1.z || 0;
|
||||
let z2 = v2.z || 0;
|
||||
return v1.x * v2.x + v1.y * v2.y + z1 * z2;
|
||||
}
|
||||
|
||||
function scale(v, s) {
|
||||
let r = {
|
||||
x: s * v.x,
|
||||
y: s * v.y
|
||||
}
|
||||
if (v.z !== undefined) {
|
||||
r.z = s * v.z
|
||||
}
|
||||
return r;
|
||||
let r = {
|
||||
x: s * v.x,
|
||||
y: s * v.y,
|
||||
};
|
||||
if (v.z !== undefined) {
|
||||
r.z = s * v.z;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function plus(v1, v2) {
|
||||
let r = {
|
||||
x: v1.x + v2.x,
|
||||
y: v1.y + v2.y
|
||||
};
|
||||
if (v1.z !== undefined || v2.z !== undefined) {
|
||||
r.z = (v1.z||0) + (v2.z||0);
|
||||
};
|
||||
return r;
|
||||
let r = {
|
||||
x: v1.x + v2.x,
|
||||
y: v1.y + v2.y,
|
||||
};
|
||||
if (v1.z !== undefined || v2.z !== undefined) {
|
||||
r.z = (v1.z || 0) + (v2.z || 0);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function minus(v1, v2) {
|
||||
let r = {
|
||||
x: v1.x - v2.x,
|
||||
y: v1.y - v2.y
|
||||
};
|
||||
if (v1.z !== undefined || v2.z !== undefined) {
|
||||
r.z = (v1.z||0) - (v2.z||0);
|
||||
};
|
||||
return r;
|
||||
let r = {
|
||||
x: v1.x - v2.x,
|
||||
y: v1.y - v2.y,
|
||||
};
|
||||
if (v1.z !== undefined || v2.z !== undefined) {
|
||||
r.z = (v1.z || 0) - (v2.z || 0);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function cross(v1, v2) {
|
||||
if (v1.z === undefined || v2.z === undefined) {
|
||||
throw new Error(`Cross product is not defined for 2D vectors.`);
|
||||
}
|
||||
return {
|
||||
x: v1.y * v2.z - v1.z * v2.y,
|
||||
y: v1.z * v2.x - v1.x * v2.z,
|
||||
z: v1.x * v2.y - v1.y * v2.x
|
||||
};
|
||||
if (v1.z === undefined || v2.z === undefined) {
|
||||
throw new Error(`Cross product is not defined for 2D vectors.`);
|
||||
}
|
||||
return {
|
||||
x: v1.y * v2.z - v1.z * v2.y,
|
||||
y: v1.z * v2.x - v1.x * v2.z,
|
||||
z: v1.x * v2.y - v1.y * v2.x,
|
||||
};
|
||||
}
|
||||
|
||||
function lerp(t, v1, v2) {
|
||||
let r = {
|
||||
x: (1-t)*v1.x + t*v2.x,
|
||||
y: (1-t)*v1.y + t*v2.y
|
||||
};
|
||||
if (v1.z !== undefined || v2.z !== undefined) {
|
||||
r.z = (1-t)*(v1.z||0) + t*(v2.z||0);
|
||||
};
|
||||
return r;
|
||||
let r = {
|
||||
x: (1 - t) * v1.x + t * v2.x,
|
||||
y: (1 - t) * v1.y + t * v2.y,
|
||||
};
|
||||
if (v1.z !== undefined || v2.z !== undefined) {
|
||||
r.z = (1 - t) * (v1.z || 0) + t * (v2.z || 0);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
export default { normalize, dot, scale, plus, minus, cross, lerp }
|
||||
export default { normalize, dot, scale, plus, minus, cross, lerp };
|
||||
|
Reference in New Issue
Block a user