Skip to content

Instantly share code, notes, and snippets.

@robksawyer
Created September 25, 2020 10:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robksawyer/444149e8fe813d828da37eb76fbf5ac5 to your computer and use it in GitHub Desktop.
Save robksawyer/444149e8fe813d828da37eb76fbf5ac5 to your computer and use it in GitHub Desktop.
class Curve3D {
constructor() {
this.arcLengthDivisions = 200
}
getPointAt(u) {
let t = this.getUtoTmapping(u);
return this.getPoint(t)
}
getPoints(divisions = 5) {
let points = [];
for (let d = 0; d <= divisions; d++) points.push(this.getPoint(d / divisions));
return points
}
getSpacedPoints(divisions = 5) {
let points = [];
for (let d = 0; d <= divisions; d++) points.push(this.getPointAt(d / divisions));
return points
}
getLength() {
let lengths = this.getLengths();
return lengths[lengths.length - 1]
}
getLengths(divisions = this.arcLengthDivisions) {
if (this.cacheArcLengths && this.cacheArcLengths.length === divisions + 1 && !this.needsUpdate) return this.cacheArcLengths;
this.needsUpdate = !1;
let current, p, cache = [],
last = this.getPoint(0),
sum = 0;
for (cache.push(0), p = 1; p <= divisions; p++) current = this.getPoint(p / divisions), sum += current.distanceTo(last), cache.push(sum), last = current;
return this.cacheArcLengths = cache, cache
}
updateArtLengths() {
this.needsUpdate = !0, this.getLengths()
}
getUtoTmapping(u, distance) {
let targetArcLength, arcLengths = this.getLengths(),
i = 0,
il = arcLengths.length;
targetArcLength = distance || u * arcLengths[il - 1];
let comparison, low = 0,
high = il - 1;
for (; low <= high;)
if (i = Math.floor(low + (high - low) / 2), comparison = arcLengths[i] - targetArcLength, comparison < 0) low = i + 1;
else {
if (!(comparison > 0)) {
high = i;
break
}
high = i - 1
} if (i = high, arcLengths[i] === targetArcLength) return i / (il - 1);
let lengthBefore = arcLengths[i];
return (i + (targetArcLength - lengthBefore) / (arcLengths[i + 1] - lengthBefore)) / (il - 1)
}
getTangent(t) {
let t1 = t - 1e-4,
t2 = t + 1e-4;
t1 < 0 && (t1 = 0), t2 > 1 && (t2 = 1);
let pt1 = this.getPoint(t1);
return this.getPoint(t2).clone().sub(pt1).normalize()
}
getTangentAt(u) {
let t = this.getUtoTmapping(u);
return this.getTangent(t)
}
computeFrenetFrames(segments, closed) {
let i, u, theta, normal = new Vector3,
tangents = [],
normals = [],
binormals = [],
vec = new Vector3,
mat = new Matrix4;
for (i = 0; i <= segments; i++) u = i / segments, tangents[i] = this.getTangentAt(u), tangents[i].normalize();
normals[0] = new Vector3, binormals[0] = new Vector3;
let min = Number.MAX_VALUE,
tx = Math.abs(tangents[0].x),
ty = Math.abs(tangents[0].y),
tz = Math.abs(tangents[0].z);
for (tx <= min && (min = tx, normal.set(1, 0, 0)), ty <= min && (min = ty, normal.set(0, 1, 0)), tz <= min && normal.set(0, 0, 1), vec.crossVectors(tangents[0], normal).normalize(), normals[0].crossVectors(tangents[0], vec), binormals[0].crossVectors(tangents[0], normals[0]), i = 1; i <= segments; i++) normals[i] = normals[i - 1].clone(), binormals[i] = binormals[i - 1].clone(), vec.crossVectors(tangents[i - 1], tangents[i]), vec.length() > Number.EPSILON && (vec.normalize(), theta = Math.acos(Math.clamp(tangents[i - 1].dot(tangents[i]), -1, 1)), normals[i].applyMatrix4(mat.makeRotationAxis(vec, theta))), binormals[i].crossVectors(tangents[i], normals[i]);
if (!0 === closed)
for (theta = Math.acos(Math.clamp(normals[0].dot(normals[segments]), -1, 1)), theta /= segments, tangents[0].dot(vec.crossVectors(normals[0], normals[segments])) > 0 && (theta = -theta), i = 1; i <= segments; i++) normals[i].applyMatrix4(mat.makeRotationAxis(tangents[i], theta * i)), binormals[i].crossVectors(tangents[i], normals[i]);
return {
tangents: tangents,
normals: normals,
binormals: binormals
}
}
}
@robksawyer
Copy link
Author

class CubicBezierCurve extends Curve3D {
  constructor(v0, v1, v2, v3) {
    super(), this.type = "CubicBezierCurve", this.v0 = v0 || new Vector3, this.v1 = v1 || new Vector3, this.v2 = v2 || new Vector3, this.v3 = v3 || new Vector3
  }
  getLength() {
    const tmp = this.tmp;
    let length = 0;
    return this.points.forEach((p, i) => {
      0 !== i && (tmp.subVectors(p, this.points[i - 1]), length += tmp.length())
    }), length
  }
  getPoint(t, optionalTarget) {
    var point = optionalTarget || new Vector3,
      v0 = this.v0,
      v1 = this.v1,
      v2 = this.v2,
      v3 = this.v3;
    return point.set(this.cubicBezier(t, v0.x, v1.x, v2.x, v3.x), this.cubicBezier(t, v0.y, v1.y, v2.y, v3.y), this.cubicBezier(t, v0.z, v1.z, v2.z, v3.z)), point
  }
  copy(source) {
    return this.copy.call(this, source), this.v0.copy(source.v0), this.v1.copy(source.v1), this.v2.copy(source.v2), this.v3.copy(source.v3), this
  }
  cubicBezierP0(t, p) {
    var k = 1 - t;
    return k * k * k * p
  }
  cubicBezierP1(t, p) {
    var k = 1 - t;
    return 3 * k * k * t * p
  }
  cubicBezierP2(t, p) {
    return 3 * (1 - t) * t * t * p
  }
  cubicBezierP3(t, p) {
    return t * t * t * p
  }
  cubicBezier(t, p0, p1, p2, p3) {
    return this.cubicBezierP0(t, p0) + this.cubicBezierP1(t, p1) + this.cubicBezierP2(t, p2) + this.cubicBezierP3(t, p3)
  }
}
class CatmullRomCurve extends Curve3D {
  constructor(points = []) {
    if (super(), this.tmp = new Vector3, this.px = new CubicPoly, this.py = new CubicPoly, this.pz = new CubicPoly, points.length < 2) throw "CatmullRomCurve: Points array needs at least two entries.";
    this.points = points, this.closed = !1
  }
  getLength() {
    const tmp = this.tmp;
    let length = 0;
    return this.points.forEach((p, i) => {
      0 !== i && (tmp.subVectors(p, this.points[i - 1]), length += tmp.length())
    }), length
  }
  getPoint(t, target) {
    let p0, p1, p2, p3, tmp = this.tmp,
      px = this.px,
      py = this.py,
      pz = this.pz,
      points = this.points,
      l = points.length,
      point = (l - (this.closed ? 0 : 1)) * t,
      intPoint = Math.floor(point),
      weight = point - intPoint;
    if (this.closed ? intPoint += intPoint > 0 ? 0 : (Math.floor(Math.abs(intPoint) / points.length) + 1) * points.length : 0 === weight && intPoint === l - 1 && (intPoint = l - 2, weight = 1), this.closed || intPoint > 0 ? p0 = points[(intPoint - 1) % l] : (tmp.subVectors(points[0], points[1]).add(points[0]), p0 = tmp), p1 = points[intPoint % l], p2 = points[(intPoint + 1) % l], this.closed || intPoint + 2 < l ? p3 = points[(intPoint + 2) % l] : (tmp.subVectors(points[l - 1], points[l - 2]).add(points[l - 1]), p3 = tmp), void 0 === this.type || "centripetal" === this.type || "chordal" === this.type) {
      let pow = "chordal" === this.type ? .5 : .25,
        dt0 = Math.pow(p0.distanceToSquared(p1), pow),
        dt1 = Math.pow(p1.distanceToSquared(p2), pow),
        dt2 = Math.pow(p2.distanceToSquared(p3), pow);
      dt1 < 1e-4 && (dt1 = 1), dt0 < 1e-4 && (dt0 = dt1), dt2 < 1e-4 && (dt2 = dt1), px.initNonuniformCatmullRom(p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2), py.initNonuniformCatmullRom(p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2), pz.initNonuniformCatmullRom(p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2)
    } else if ("catmullrom" === this.type) {
      let tension = void 0 !== this.tension ? this.tension : .5;
      px.initCatmullRom(p0.x, p1.x, p2.x, p3.x, tension), py.initCatmullRom(p0.y, p1.y, p2.y, p3.y, tension), pz.initCatmullRom(p0.z, p1.z, p2.z, p3.z, tension)
    }
    if (!target) return new Vector3(px.calc(weight), py.calc(weight), pz.calc(weight));
    target.set(px.calc(weight), py.calc(weight), pz.calc(weight))
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment