mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-09 18:02:50 +01:00
467 lines
No EOL
11 KiB
JavaScript
467 lines
No EOL
11 KiB
JavaScript
import {BasisForPlane, Matrix3} from '../../../math/l3space'
|
|
import * as math from '../../../math/math'
|
|
import {Point} from '../point'
|
|
import {Surface} from "../surface";
|
|
import Vector from 'math/vector';
|
|
import * as ext from "./nurbs-ext";
|
|
import {EPSILON, eqEps, eqSqTol, TOLERANCE, TOLERANCE_SQ, ueq, veq, veq3, veqNeg} from "../tolerance";
|
|
import curveIntersect from "./curve/curves-isec";
|
|
import curveTess from "./curve/curve-tess";
|
|
import {areEqual} from "../../../math/math";
|
|
import {Plane} from "./plane";
|
|
|
|
|
|
class ParametricCurve {
|
|
|
|
domain() { }
|
|
|
|
degree() { }
|
|
|
|
degree1Tess() {}
|
|
|
|
eval(u, num) { }
|
|
|
|
point(param) { }
|
|
|
|
param(point) { }
|
|
|
|
transform(tr) { }
|
|
|
|
optimalSplits() { }
|
|
|
|
normalizeParametrization() { }
|
|
|
|
invert() { }
|
|
}
|
|
|
|
export class NurbsCurveImpl { //TODO: rename to NurbsCurve implements ParametricCurve
|
|
|
|
constructor(verbCurve) {
|
|
this.verb = verbCurve;
|
|
this.data = verbCurve.asNurbs();
|
|
}
|
|
|
|
domain() {
|
|
return ext.curveDomain(this.data);
|
|
}
|
|
|
|
degree1Tess() {
|
|
return ext.distinctKnots(this.data);
|
|
}
|
|
|
|
degree() {
|
|
return this.data.degree;
|
|
}
|
|
|
|
transform(tr) {
|
|
return new NurbsCurveImpl(this.verb.transform(tr));
|
|
}
|
|
|
|
point(u) {
|
|
return this.verb.point(u);
|
|
}
|
|
|
|
param(point) {
|
|
return this.verb.closestParam(point);
|
|
}
|
|
|
|
eval(u, num) {
|
|
return verb.eval.Eval.rationalCurveDerivatives( this.data, u, num );
|
|
}
|
|
|
|
optimalSplits() {
|
|
return this.data.knots.length - 1;
|
|
}
|
|
|
|
invert() {
|
|
|
|
let inverted = ext.curveInvert(this.data);
|
|
ext.normalizeCurveParametrizationIfNeeded(inverted);
|
|
// let [min, max] = curveDomain(curve);
|
|
// for (let i = 0; i < reversed.knots.length; i++) {
|
|
// if (eqEps(reversed.knots[i], max)) {
|
|
// reversed.knots[i] = max;
|
|
// } else {
|
|
// break;
|
|
// }
|
|
// }
|
|
// for (let i = reversed.knots.length - 1; i >= 0 ; i--) {
|
|
// if (eqEps(reversed.knots[i], min)) {
|
|
// reversed.knots[i] = min;
|
|
// } else {
|
|
// break;
|
|
// }
|
|
// }
|
|
|
|
return new NurbsCurveImpl(newVerbCurve(inverted));
|
|
}
|
|
|
|
split(u) {
|
|
let split = verb.eval.Divide.curveSplit(this.data, u);
|
|
split.forEach(n => ext.normalizeCurveParametrization(n));
|
|
return split.map(c => new NurbsCurveImpl(newVerbCurve(c)));
|
|
}
|
|
}
|
|
|
|
export class NurbsCurve { //TODO: rename to BrepCurve
|
|
|
|
constructor(_impl, uMin, uMax) {
|
|
let [iMin, iMax] = _impl.domain();
|
|
if (iMin !== 0 || iMax !== 1) {
|
|
throw 'only normalized(0..1) parametrization is supported';
|
|
}
|
|
this.impl = _impl;
|
|
// if (uMin === undefined || uMax === undefined) {
|
|
// [uMin, uMax] = this.impl.domain();
|
|
// }
|
|
// this.uMin = uMin;
|
|
// this.uMax = uMax;
|
|
this.uMin = 0;
|
|
this.uMax = 1;
|
|
}
|
|
|
|
translate(vector) {
|
|
const tr = new Matrix3().translate(vector.x, vector.y, vector.z);
|
|
return new NurbsCurve(this.impl.transform(tr.toArray()), this.uMin, this.uMax);
|
|
}
|
|
|
|
tangentAtPoint(point) {
|
|
let u = this.impl.param(point.data());
|
|
if (areEqual(u, this.uMax, 1e-3)) { // we don't need much tolerance here
|
|
//TODO:
|
|
// let cps = this.impl.data.controlPoints;
|
|
// return pt(cps[cps.length - 1])._minus(pt(cps[cps.length - 2]))._normalize();
|
|
u -= 1e-3;
|
|
}
|
|
return this.tangentAtParam(u);
|
|
}
|
|
|
|
tangentAtParam(u) {
|
|
const dr = this.impl.eval(u, 1);
|
|
return pt(dr[1])._normalize();
|
|
}
|
|
|
|
param(point) {
|
|
return this.impl.param(point.data());
|
|
}
|
|
|
|
split(point) {
|
|
return this.splitByParam(this.param(point));
|
|
}
|
|
|
|
splitByParam(u) {
|
|
if (ueq(this.uMin) || ueq(this.uMax) || u < this.uMin || u > this.uMax) {
|
|
return null
|
|
}
|
|
let split = this.impl.split(u);
|
|
|
|
const splitCheck = (split) => {
|
|
return (
|
|
math.equal(this.impl.param(split[0].point(1)), this.impl.param(split[1].point(0))) &&
|
|
math.equal(this.impl.param(split[0].point(0)), 0) &&
|
|
math.equal(this.impl.param(split[0].point(1)), u) &&
|
|
math.equal(this.impl.param(split[1].point(0)), u) &&
|
|
math.equal(this.impl.param(split[1].point(1)), 1)
|
|
)
|
|
};
|
|
if (!splitCheck(split)) {
|
|
throw 'wrong split';
|
|
}
|
|
return split.map(v => new NurbsCurve(v));
|
|
|
|
// return [
|
|
// new NurbsCurve(this.impl, this.uMin, u),
|
|
// new NurbsCurve(this.impl, u, this.uMax)
|
|
// ];
|
|
}
|
|
|
|
point(u) {
|
|
return pt(this.impl.point(u));
|
|
}
|
|
|
|
tessellate(tessTol, scale) {
|
|
return CURVE_CACHING_TESSELLATOR(this.impl, this.uMin, this.uMax, tessTol, scale).map(p => pt(p));
|
|
}
|
|
|
|
boundary() {
|
|
return [this.uMin, this.uMax];
|
|
}
|
|
|
|
intersectCurve(other) {
|
|
let isecs = [];
|
|
|
|
const eq = veq3;
|
|
|
|
function add(i0) {
|
|
for (let i1 of isecs) {
|
|
if (eq(i0.p0, i1.p0)) {
|
|
return;
|
|
}
|
|
}
|
|
isecs.push(i0);
|
|
}
|
|
|
|
function isecOn(c0, c1, u0) {
|
|
const p0 = c0.impl.point(u0);
|
|
const u1 = c1.impl.param(p0);
|
|
if (!c1.isInside(u1)) {
|
|
return;
|
|
}
|
|
const p1 = c1.impl.point(u1);
|
|
if (eq(p0, p1)) {
|
|
if (c0 === other) {
|
|
add({u0: u1, u1: u0, p0: p1, p1: p0});
|
|
} else {
|
|
add({u0, u1, p0, p1});
|
|
}
|
|
}
|
|
}
|
|
|
|
isecOn(this, other, this.uMin);
|
|
isecOn(this, other, this.uMax);
|
|
isecOn(other, this, other.uMin);
|
|
isecOn(other, this, other.uMax);
|
|
|
|
curveIntersect(
|
|
this.impl, other.impl,
|
|
this.boundary(), other.boundary(),
|
|
CURVE_CACHING_TESSELLATOR, CURVE_CACHING_TESSELLATOR
|
|
).forEach(i => add(i));
|
|
|
|
isecs.forEach(i => {
|
|
i.p0 = pt(i.p0);
|
|
i.p1 = pt(i.p1);
|
|
});
|
|
isecs = isecs.filter(({u0, u1}) => {
|
|
let t0 = this.tangentAtParam(u0);
|
|
let t1 = other.tangentAtParam(u1);
|
|
return !veq(t0, t1) && !veqNeg(t0, t1);
|
|
});
|
|
return isecs;
|
|
}
|
|
|
|
isInside(u) {
|
|
return u >= this.uMin && u <= this.uMax;
|
|
}
|
|
|
|
invert() {
|
|
return new NurbsCurve(this.impl.invert());
|
|
}
|
|
|
|
middlePoint() {
|
|
if (!this.__middlePoint) {
|
|
this.__middlePoint = this.point(0.5);
|
|
}
|
|
return this.__middlePoint;
|
|
}
|
|
|
|
passesThrough(point) {
|
|
return eqSqTol(0, point.distanceToSquared(this.point(this.param(point))));
|
|
}
|
|
}
|
|
|
|
const CURVE_CACHING_TESSELLATOR = function(curve, min, max, tessTol, scale) {
|
|
return cache('tess', [min, max, tessTol, scale], curve, () => degree1OptTessellator(curve, min, max, tessTol, scale));
|
|
};
|
|
|
|
function degree1OptTessellator(curve, min, max, tessTol, scale) {
|
|
if (curve.degree() === 1) {
|
|
return curve.degree1Tess().map(u => curve.point(u));
|
|
}
|
|
return curveTess(curve, min, max, tessTol, scale);
|
|
}
|
|
|
|
NurbsCurve.createLinearNurbs = function(a, b) {
|
|
let line = verb.geom.NurbsCurve.byKnotsControlPointsWeights( 1, [0,0,1,1], [a.data(), b.data()]);
|
|
return new NurbsCurve(new NurbsCurveImpl(line));
|
|
};
|
|
|
|
export class NurbsSurface extends Surface {
|
|
|
|
constructor(verbSurface, inverted, simpleSurface) {
|
|
super();
|
|
let {min: uMin, max: uMax} = verbSurface.domainU();
|
|
let {min: vMin, max: vMax} = verbSurface.domainV();
|
|
|
|
if (uMin !== 0 || uMax !== 1 || vMin !== 0 || vMax !== 1) {
|
|
throw 'only normalized(0..1) parametrization is supported';
|
|
}
|
|
|
|
this.data = verbSurface.asNurbs();
|
|
this.verb = verbSurface;
|
|
this.inverted = inverted === true;
|
|
this.mirrored = NurbsSurface.isMirrored(this);
|
|
this.simpleSurface = simpleSurface || figureOutSimpleSurface(this);
|
|
}
|
|
|
|
domainU() {
|
|
return this.verb.domainU();
|
|
}
|
|
|
|
domainV() {
|
|
return this.verb.domainV();
|
|
}
|
|
|
|
middle() {
|
|
let {min: uMin, max: uMax} = this.verb.domainU();
|
|
let {min: vMin, max: vMax} = this.verb.domainV();
|
|
return [
|
|
(uMax - uMin) * 0.5,
|
|
(vMax - vMin) * 0.5
|
|
];
|
|
}
|
|
|
|
toNurbs() {
|
|
return this;
|
|
}
|
|
|
|
normal(point) {
|
|
let uv = this.verb.closestParam(point.data());
|
|
let normal = pt(this.verb.normal(uv[0], uv[1]));
|
|
if (this.inverted) {
|
|
normal._negate();
|
|
}
|
|
normal._normalize();
|
|
return normal;
|
|
}
|
|
|
|
normalUV(u, v) {
|
|
let normal = pt(this.verb.normal(u, v));
|
|
if (this.inverted) {
|
|
normal._negate();
|
|
}
|
|
normal._normalize();
|
|
return normal;
|
|
}
|
|
|
|
normalInMiddle() {
|
|
//TODO: use domain!
|
|
return this.normalUV(0.5, 0.5);
|
|
}
|
|
|
|
pointInMiddle() {
|
|
//TODO: use domain!
|
|
return this.point(0.5, 0.5);
|
|
}
|
|
|
|
|
|
param(point) {
|
|
return this.verb.closestParam(point.data());
|
|
}
|
|
|
|
point(u, v) {
|
|
return pt(this.verb.point(u, v));
|
|
}
|
|
|
|
workingPoint(point) {
|
|
return this.createWorkingPoint(this.verb.closestParam(point.data()), point);
|
|
}
|
|
|
|
createWorkingPoint(uv, pt3d) {
|
|
const wp = new Vector(uv[0], uv[1], 0)._multiply(NurbsSurface.WORKING_POINT_SCALE_FACTOR);
|
|
if (this.mirrored) {
|
|
wp.x *= -1;
|
|
}
|
|
wp.__3D = pt3d;
|
|
return wp;
|
|
}
|
|
|
|
workingPointTo3D(wp) {
|
|
if (wp.__3D === undefined) {
|
|
const uv = wp.multiply(NurbsSurface.WORKING_POINT_UNSCALE_FACTOR);
|
|
if (this.mirrored) {
|
|
uv.x *= -1;
|
|
}
|
|
wp.__3D = this.point(uv.x, uv.y);
|
|
}
|
|
return wp.__3D;
|
|
}
|
|
|
|
static isMirrored(surface) {
|
|
let {min: uMin} = surface.domainU();
|
|
let {min: vMin} = surface.domainV();
|
|
|
|
let x = surface.isoCurveAlignU(uMin).tangentAtParam(uMin);
|
|
let y = surface.isoCurveAlignV(vMin).tangentAtParam(vMin);
|
|
|
|
return x.cross(y).dot(surface.normalUV(uMin, vMin)) < 0;
|
|
}
|
|
|
|
intersectSurfaceForSameClass(other) {
|
|
let curves = ext.surfaceIntersect(this.data, other.data);
|
|
let inverted = this.inverted !== other.inverted;
|
|
if (inverted) {
|
|
curves = curves.map(curve => ext.curveInvert(curve));
|
|
}
|
|
curves.forEach(curve => ext.normalizeCurveParametrizationIfNeeded(curve))
|
|
return curves.map(curve => new NurbsCurve(new NurbsCurveImpl(newVerbCurve(curve))));
|
|
}
|
|
|
|
invert() {
|
|
return new NurbsSurface(this.verb, !this.inverted);
|
|
}
|
|
|
|
isoCurve(param, useV) {
|
|
const data = verb.eval.Make.surfaceIsocurve(this.verb._data, param, useV);
|
|
const isoCurve = newVerbCurve(data);
|
|
return new NurbsCurve(new NurbsCurveImpl(isoCurve));
|
|
}
|
|
|
|
isoCurveAlignU(param) {
|
|
return this.isoCurve(param, true);
|
|
}
|
|
|
|
isoCurveAlignV(param) {
|
|
return this.isoCurve(param, false);
|
|
}
|
|
|
|
intersectWithCurve(curve) {
|
|
return verb.geom.Intersect.curveAndSurface(curve.impl.verb, this.verb, TOLERANCE).map(({uv}) => uv);
|
|
}
|
|
|
|
tangentPlane(u, v) {
|
|
let normal = this.normalUV(u, v);
|
|
return new Plane(normal, normal.dot(this.point(u, v)));
|
|
}
|
|
|
|
tangentPlaneInMiddle() {
|
|
return this.tangentPlane(0.5, 0.5);
|
|
}
|
|
}
|
|
|
|
NurbsSurface.WORKING_POINT_SCALE_FACTOR = 1000;
|
|
NurbsSurface.WORKING_POINT_UNSCALE_FACTOR = 1 / NurbsSurface.WORKING_POINT_SCALE_FACTOR;
|
|
|
|
NurbsSurface.loft = function(curve1, curve2) {
|
|
return new NurbsSurface(verb.geom.NurbsSurface.byLoftingCurves([curve1.impl.verb, curve2.impl.verb], 1));
|
|
};
|
|
|
|
function newVerbCurve(data) {
|
|
return new verb.geom.NurbsCurve(data);
|
|
}
|
|
|
|
function pt(data) {
|
|
return new Point().set3(data);
|
|
}
|
|
|
|
function cache(id, keys, obj, op) {
|
|
id = '__cache__:' + id + ':' + keys.join('/');
|
|
if (!obj[id]) {
|
|
obj[id] = op();
|
|
}
|
|
return obj[id];
|
|
}
|
|
|
|
const surTess = verb.eval.Tess.rationalSurfaceAdaptive;
|
|
verb.eval.Tess.rationalSurfaceAdaptive = function(surface, opts) {
|
|
const keys = [opts ? opts.maxDepth: 'undefined'];
|
|
return cache('tess', keys, surface, () => surTess(surface, opts));
|
|
};
|
|
|
|
function figureOutSimpleSurface(nurbs) {
|
|
if (ext.surfaceMaxDegree(nurbs.data) === 1) {
|
|
//TODO: use domain!
|
|
return nurbs.tangentPlane(0.5, 0.5);
|
|
}
|
|
return null;
|
|
} |