mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-07 00:45:08 +01:00
327 lines
8.2 KiB
JavaScript
327 lines
8.2 KiB
JavaScript
import verb from 'verb-nurbs'
|
|
import BrepCurve from '../../brep/geom/curves/brepCurve';
|
|
import NurbsCurve from '../../brep/geom/curves/nurbsCurve';
|
|
import {Point} from '../../brep/geom/point'
|
|
import {LUT} from '../../math/bezier-cubic'
|
|
import {distanceAB, isCCW, makeAngle0_360} from '../../math/math'
|
|
import {normalizeCurveEnds} from '../../brep/geom/impl/nurbs-ext';
|
|
|
|
const RESOLUTION = 20;
|
|
|
|
class SketchPrimitive {
|
|
constructor(id) {
|
|
this.id = id;
|
|
this.inverted = false;
|
|
}
|
|
|
|
invert() {
|
|
this.inverted = !this.inverted;
|
|
}
|
|
|
|
approximate(resolution) {
|
|
const approximation = this.approximateImpl(resolution);
|
|
if (this.inverted) {
|
|
approximation.reverse();
|
|
}
|
|
return approximation;
|
|
}
|
|
|
|
isCurve() {
|
|
return this.constructor.name != 'Segment';
|
|
}
|
|
|
|
toNurbs(plane) {
|
|
let verbNurbs = this.toVerbNurbs(plane, to3DTrFunc(plane));
|
|
if (this.inverted) {
|
|
verbNurbs = verbNurbs.reverse();
|
|
}
|
|
let data = verbNurbs.asNurbs();
|
|
normalizeCurveEnds(data);
|
|
verbNurbs = new verb.geom.NurbsCurve(data);
|
|
|
|
return new BrepCurve(new NurbsCurve(verbNurbs));
|
|
}
|
|
|
|
toVerbNurbs(plane, _3dtr) {
|
|
throw 'not implemented'
|
|
}
|
|
}
|
|
|
|
export class Segment extends SketchPrimitive {
|
|
constructor(id, a, b) {
|
|
super(id);
|
|
this.a = a;
|
|
this.b = b;
|
|
}
|
|
|
|
approximateImpl(resolution) {
|
|
return [this.a, this.b];
|
|
}
|
|
|
|
toVerbNurbs(plane, _3dtr) {
|
|
return new verb.geom.Line(_3dtr(this.a).data(), _3dtr(this.b).data());
|
|
}
|
|
}
|
|
|
|
export class Arc extends SketchPrimitive {
|
|
constructor(id, a, b, c) {
|
|
super(id);
|
|
this.a = a;
|
|
this.b = b;
|
|
this.c = c;
|
|
}
|
|
|
|
approximateImpl(resolution) {
|
|
return Arc.approximateArc(this.a, this.b, this.c, resolution);
|
|
}
|
|
|
|
static approximateArc(ao, bo, c, resolution) {
|
|
var a = ao.minus(c);
|
|
var b = bo.minus(c);
|
|
var points = [ao];
|
|
var abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x);
|
|
if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle;
|
|
if (abAngle < 0) abAngle = Math.PI * 2 + abAngle;
|
|
|
|
var r = a.length();
|
|
resolution = 1;
|
|
//var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r)));
|
|
var step = resolution / (2 * Math.PI);
|
|
var k = Math.round(abAngle / step);
|
|
var angle = Math.atan2(a.y, a.x) + step;
|
|
|
|
for (var i = 0; i < k - 1; ++i) {
|
|
points.push(new Point(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle)));
|
|
angle += step;
|
|
}
|
|
points.push(bo);
|
|
return points;
|
|
}
|
|
|
|
toVerbNurbs(plane, _3dtr) {
|
|
const basis = plane.basis();
|
|
const startAngle = makeAngle0_360(Math.atan2(this.a.y - this.c.y, this.a.x - this.c.x));
|
|
const endAngle = makeAngle0_360(Math.atan2(this.b.y - this.c.y, this.b.x - this.c.x));
|
|
|
|
let angle = endAngle - startAngle;
|
|
if (angle < 0) {
|
|
angle = Math.PI * 2 + angle;
|
|
}
|
|
function pointAtAngle(angle) {
|
|
const dx = basis[0].multiply(Math.cos(angle));
|
|
const dy = basis[1].multiply(Math.sin(angle));
|
|
return dx.plus(dy);
|
|
}
|
|
const xAxis = pointAtAngle(startAngle);
|
|
const yAxis = pointAtAngle(startAngle + Math.PI * 0.5);
|
|
|
|
let arc = new verb.geom.Arc(_3dtr(this.c).data(), xAxis.data(), yAxis.data(), distanceAB(this.c, this.a), 0, Math.abs(angle));
|
|
return arc;
|
|
}
|
|
}
|
|
|
|
export class BezierCurve extends SketchPrimitive {
|
|
constructor(id, a, b, cp1, cp2) {
|
|
super(id);
|
|
this.a = a;
|
|
this.b = b;
|
|
this.cp1 = cp1;
|
|
this.cp2 = cp2;
|
|
}
|
|
|
|
approximateImpl(resolution) {
|
|
return LUT(this.a, this.b, this.cp1, this.cp2, 10);
|
|
}
|
|
}
|
|
|
|
export class EllipticalArc extends SketchPrimitive {
|
|
constructor(id, ep1, ep2, a, b, r) {
|
|
super(id);
|
|
this.ep1 = ep1;
|
|
this.ep2 = ep2;
|
|
this.a = a;
|
|
this.b = b;
|
|
this.r = r;
|
|
}
|
|
|
|
approximateImpl(resolution) {
|
|
return EllipticalArc.approxEllipticalArc(this.ep1, this.ep2, this.a, this.b, this.r, resolution);
|
|
}
|
|
|
|
static approxEllipticalArc(ep1, ep2, ao, bo, radiusY, resolution) {
|
|
const axisX = ep2.minus(ep1);
|
|
const radiusX = axisX.length() * 0.5;
|
|
axisX._normalize();
|
|
const c = ep1.plus(axisX.multiply(radiusX));
|
|
const a = ao.minus(c);
|
|
const b = bo.minus(c);
|
|
const points = [ao];
|
|
const rotation = Math.atan2(axisX.y, axisX.x);
|
|
let abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x);
|
|
if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle;
|
|
if (abAngle < 0) abAngle = Math.PI * 2 + abAngle;
|
|
|
|
const sq = (a) => a * a;
|
|
|
|
resolution = 1;
|
|
|
|
const step = resolution / (2 * Math.PI);
|
|
const k = Math.round(abAngle / step);
|
|
let angle = Math.atan2(a.y, a.x) + step - rotation;
|
|
|
|
for (let i = 0; i < k - 1; ++i) {
|
|
const r = Math.sqrt(1/( sq(Math.cos(angle)/radiusX) + sq(Math.sin(angle)/radiusY)));
|
|
points.push(new Point(c.x + r*Math.cos(angle + rotation), c.y + r*Math.sin(angle + rotation)));
|
|
angle += step;
|
|
}
|
|
points.push(bo);
|
|
return points;
|
|
}
|
|
|
|
}
|
|
|
|
export class Circle extends SketchPrimitive {
|
|
constructor(id, c, r) {
|
|
super(id);
|
|
this.c = c;
|
|
this.r = r;
|
|
}
|
|
|
|
approximateImpl(resolution) {
|
|
return Circle.approxCircle(this.c, this.r, resolution);
|
|
}
|
|
|
|
static approxCircle(c, r, resolution) {
|
|
var points = [];
|
|
resolution = 1;
|
|
//var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r)));
|
|
var step = resolution / (2 * Math.PI);
|
|
var k = Math.round((2 * Math.PI) / step);
|
|
|
|
for (var i = 0, angle = 0; i < k; ++i, angle += step) {
|
|
points.push(new Point(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle)));
|
|
}
|
|
points.push(points[0]); // close it
|
|
return points;
|
|
}
|
|
|
|
|
|
toVerbNurbs(plane, _3dtr) {
|
|
const basis = plane.basis();
|
|
return new verb.geom.Circle(_3dtr(this.c).data(), basis[0].data(), basis[1].data(), this.r);
|
|
}
|
|
}
|
|
|
|
export class Ellipse extends SketchPrimitive {
|
|
constructor(id, ep1, ep2, r) {
|
|
super(id);
|
|
this.ep1 = ep1;
|
|
this.ep2 = ep2;
|
|
this.r = r;
|
|
}
|
|
|
|
approximateImpl(resolution) {
|
|
return EllipticalArc.approxEllipticalArc(this.ep1, this.ep2, this.ep1, this.ep1, this.r, resolution);
|
|
}
|
|
}
|
|
|
|
export class Contour {
|
|
|
|
constructor() {
|
|
this.segments = [];
|
|
}
|
|
|
|
add(obj) {
|
|
this.segments.push(obj);
|
|
}
|
|
|
|
approximateOnSurface(surface) {
|
|
const cc = new CompositeCurve();
|
|
const tr = to3DTrFunc(surface);
|
|
|
|
let prev = null;
|
|
let firstPoint = null;
|
|
for (let segIdx = 0; segIdx < this.segments.length; ++segIdx) {
|
|
let segment = this.segments[segIdx];
|
|
let approximation = segment.approximate(RESOLUTION);
|
|
|
|
approximation = approximation.map(p => tr(p));
|
|
|
|
const n = approximation.length;
|
|
prev = prev == null ? approximation[0] : prev;
|
|
approximation[0] = prev; // this magic is to keep identity of same vectors
|
|
if (firstPoint == null) firstPoint = approximation[0];
|
|
|
|
if (segIdx == this.segments.length - 1) {
|
|
approximation[n - 1] = firstPoint;
|
|
}
|
|
|
|
cc.add(segment.toNurbs(surface), prev, segment);
|
|
prev = approximation[n - 1];
|
|
|
|
//It might be an optimization for segments
|
|
// for (let i = 1; i < n; ++i) {
|
|
// const curr = approximation[i];
|
|
// cc.add(new Line.fromSegment(prev, curr), prev, segment);
|
|
// prev = curr;
|
|
// }
|
|
}
|
|
return cc;
|
|
}
|
|
|
|
transferOnSurface(surface) {
|
|
const cc = [];
|
|
|
|
let prev = null;
|
|
let firstPoint = null;
|
|
for (let segIdx = 0; segIdx < this.segments.length; ++segIdx) {
|
|
let segment = this.segments[segIdx];
|
|
cc.push(segment.toNurbs(surface));
|
|
}
|
|
return cc;
|
|
}
|
|
|
|
approximate(resolution) {
|
|
const approximation = [];
|
|
for (let segment of this.segments) {
|
|
const segmentApproximation = segment.approximate(resolution);
|
|
//skip last one cuz it's guaranteed to be closed
|
|
for (let i = 0; i < segmentApproximation.length - 1; ++i) {
|
|
approximation.push(segmentApproximation[i]);
|
|
}
|
|
}
|
|
return approximation;
|
|
}
|
|
|
|
isCCW() {
|
|
return isCCW(this.approximate(10));
|
|
}
|
|
|
|
reverse() {
|
|
this.segments.reverse();
|
|
this.segments.forEach(s => s.invert());
|
|
}
|
|
}
|
|
|
|
function to3DTrFunc(surface) {
|
|
const _3dTransformation = surface.get3DTransformation();
|
|
return function (v) {
|
|
return _3dTransformation.apply(v);
|
|
}
|
|
}
|
|
|
|
class CompositeCurve {
|
|
|
|
constructor() {
|
|
this.curves = [];
|
|
this.points = [];
|
|
this.groups = [];
|
|
}
|
|
|
|
add(curve, point, group) {
|
|
this.curves.push(curve);
|
|
this.points.push(point);
|
|
this.groups.push(group);
|
|
}
|
|
}
|
|
|