introducing sketch model / change reading 2d geometry / change extruding API

This commit is contained in:
Val Erastov 2017-04-04 17:27:04 -07:00
parent 721b2fc137
commit 84de8be661
20 changed files with 827 additions and 501 deletions

View file

@ -225,96 +225,6 @@ export function fixCCW(path, normal) {
export const isPointInsidePolygon = math.isPointInsidePolygon;
export function sketchToPolygons(geom) {
var dict = HashTable.forVector2d();
var edges = HashTable.forDoubleArray();
var lines = geom.connections;
function edgeKey(a, b) {
return [a.x, a.y, b.x, b.y];
}
var size = 0;
var points = [];
function memDir(a, b) {
var dirs = dict.get(a);
if (dirs === null) {
dirs = [];
dict.put(a, dirs);
points.push(a);
}
dirs.push(b);
}
for (var i = 0; i < lines.length; i++) {
var a = lines[i].a;
var b = lines[i].b;
memDir(a, b);
memDir(b, a);
edges.put(edgeKey(a, b), lines[i]);
}
var graph = {
connections : function(e) {
var dirs = dict.get(e);
return dirs === null ? [] : dirs;
},
at : function(index) {
return points[index];
},
size : function() {
return points.length;
}
};
var loops = Graph.findAllLoops(graph, dict.hashCodeF, dict.equalsF);
var polygons = [];
var li, loop, polyPoints;
for (li = 0; li < loops.length; ++li) {
loop = loops[li];
if (!isCCW(loop)) loop.reverse();
polyPoints = [];
for (var pi = 0; pi < loop.length; ++pi) {
var point = loop[pi];
var next = loop[(pi + 1) % loop.length];
var edge = edges.get(edgeKey(point, next));
if (edge === null) {
edge = edges.get(edgeKey(next, point));
}
polyPoints.push(point);
point.sketchConnectionObject = edge.sketchObject;
}
if (polyPoints.length >= 3) {
polygons.push(polyPoints);
} else {
console.warn("Points count < 3!");
}
}
for (li = 0; li < geom.loops.length; ++li) {
loop = geom.loops[li];
polyPoints = loop.slice(0);
for (var si = 0; si < polyPoints.length; si++) {
var conn = polyPoints[si];
//reuse a point and ignore b point since it's a guaranteed loop
conn.a.sketchConnectionObject = conn.sketchObject;
polyPoints[si] = conn.a;
}
// we assume that connection object is the same al other the loop. That's why reverse is safe.
if (!isCCW(polyPoints)) polyPoints.reverse();
if (polyPoints.length >= 3) {
polygons.push(polyPoints);
}
}
return polygons;
}
export function someBasis2(normal) {
var x = normal.cross(normal.randomNonParallelVector());
var y = normal.cross(x).unit();

View file

@ -1,13 +1,15 @@
import {Matrix3, BasisForPlane, ORIGIN} from '../../../math/l3space'
import * as math from '../../../math/math'
import Vector from '../../../math/vector'
import {Extruder} from '../../../brep/brep-builder'
import {enclose, iterateSegments} from '../../../brep/brep-builder'
import {BREPValidator} from '../../../brep/brep-validator'
import * as stitching from '../../../brep/stitching'
import {subtract, union} from '../../../brep/operations/boolean'
import {Loop} from '../../../brep/topo/loop'
import {Shell} from '../../../brep/topo/shell'
import {ReadSketchFromFace} from './sketch-reader'
import {CompositeCurve} from '../../../brep/geom/curve'
import {ReadSketchContoursFromFace} from '../sketch/sketch-reader'
import {Segment} from '../sketch/sketch-model'
import {isCurveClass} from '../../cad-utils'
import {BREPSceneSolid} from '../../scene/brep-scene-object'
@ -23,19 +25,11 @@ export function Cut(app, params) {
export function doOperation(app, params, cut) {
const face = app.findFace(params.face);
const solid = face.solid;
let reverseNormal = !cut;
let normal = face.normal();
if (params.value < 0) {
params = fixNegativeValue(params);
reverseNormal = !reverseNormal;
}
if (reverseNormal) normal = normal.negate();
const sketch = ReadSketchFromFace(app, face, reverseNormal);
const sketch = ReadSketchContoursFromFace(app, face);
const extruder = new ParametricExtruder(params);
const operand = combineShells(sketch.map(s => extruder.extrude(s, normal)));
const details = getEncloseDetails(params, sketch, face.brepFace.surface, !cut);
const operand = combineShells(details.map(d => enclose(d.basePath, d.lidPath, d.baseSurface, d.lidSurface, wallJoiner)));
BREPValidator.validateToConsole(operand);
let result;
@ -59,14 +53,6 @@ export function doOperation(app, params, cut) {
}
}
export function fixNegativeValue(params) {
if (params.value < 0) {
params = Object.assign({}, params);
params.value *= -1;
}
return params;
}
function combineShells(shells) {
if (shells.length == 1) {
return shells[0];
@ -76,47 +62,61 @@ function combineShells(shells) {
return cutter;
}
export class ParametricExtruder extends Extruder {
constructor(params) {
super();
this.params = params;
}
prepareLidCalculation(baseNormal, lidNormal) {
let target;
this.basis = BasisForPlane(baseNormal);
if (this.params.rotation != 0) {
target = Matrix3.rotateMatrix(this.params.rotation * Math.PI / 180, this.basis[0], ORIGIN).apply(lidNormal);
if (this.params.angle != 0) {
target = Matrix3.rotateMatrix(this.params.angle * Math.PI / 180, this.basis[2], ORIGIN)._apply(target);
}
target._multiply(Math.abs(this.params.value));
} else {
target = lidNormal.multiply(Math.abs(this.params.value));
}
this.target = target;
}
calculateLid(basePoints, baseNormal, lidNormal) {
if (this.params.prism != 1) {
const scale = this.params.prism;
const _3Dtr = new Matrix3().setBasis(this.basis);
const _2Dtr = _3Dtr.invert();
const poly2d = basePoints.map(p => _2Dtr.apply(p));
basePoints = math.polygonOffset(poly2d, scale).map(p => _3Dtr.apply(p));
}
return basePoints.map(p => p.plus(this.target));
}
onWallCallback(wallFace, baseHalfEdge) {
const conn = baseHalfEdge.vertexA.point.sketchConnectionObject;
if (conn && isCurveClass(conn._class)) {
if (!conn.stitchedSurface) {
conn.stitchedSurface = new stitching.StitchedSurface();
}
conn.stitchedSurface.addFace(wallFace);
export function wallJoiner(wallFace, group) {
if (group && group.constructor.name != 'Segment') {
if (!group.stitchedSurface) {
group.stitchedSurface = new stitching.StitchedSurface();
}
group.stitchedSurface.addFace(wallFace);
}
}
export function getEncloseDetails(params, contours, sketchSurface, invert) {
let value = params.value;
if (value < 0) {
value = Math.abs(value);
invert = !invert;
}
const baseSurface = invert ? sketchSurface.invert() : sketchSurface;
let target;
const targetDir = baseSurface.normal.negate();
if (params.rotation != 0) {
target = Matrix3.rotateMatrix(params.rotation * Math.PI / 180, this.basis[0], ORIGIN).apply(targetDir);
if (params.angle != 0) {
target = Matrix3.rotateMatrix(params.angle * Math.PI / 180, this.basis[2], ORIGIN)._apply(target);
}
target._multiply(value);
} else {
target = targetDir.multiply(value);
}
let details = [];
for (let contour of contours) {
if (invert) {
contour.reverse();
}
const basePath = contour.transferOnSurface(sketchSurface);
const lidPath = new CompositeCurve();
let lidPoints = basePath.points;
if (!math.equal(params.prism, 1)) {
const _3D = sketchSurface.get3DTransformation();
const _2D = _3D.invert();
lidPoints = math.polygonOffset(lidPoints.map(p => _2D.apply(p)) , params.prism).map(p => _3D._apply(p));
}
for (let i = 0; i < basePath.points.length; ++i) {
const curve = basePath.curves[i];
const point = lidPoints[i];
const group = basePath.groups[i];
lidPath.add(curve.translate(target), point.plus(target), group);
}
const lidSurface = baseSurface.translate(target).invert();
details.push({basePath, lidPath, baseSurface, lidSurface});
}
return details;
}

View file

@ -1,9 +0,0 @@
import {sortPolygons, getSketchedPolygons3D} from '../mesh/workbench'
// here will be function of conversion sketch objects to brep DS
export function ReadSketchFromFace(app, face, reverseGeom) {
return getSketchedPolygons3D(app, face, reverseGeom);
}

View file

@ -1,8 +1,10 @@
import {CURRENT_SELECTION as S} from './wizard'
import {PreviewWizard, SketchBasedPreviewer} from './preview-wizard'
import {ParametricExtruder, fixNegativeValue} from '../cut-extrude'
import {getEncloseDetails} from '../cut-extrude'
import {TriangulatePolygons} from '../../../triangulation'
import Vector from '../../../../math/vector'
import {reversedIndex} from '../../../../utils/utils'
const METADATA = [
['value' , 'number', 50],
@ -50,31 +52,23 @@ export class ExtrudePreviewer extends SketchBasedPreviewer {
}
createImpl(app, params, sketch, face) {
const normal = face.normal();
let reverseNormal = this.inversed;
if (params.value < 0) {
params = fixNegativeValue(params);
reverseNormal = !reverseNormal;
}
const parametricExtruder = new ParametricExtruder(params);
const baseNormal = reverseNormal ? normal : normal.negate();
const lidNormal = reverseNormal ? baseNormal.negate() : normal;
parametricExtruder.prepareLidCalculation(baseNormal, lidNormal);
const triangles = [];
for (let base of sketch) {
var lid = parametricExtruder.calculateLid(base);
const encloseDetails = getEncloseDetails(params, sketch, face.brepFace.surface, !this.inversed);
const triangles = [];
for (let d of encloseDetails) {
const base = d.basePath.points;
const lid = d.lidPath.points;
const n = base.length;
for (let p = n - 1, q = 0; q < n; p = q ++) {
triangles.push([ base[p], base[q], lid[q] ]);
triangles.push([ lid[q], lid[p], base[p] ]);
}
TriangulatePolygons([base], baseNormal, (v) => v.toArray(), (arr) => new Vector().set3(arr))
.forEach(tr => triangles.push(tr));
TriangulatePolygons([lid], lidNormal, (v) => v.toArray(), (arr) => new Vector().set3(arr))
.forEach(tr => triangles.push(tr));
function collectOnSurface(points, normal) {
TriangulatePolygons([points], normal, (v) => v.toArray(), (arr) => new Vector().set3(arr))
.forEach(tr => triangles.push(tr));
}
collectOnSurface(base, d.baseSurface.normal);
collectOnSurface(lid, d.lidSurface.normal);
}
return triangles;
}

View file

@ -1,5 +1,5 @@
import {Wizard} from './wizard'
import {ReadSketchFromFace} from '../sketch-reader'
import {ReadSketchContoursFromFace} from '../../sketch/sketch-reader'
import {Loop} from '../../../../brep/topo/loop'
export class PreviewWizard extends Wizard {
@ -77,7 +77,7 @@ export class SketchBasedPreviewer {
if (!face) return null;
const needSketchRead = !this.sketch || params.face != this.face;
if (needSketchRead) {
this.sketch = ReadSketchFromFace(app, face);
this.sketch = ReadSketchContoursFromFace(app, face, false);
//for (let polygon of this.sketch) {
//if (!Loop.isPolygonCCWOnSurface(polygon, face.brepFace.surface) && this.fixToCCW) {
// polygon.reverse();

View file

@ -9,197 +9,6 @@ import {LoadSTLFromURL} from '../../io'
import revolve from './revolve'
import {Triangulate} from '../../triangulation'
function SketchConnection(a, b, sketchObject) {
this.a = a;
this.b = b;
this.sketchObject = sketchObject;
}
export function readSketchGeom(sketch, faceId, readConstructionSegments) {
let idCounter = 0;
function createData(obj) {
return {_class : obj._class, id : faceId + ":" + (idCounter ++)}
}
const RESOLUTION = 20;
const out = {connections : [], loops : [], constructionSegments: []};
if (sketch.layers !== undefined) {
for (let layer of sketch.layers) {
const isConstructionLayer = layer.name == "_construction_";
if (isConstructionLayer && !readConstructionSegments) continue;
for (let obj of layer.data) {
if (isConstructionLayer && obj._class !== 'TCAD.TWO.Segment') continue;
if (obj.edge !== undefined) continue;
if (!!obj.aux) continue;
if (obj._class === 'TCAD.TWO.Segment') {
const segA = new Vector(obj.points[0][1][1], obj.points[0][2][1], 0);
const segB = new Vector(obj.points[1][1][1], obj.points[1][2][1], 0);
const sketchConnection = new SketchConnection(segA, segB, createData(obj));
if (isConstructionLayer) {
out.constructionSegments.push(sketchConnection);
} else {
out.connections.push(sketchConnection);
}
} else if (obj._class === 'TCAD.TWO.Arc') {
const arcA = new Vector(obj.points[0][1][1], obj.points[0][2][1], 0);
const arcB = new Vector(obj.points[1][1][1], obj.points[1][2][1], 0);
const arcCenter = new Vector(obj.points[2][1][1], obj.points[2][2][1], 0);
const approxedArc = approxArc(arcA, arcB, arcCenter, RESOLUTION);
const arcData = createData(obj);
for (let j = 0; j < approxedArc.length - 1; j++) {
out.connections.push(new SketchConnection(approxedArc[j], approxedArc[j+1], arcData));
}
} else if (obj._class === 'TCAD.TWO.EllipticalArc') {
const ep1 = ReadSketchPoint(obj.ep1);
const ep2 = ReadSketchPoint(obj.ep2);
const a = ReadSketchPoint(obj.a);
const b = ReadSketchPoint(obj.b);
const r = obj.r;
const approxedEllArc = approxEllipticalArc(ep1, ep2, a, b, r, RESOLUTION);
const arcData = createData(obj);
for (let j = 0; j < approxedEllArc.length - 1; j++) {
out.connections.push(new SketchConnection(approxedEllArc[j], approxedEllArc[j+1], arcData));
}
} else if (obj._class === 'TCAD.TWO.BezierCurve') {
const a = ReadSketchPoint(obj.a);
const b = ReadSketchPoint(obj.b);
const cp1 = ReadSketchPoint(obj.cp1);
const cp2 = ReadSketchPoint(obj.cp2);
const approxedCurve = approxBezierCurve(a, b, cp1, cp2, RESOLUTION);
const curvedData = createData(obj);
for (let j = 0; j < approxedCurve.length - 1; j++) {
out.connections.push(new SketchConnection(approxedCurve[j], approxedCurve[j+1], curvedData));
}
} else if (obj._class === 'TCAD.TWO.Circle') {
const circleCenter = new Vector(obj.c[1][1], obj.c[2][1], 0);
const approxedCircle = approxCircle(circleCenter, obj.r, RESOLUTION);
const circleData = createData(obj);
const loop = [];
let p, q, n = approxedCircle.length;
for (p = n - 1, q = 0; q < n; p = q++) {
loop.push(new SketchConnection(approxedCircle[p], approxedCircle[q], circleData));
}
out.loops.push(loop);
}
}
}
}
return out;
}
function ReadSketchPoint(arr) {
return new Vector(arr[1][1], arr[2][1], 0)
}
export function approxArc(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 Vector(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle)));
angle += step;
}
points.push(bo);
return points;
}
export function 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 Vector(c.x + r*Math.cos(angle + rotation), c.y + r*Math.sin(angle + rotation)));
angle += step;
}
points.push(bo);
return points;
}
export function 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 Vector(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle)));
}
return points;
}
export function approxBezierCurve(a, b, cp1, cp2, resolution) {
return LUT(a, b, cp1, cp2, 10);
}
export function getSketchedPolygons3D(app, face, reverseGeom) {
var savedFace = localStorage.getItem(app.faceStorageKey(face.id));
if (savedFace == null) return null;
var geom = readSketchGeom(JSON.parse(savedFace), face.id, false);
var polygons2D = cad_utils.sketchToPolygons(geom);
if (reverseGeom) {
polygons2D.forEach(p => p.reverse());
}
var depth = null;
var sketchedPolygons = [];
for (var i = 0; i < polygons2D.length; i++) {
var poly2D = polygons2D[i];
if (poly2D.length < 3) continue;
if (depth == null) {
var _3dTransformation = new Matrix3().setBasis(face.basis());
//we lost depth or z off in 2d sketch, calculate it again
depth = face.depth();
}
var polygon = [];
polygon._2D = poly2D;
for (var m = 0; m < poly2D.length; ++m) {
var vec = poly2D[m];
vec.z = depth;
// var a = _3dTransformation.apply(new Vector(poly2D[m][0], poly2D[m][1], depth));
var a = _3dTransformation.apply(vec);
a.sketchConnectionObject = vec.sketchConnectionObject;
polygon.push(a);
}
sketchedPolygons.push(polygon);
}
return sketchedPolygons;
}
export function sortPolygons(polygons) {
function Loop(polygon) {
this.polygon = polygon;

View file

@ -0,0 +1,252 @@
import {CompositeCurve} from '../../../brep/geom/curve'
import {ApproxCurve} from '../../../brep/geom/impl/approx'
import {Point} from '../../../brep/geom/point'
import {Line} from '../../../brep/geom/impl/Line'
import {LUT} from '../../../math/bezier-cubic'
import {isCCW} from '../../../math/math'
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';
}
}
export class Segment extends SketchPrimitive {
constructor(id, a, b) {
super(id);
this.a = a;
this.b = b;
}
approximateImpl(resolution) {
return [this.a, this.b];
}
}
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;
}
}
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;
}
}
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);
}
transferOnSurface(surface, forceApproximation) {
const cc = new CompositeCurve();
const _3dTransformation = surface.get3DTransformation();
const depth = surface.w;
function tr(v) {
v = v.copy();
v.z = depth;
return _3dTransformation._apply(v);
}
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;
}
if (!forceApproximation && segment.constructor.name == 'Arc') {
cc.add(new ApproxCurve(approximation, segment), prev, segment);
prev = approximation[n - 1];
} else {
for (let i = 1; i < n; ++i) {
const curr = approximation[i];
cc.add(new Line.fromSegment(prev, curr), prev, segment);
prev = curr;
}
}
}
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());
}
}

View file

@ -0,0 +1,170 @@
import * as sm from './sketch-model'
import {Matrix3, AXIS, ORIGIN} from '../../../math/l3space'
import Vector from '../../../math/vector'
import {Graph} from '../../../math/graph'
import * as math from '../../../math/math'
import {HashTable} from '../../../utils/hashmap'
export function ReadSketch(sketch, faceId, readConstructionSegments) {
let idCounter = 0;
function genID() {
return faceId + ":" + (idCounter++);
}
const out = {connections : [], loops : [], constructionSegments: []};
if (sketch.layers !== undefined) {
for (let layer of sketch.layers) {
const isConstructionLayer = layer.name == "_construction_";
if (isConstructionLayer && !readConstructionSegments) continue;
for (let obj of layer.data) {
if (isConstructionLayer && obj._class !== 'TCAD.TWO.Segment') continue;
if (obj.edge !== undefined) continue;
if (!!obj.aux) continue;
if (obj._class === 'TCAD.TWO.Segment') {
const segA = ReadSketchPoint(obj.points[0]);
const segB = ReadSketchPoint(obj.points[1]);
const pushOn = isConstructionLayer ? out.constructionSegments : out.connections;
pushOn.push(new sm.Segment(genID(), segA, segB));
} else if (obj._class === 'TCAD.TWO.Arc') {
const arcA = ReadSketchPoint(obj.points[0]);
const arcB = ReadSketchPoint(obj.points[1]);
const arcCenter = ReadSketchPoint(obj.points[2]);
out.connections.push(new sm.Arc(genID(), arcA, arcB, arcCenter));
} else if (obj._class === 'TCAD.TWO.EllipticalArc') {
const ep1 = ReadSketchPoint(obj.ep1);
const ep2 = ReadSketchPoint(obj.ep2);
const a = ReadSketchPoint(obj.a);
const b = ReadSketchPoint(obj.b);
out.connections.push(new sm.EllipticalArc(genID(), ep1, ep2, a, b, obj.r));
} else if (obj._class === 'TCAD.TWO.BezierCurve') {
const a = ReadSketchPoint(obj.a);
const b = ReadSketchPoint(obj.b);
const cp1 = ReadSketchPoint(obj.cp1);
const cp2 = ReadSketchPoint(obj.cp2);
out.connections.push(new sm.BezierCurve(genID(), a, b, cp1, cp2));
} else if (obj._class === 'TCAD.TWO.Circle') {
const circleCenter = ReadSketchPoint(obj.c);
out.loops.push(new sm.Circle(genID(), circleCenter, obj.r));
} else if (obj._class === 'TCAD.TWO.Ellipse') {
const ep1 = ReadSketchPoint(obj.ep1);
const ep2 = ReadSketchPoint(obj.ep2);
out.loops.push(new sm.Ellipse(genID(), ep1, ep2, obj.r));
}
}
}
}
return out;
}
export function ReadSketchPoint(arr) {
return new Vector(arr[1][1], arr[2][1], 0)
}
export function ReadSketchContoursFromFace(app, face) {
const savedFace = localStorage.getItem(app.faceStorageKey(face.id));
if (savedFace == null) return null;
const geom = ReadSketch(JSON.parse(savedFace), face.id, false);
const contours = findClosedContours(geom.connections);
for (let loop of geom.loops) {
const contour = new sm.Contour();
contour.add(loop);
contours.push(contour);
}
for (let contour of contours) {
if (!contour.isCCW()) contour.reverse();
}
return contours;
}
function findClosedContours(segments) {
const result = [];
findClosedContoursFromPairedCurves(segments, result);
findClosedContoursFromGraph(segments, result);
return result;
}
function findClosedContoursFromPairedCurves(segments, result) {
for (let i = 0; i < segments.length; i++) {
const s1 = segments[i];
for (let j = i; j < segments.length; j++) {
if (i == j) continue;
const s2 = segments[j];
if (s1.isCurve() && s2.isCurve()) {
let paired = false;
if (math.strictEqual2D(s1.a, s2.a) && math.strictEqual2D(s1.b, s2.b)) {
paired = true;
s2.invert();
} else if (math.strictEqual2D(s1.a, s2.b) && math.strictEqual2D(s1.b, s2.a)) {
paired = true;
}
if (paired) {
const contour = new sm.Contour();
contour.add(s1);
contour.add(s2);
result.push(contour);
}
}
}
}
}
function findClosedContoursFromGraph(segments, result) {
const dict = HashTable.forVector2d();
const edges = HashTable.forDoubleArray();
function edgeKey(a, b) {
return [a.x, a.y, b.x, b.y];
}
const points = [];
function memDir(a, b) {
let dirs = dict.get(a);
if (dirs === null) {
dirs = [];
dict.put(a, dirs);
points.push(a);
}
dirs.push(b);
}
for (let seg of segments) {
const a = seg.a;
const b = seg.b;
memDir(a, b);
memDir(b, a);
edges.put(edgeKey(a, b), seg);
}
const graph = {
connections : function(e) {
const dirs = dict.get(e);
return dirs === null ? [] : dirs;
},
at : function(index) {
return points[index];
},
size : function() {
return points.length;
}
};
const loops = Graph.findAllLoops(graph, dict.hashCodeF, dict.equalsF);
for (let loop of loops) {
const contour = new sm.Contour();
for (let pi = 0; pi < loop.length; ++pi) {
const point = loop[pi];
const next = loop[(pi + 1) % loop.length];
let edge = edges.get(edgeKey(point, next));
if (edge === null) {
edge = edges.get(edgeKey(next, point));
edge.invert();
}
contour.add(edge);
}
result.push(contour);
}
}

View file

@ -9,6 +9,7 @@ import * as AllActions from './actions/all-actions'
import Vector from '../math/vector'
import {Matrix3, AXIS, ORIGIN, IDENTITY_BASIS} from '../math/l3space'
import {Craft} from './craft/craft'
import {ReadSketch} from './craft/sketch/sketch-reader'
import * as workbench from './craft/mesh/workbench'
import * as cad_utils from './cad-utils'
import * as math from '../math/math'
@ -598,7 +599,7 @@ App.prototype.refreshSketchOnFace = function(sketchFace) {
var faceStorageKey = this.faceStorageKey(sketchFace.id);
var savedFace = localStorage.getItem(faceStorageKey);
if (savedFace != null) {
var geom = workbench.readSketchGeom(JSON.parse(savedFace), sketchFace.id, true);
var geom = ReadSketch(JSON.parse(savedFace), sketchFace.id, true);
sketchFace.syncSketches(geom);
}
};

View file

@ -2,7 +2,7 @@ import {HashTable} from '../../utils/hashmap'
import Vector from '../../math/vector'
import Counters from '../counters'
import {Matrix3, BasisForPlane} from '../../math/l3space'
import {arrFlatten1L, isCurveClass} from '../cad-utils'
import {isCurveClass} from '../cad-utils'
import DPR from '../../utils/dpr'
export class SceneSolid {
@ -61,6 +61,8 @@ export function createSolidMaterial(skin) {
}, skin));
}
const OFF_LINES_VECTOR = new Vector();//normal.multiply(0); // disable it. use polygon offset feature of material
export class SceneFace {
constructor(solid, propagatedId) {
if (propagatedId === undefined) {
@ -109,9 +111,6 @@ export class SceneFace {
}
syncSketches(geom) {
const normal = this.normal();
const offVector = new Vector();//normal.multiply(0); // disable it. use polygon offset feature of material
if (this.sketch3DGroup != null) {
for (let i = this.sketch3DGroup.children.length - 1; i >= 0; --i) {
this.sketch3DGroup.remove(this.sketch3DGroup.children[i]);
@ -125,34 +124,29 @@ export class SceneFace {
const _3dTransformation = new Matrix3().setBasis(basis);
//we lost depth or z off in 2d sketch, calculate it again
const depth = this.depth();
const polyLines = new Map();
function addSketchConnections(connections, material) {
for (let i = 0; i < connections.length; ++i) {
const l = connections[i];
const addSketchObjects = (sketchObjects, material, close) => {
for (let sketchObject of sketchObjects) {
let line = new THREE.Line(undefined, material);
line.__TCAD_SketchObject = sketchObject;
const chunks = sketchObject.approximate(10);
function addLine(p, q) {
const lg = line.geometry;
chunks[p].z = chunks[q].z = depth;
const a = _3dTransformation.apply(chunks[p]);
const b = _3dTransformation.apply(chunks[q]);
let line = polyLines.get(l.sketchObject.id);
if (!line) {
line = new THREE.Line(undefined, material);
line.__TCAD_SketchObject = l.sketchObject;
polyLines.set(l.sketchObject.id, line);
lg.vertices.push(a._plus(OFF_LINES_VECTOR).three());
lg.vertices.push(b._plus(OFF_LINES_VECTOR).three());
}
const lg = line.geometry;
l.a.z = l.b.z = depth;
const a = _3dTransformation.apply(l.a);
const b = _3dTransformation.apply(l.b);
lg.vertices.push(a.plus(offVector).three());
lg.vertices.push(b.plus(offVector).three());
for (let q = 1; q < chunks.length; q ++) {
addLine(q - 1, q);
}
this.sketch3DGroup.add(line);
}
}
addSketchConnections(geom.constructionSegments, SKETCH_CONSTRUCTION_MATERIAL);
addSketchConnections(geom.connections, SKETCH_MATERIAL);
addSketchConnections(arrFlatten1L(geom.loops), SKETCH_MATERIAL);
for (let line of polyLines.values()) {
this.sketch3DGroup.add(line);
}
};
addSketchObjects(geom.constructionSegments, SKETCH_CONSTRUCTION_MATERIAL);
addSketchObjects(geom.connections, SKETCH_MATERIAL);
addSketchObjects(geom.loops, SKETCH_MATERIAL);
}
findById(sketchObjectId) {

View file

@ -4,9 +4,11 @@ import {Loop} from './topo/loop'
import {Face} from './topo/face'
import {HalfEdge, Edge} from './topo/edge'
import {Line} from './geom/impl/line'
import {ApproxCurve, ApproxSurface} from './geom/impl/approx'
import {Plane} from './geom/impl/plane'
import {Point} from './geom/point'
import {BasisForPlane, Matrix3} from '../math/l3space'
import {CompositeCurve} from './geom/curve'
import * as cad_utils from '../3d/cad-utils'
import * as math from '../math/math'
@ -25,95 +27,111 @@ function checkCCW(points, normal) {
}
export function createPrism(basePoints, height) {
return new SimpleExtruder(height).extrude(basePoints, cad_utils.normalOfCCWSeq(basePoints));
const normal = cad_utils.normalOfCCWSeq(basePoints);
const baseSurface = new Plane(normal, normal.dot(basePoints[0]));
const extrudeVector = baseSurface.normal.multiply( - height);
const lidSurface = baseSurface.translate(extrudeVector).invert();
const lidPoints = basePoints.map(p => p.plus(extrudeVector));
const basePath = new CompositeCurve();
const lidPath = new CompositeCurve();
for (let i = 0; i < basePoints.length; i++) {
let j = (i + 1) % basePoints.length;
basePath.add(Line.fromSegment(basePoints[i], basePoints[j]), basePoints[i], null);
lidPath.add(Line.fromSegment(lidPoints[i], lidPoints[j]), lidPoints[i], null);
}
return enclose(basePath, lidPath, baseSurface, lidSurface, () => {});
}
export class Extruder {
prepareLidCalculation(baseNormal, lidNormal) {
}
calculateLid(basePoints) {
throw 'not implemented';
}
extrude(basePoints, normal) {
const baseLoop = createPlaneLoop(basePoints.map(p => new Vertex(p)));
const baseFace = createPlaneFace(normal, baseLoop);
const lidNormal = normal.multiply(-1);
this.prepareLidCalculation(normal, lidNormal);
export function enclose(basePath, lidPath, baseSurface, lidSurface, onWallF) {
//iterateSegments(basePoints.map(p => new Vertex(p.plus(offVector))), (a, b) => lidSegments.push({a, b}));
const lidPoints = this.calculateLid(basePoints, normal, lidNormal).reverse();
const lidLoop = createPlaneLoop(lidPoints.map(p => new Vertex(p)));
if (basePath.points.length != lidPath.points.length) {
throw 'illegal arguments';
}
const baseLoop = new Loop();
const lidLoop = new Loop();
const shell = new Shell();
const shell = new Shell();
const baseVertices = basePath.points.map(p => new Vertex(p));
const lidVertices = lidPath.points.map(p => new Vertex(p));
const n = baseLoop.halfEdges.length;
for (let i = 0; i < n; i++) {
let lidIdx = n - 2 - i;
if (lidIdx == -1) {
lidIdx = n - 1;
}
const baseHalfEdge = baseLoop.halfEdges[i];
const lidHalfEdge = lidLoop.halfEdges[lidIdx];
const wallPolygon = [baseHalfEdge.vertexB, baseHalfEdge.vertexA, lidHalfEdge.vertexB, lidHalfEdge.vertexA];
const wallLoop = createPlaneLoop(wallPolygon);
const baseEdge = new Edge(Line.fromSegment(baseHalfEdge.vertexA.point, baseHalfEdge.vertexB.point));
linkHalfEdges(baseEdge, baseHalfEdge, wallLoop.halfEdges[0]);
const lidEdge = new Edge(Line.fromSegment(lidHalfEdge.vertexA.point, lidHalfEdge.vertexB.point));
linkHalfEdges(lidEdge, lidHalfEdge, wallLoop.halfEdges[2]);
const wallNormal = cad_utils.normalOfCCWSeq(wallPolygon.map(v => v.point));
const wallFace = createPlaneFace(wallNormal, wallLoop);
wallFace.role = 'wall:' + i;
this.onWallCallback(wallFace, baseHalfEdge);
shell.faces.push(wallFace);
}
const lidFace = createPlaneFace(lidNormal, lidLoop);
iterateSegments(shell.faces, (a, b) => {
const halfEdgeA = a.outerLoop.halfEdges[3];
const halfEdgeB = b.outerLoop.halfEdges[1];
const curve = Line.fromSegment(halfEdgeA.vertexA.point, halfEdgeA.vertexB.point);
linkHalfEdges(new Edge(curve), halfEdgeA, halfEdgeB);
});
baseFace.role = 'base';
lidFace.role = 'lid';
const n = basePath.points.length;
for (let i = 0; i < n; i++) {
let j = (i + 1) % n;
const baseHalfEdge = new HalfEdge().setAB(baseVertices[i], baseVertices[j]);
const lidHalfEdge = new HalfEdge().setAB(lidVertices[j], lidVertices[i]);
shell.faces.push(baseFace, lidFace);
shell.faces.forEach(f => f.shell = shell);
return shell;
}
baseHalfEdge.edge = new Edge(basePath.curves[i]);
lidHalfEdge.edge = new Edge(lidPath.curves[i]);
onWallCallback(wallFace, baseHalfEdge) {
baseHalfEdge.edge.halfEdge1 = baseHalfEdge;
lidHalfEdge.edge.halfEdge1 = lidHalfEdge;
baseHalfEdge.loop = baseLoop;
baseLoop.halfEdges.push(baseHalfEdge);
lidHalfEdge.loop = lidLoop;
lidLoop.halfEdges[(n + n - 2 - i) % n] = lidHalfEdge; // keep old style order for the unit tests
const wallFace = createFaceFromTwoEdges(createTwin(baseHalfEdge), createTwin(lidHalfEdge));
wallFace.role = 'wall:' + i;
onWallF(wallFace, basePath.groups[i]);
shell.faces.push(wallFace);
}
iterateSegments(shell.faces, (a, b) => {
const halfEdgeA = a.outerLoop.halfEdges[3];
const halfEdgeB = b.outerLoop.halfEdges[1];
const curve = Line.fromSegment(halfEdgeA.vertexA.point, halfEdgeA.vertexB.point);
linkHalfEdges(new Edge(curve), halfEdgeA, halfEdgeB);
});
linkSegments(baseLoop.halfEdges);
linkSegments(lidLoop.halfEdges);
const baseFace = createFace(baseSurface, baseLoop);
const lidFace = createFace(lidSurface, lidLoop);
baseFace.role = 'base';
lidFace.role = 'lid';
shell.faces.push(baseFace, lidFace);
shell.faces.forEach(f => f.shell = shell);
return shell;
}
export class SimpleExtruder extends Extruder {
constructor(height) {
super();
this.height = height;
function createTwin(halfEdge) {
const twin = new HalfEdge();
twin.vertexA = halfEdge.vertexB;
twin.vertexB = halfEdge.vertexA;
twin.edge = halfEdge.edge;
if (halfEdge.edge.halfEdge1 == halfEdge) {
halfEdge.edge.halfEdge2 = twin;
} else {
halfEdge.edge.halfEdge1 = twin;
}
return twin;
}
prepareLidCalculation(baseNormal, lidNormal) {
this.extrudeVector = lidNormal.multiply(this.height);
}
function createFace(surface, loop) {
const face = new Face(surface);
face.outerLoop = loop;
loop.face = face;
return face;
}
calculateLid(basePoints) {
return basePoints.map(p => p.plus(this.extrudeVector))
}
function createPlaneForLoop(normal, loop) {
const w = loop.halfEdges[0].vertexA.point.dot(normal);
const plane = new Plane(normal, w);
return plane;
}
function createPlaneFace(normal, loop) {
const w = loop.halfEdges[0].vertexA.point.dot(normal);
const plane = new Plane(normal, w);
const plane = createPlaneForLoop();
const face = new Face(plane);
face.outerLoop = loop;
loop.face = face;
@ -128,23 +146,26 @@ export function linkHalfEdges(edge, halfEdge1, halfEdge2) {
edge.halfEdge2 = halfEdge2;
}
export function createPlaneLoop(vertices) {
export function createLoopFromCompositeCurve(path) { // TODO: REMOVE!
const loop = new Loop();
iterateSegments(vertices, (a, b) => {
createHalfEdge(loop, a, b)
});
const vertices = [];
for (let seg of path) {
vertices[seg.pos] = new Vertex(s.pointA);
}
for (let seg of path) {
const halfEdge = createHalfEdge(loop, vertices[seg.pos], vertices[seg.posNext]);
halfEdge.edge = new Edge(seg.curve);
halfEdge.edge.halfEdge1 = halfEdge;
}
linkSegments(loop.halfEdges);
return loop;
}
export function createHalfEdge(loop, a, b) {
export function createHalfEdge(loop, vertexA, vertexB) {
const halfEdge = new HalfEdge();
halfEdge.loop = loop;
halfEdge.vertexA = a;
halfEdge.vertexB = b;
halfEdge.vertexA = vertexA;
halfEdge.vertexB = vertexB;
loop.halfEdges.push(halfEdge);
return halfEdge;
}
@ -177,3 +198,54 @@ export function invertLoop(loop) {
loop.halfEdges.reverse();
linkSegments(loop.halfEdges);
}
export function createPlaneLoop(vertices) {
const loop = new Loop();
iterateSegments(vertices, (a, b) => {
createHalfEdge(loop, a, b)
});
linkSegments(loop.halfEdges);
return loop;
}
export function createFaceFromTwoEdges(e1, e2) {
const loop = new Loop();
e1.loop = loop;
e2.loop = loop;
loop.halfEdges.push(
e1,
HalfEdge.create(e1.vertexB, e2.vertexA, loop),
e2,
HalfEdge.create(e2.vertexB, e1.vertexA, loop));
let surface = null;
if (e1.edge.curve.constructor.name == 'Line' &&
e2.edge.curve.constructor.name == 'Line') {
const normal = cad_utils.normalOfCCWSeq(loop.halfEdges.map(e => e.vertexA.point));
surface = createPlaneForLoop(normal, loop);
} else if ((e1.edge.curve instanceof ApproxCurve) && (e2.edge.curve instanceof ApproxCurve)) {
const chunk1 = e1.edge.curve.getChunk(e1.edge.vertexA.point, e1.edge.vertexB.point);
const chunk2 = e2.edge.curve.getChunk(e2.edge.vertexA.point, e2.edge.vertexB.point);
const n = chunk1.length;
if (n != chunk2.length) {
throw 'unsupported';
}
surface = new ApproxSurface();
for (let p = n - 1, q = 0; q < n; p = q ++) {
const polygon = [ chunk1[p], chunk1[q], chunk2[q], chunk2[p] ];
surface.mesh.push(polygon);
}
} else {
throw 'unsupported';
}
linkSegments(loop.halfEdges);
const face = new Face(surface);
face.outerLoop = loop;
loop.face = face;
return face;
}

View file

@ -2,7 +2,6 @@
export class Curve {
constructor() {
}
intersectCurve(curve) {
@ -12,4 +11,24 @@ export class Curve {
parametricEquation(t) {
throw 'not implemented';
}
}
translate(vector) {
throw 'not implemented';
}
}
export 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);
}
}

View file

@ -0,0 +1,20 @@
import {Surface} from '../surface'
import {Curve} from '../curve'
import {Matrix3, AXIS, BasisForPlane} from '../../../math/l3space'
import * as math from '../../../math/math'
export class ApproxSurface extends Surface {
constructor(mesh) {
super();
this.mesh = mesh;
}
}
export class ApproxCurve extends Curve {
constructor(segments, proto) {
super();
this.segments = segments;
this.proto = proto;
}
}

View file

@ -44,6 +44,10 @@ export class Line extends Curve {
}
return point;
}
translate(vector) {
return new Line(this.p0.plus(vector), this.v);
}
}
Line.fromTwoPlanesIntersection = function(plane1, plane2) {

View file

@ -12,16 +12,14 @@ export class Plane extends Surface {
}
calculateBasis() {
const normal = this.normal;
let alignPlane, x, y;
if (Math.abs(normal.dot(AXIS.Y)) < 0.5) {
alignPlane = normal.cross(AXIS.Y);
} else {
alignPlane = normal.cross(AXIS.Z);
return BasisForPlane(this.normal);
}
basis() {
if (!this._basis) {
this._basis = this.calculateBasis();
}
y = alignPlane.cross(normal);
x = y.cross(normal);
return [x, y, normal];
return this._basis;
}
intersect(other) {
@ -30,7 +28,11 @@ export class Plane extends Surface {
}
return super.intersect();
}
translate(vector) {
return new Plane(this.normal, this.normal.dot(this.normal.multiply(this.w)._plus(vector)));
}
invert() {
return new Plane(this.normal.multiply(-1), - this.w);
}
@ -40,7 +42,7 @@ export class Plane extends Surface {
}
get3DTransformation() {
return new Matrix3().setBasis(this.calculateBasis());
return new Matrix3().setBasis(this.basis());
}
coplanarUnsigned(other, tol) {

62
web/app/brep/geom/path.js Normal file
View file

@ -0,0 +1,62 @@
import {Point} from './point'
import {defineIterable} from '../../utils/utils'
class Path {
constructor(head) {
this.head = head;
defineIterable(this, 'segments', () => segmentsGenerator(this));
}
isClosed() {
return this.head.prev != null;
}
}
export class PathBuilder {
constructor(head) {
this.head = null;
this.tail = null;
defineIterable(this, 'segments', () => segmentsGenerator(this));
}
addSegment(segment) {
if (this.tail == null) {
this.head = segment;
} else {
this.tail.next = segment;
}
segment.prev = this.tail;
this.tail = segment;
}
close() {
this.tail.next = this.head;
this.head.prev = this.tail;
return this.head;
}
unclosed() {
return this.head;
}
}
export class Segment {
constructor(curve, point) {
this.curve = curve;
this.point = point;
this.next = null;
this.prev = null;
this.data = {};
}
}
export function* segmentsGenerator(path) {
let node = path.head;
while (node != null) {
yield node;
node = node.next;
if (node == path.node) break;
}
}

View file

@ -29,9 +29,17 @@ export class HalfEdge extends TopoObject {
this.prev = null;
}
static create(a, b, loop, edge) {
const e = new HalfEdge().setAB(a, b);
e.loop = loop;
e.edge = edge;
return e;
}
setAB(a, b) {
this.vertexA = a;
this.vertexB = b;
return this;
}
twin() {

View file

@ -3,12 +3,13 @@ import * as math from './math'
export function LUT(a, b, cp1, cp2, scale) {
scale = 1 / scale;
const lut = [a];
const lut = [];
for (let t = 0; t < 1; t += 0.1 * scale) {
const p = compute(t, a, b, cp1, cp2);
lut.push(p);
}
lut.push(b);
lut[0] = a;
lut[lut.length - 1] = b;
return lut;
}

View file

@ -70,6 +70,10 @@ export function strictEqual(a, b) {
return a.x == b.x && a.y == b.y && a.z == b.z;
}
export function strictEqual2D(a, b) {
return a.x == b.x && a.y == b.y;
}
export function _vec(size) {
var out = [];
out.length = size;

View file

@ -67,6 +67,11 @@ export function camelCaseSplit(str) {
return words;
}
export function defineIterable(obj, name, iteratorFactory) {
obj[name] = {};
obj[name][Symbol.iterator] = iteratorFactory;
}
export class DoubleKeyMap {
constructor() {
@ -98,4 +103,12 @@ export class DoubleKeyMap {
}
subMap.set(b, value);
}
}
export function reversedIndex(i, n) {
let lidIdx = n - i;
if (lidIdx == n) {
lidIdx = 0;
}
return lidIdx;
}