Public
Edited
Jul 26, 2024
1 fork
Importers
Insert cell
Insert cell
/**
@param {Vector} anchor the anchor point
@param {Vector} target the target point to be constrained
@param {number} distance
@param {boolean}[minOnly=false] if true, only apply the constraint if the distance between the points are bigger than the desire distance
**/
function distanceConstrain(anchor, target, distance, minOnly=false){
const vec = vecFromSub(target, anchor); // get the vector pointing from anchor to target
if (minOnly){
const currentDistSq = vecMagSq(vec);
if (currentDistSq < distance * distance) return;
}
vecSetMag(vec, distance); // scale the vector to the desired distance
target.x = anchor.x + vec.x;
target.y = anchor.y + vec.y;
target.z = anchor.z + vec.z; // move the target point to satisfy the constraint
}
Insert cell
Insert cell
Insert cell
class Chain {
/**
@param {Vector[]} positions
@param {number[]} distances
**/
constructor(positions, distances){
if (positions.length != distances.length+1) throw new Error("bad arguments");
this.joints = positions;
this.distances = distances;
}

resolve(fromIndex = 0){
let current = fromIndex;
let next = fromIndex - 1;
while(next > - 1) {
const anchor = this.joints[current];
const target = this.joints[next];
const distance = this.distances[next];
distanceConstrain(anchor, target, distance);
current = next;
next --;
}
current = fromIndex;
next = fromIndex + 1;
while(next < this.joints.length){
const anchor = this.joints[current];
const target = this.joints[next];
const distance = this.distances[current];
distanceConstrain(anchor, target, distance);
current = next;
next ++;
}
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class Snake extends Chain{
constructor(positions, distances, bodySizes){
super(positions, distances);
if (bodySizes.length != this.joints.length) throw new Error("bad arguments");
this.bodies = bodySizes;
this.angles = new Array(this.joints.length).fill(Math.PI);
}

resolve(facingAng){
// resolve the constraint
super.resolve(0);
// update angles
this.angles[0] = facingAng; // where the head facing
for (let i = 1; i < this.joints.length; ++i) {
let ang = vecHeading(vecFromSub(this.joints[i-1], this.joints[i]));
this.angles[i] = ang;
}
}

getShape(){
const getPoint = (idx, angOffset, sizeOffset) => {
const x = this.joints[idx].x + Math.cos(this.angles[idx] + angOffset) * (this.bodies[idx] / 2 + sizeOffset);
const y = this.joints[idx].y + Math.sin(this.angles[idx] + angOffset) * (this.bodies[idx] / 2 + sizeOffset);
return {x, y};
}
let points = [getPoint(0,Math.PI/6, 0), getPoint(0,0,0), getPoint(0, -Math.PI/6, 0)]; // head
for (let i = 0; i < this.joints.length; ++i) {
points.push(getPoint(i, -Math.PI/2, 0));
points.unshift(getPoint(i,Math.PI/2, 0));
}
// tail
points.push(getPoint(this.joints.length-1, -5*Math.PI/6, 0));
points.unshift(getPoint(this.joints.length-1,5*Math.PI/6, 0));
points.unshift(getPoint(this.joints.length-1, Math.PI,0));
return points;
}
}
Insert cell
Insert cell
Insert cell
class Snake2 extends Snake{
constructor(positions, distances, bodySizes, angleConstraint){
super(positions, distances, bodySizes);
this.angleConstraint = angleConstraint;
}

resolve(facingAng){
const modAngle = (a) => {
let angle = a;
while(angle >= Math.PI * 2){
angle -= Math.PI * 2;
}
while(angle < 0){
angle += Math.PI * 2;
}
return angle;
}

const angleDiff = (a1, a2) => {
const a = modAngle(a1-a2+Math.PI);
return Math.PI - a;
}

const constrainAngle = (angle, reference, allowedDiff) => {
const diff = angleDiff(angle,reference)
if (Math.abs(diff) <= allowedDiff) return modAngle(angle);
if (diff > allowedDiff) return modAngle(reference - allowedDiff);
return modAngle(reference + allowedDiff);
}

this.angles[0] = facingAng;
for (let i = 1; i < this.joints.length; i++){
const currAng = vecHeading(vecFromSub(this.joints[i-1], this.joints[i]));
this.angles[i] = constrainAngle(currAng, this.angles[i-1], this.angleConstraint);
const v =vecSetMag(vecFromAngle(this.angles[i]), this.distances[i-1]);
this.joints[i].x = this.joints[i-1].x - v.x;
this.joints[i].y = this.joints[i-1].y - v.y;
this.joints[i].z = this.joints[i-1].z - v.z;
}
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more