jsketcher/web/app/brep/brep-builder.js
2017-08-23 00:03:53 -07:00

249 lines
7.2 KiB
JavaScript

import {Shell} from './topo/shell'
import {Vertex} from './topo/vertex'
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 {NurbsSurface} from './geom/impl/nurbs'
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'
import verb from 'verb-nurbs'
function isCCW(points, normal) {
const tr2d = new Matrix3().setBasis(BasisForPlane(normal)).invert();
const points2d = points.map(p => tr2d.apply(p));
return math.isCCW(points2d);
}
function checkCCW(points, normal) {
if (!isCCW(points, normal)) {
points = points.slice();
points.reverse();
}
return points;
}
export function createPrism(basePoints, height) {
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 function enclose(basePath, lidPath, baseSurface, lidSurface, onWallF) {
if (basePath.points.length != lidPath.points.length) {
throw 'illegal arguments';
}
const baseLoop = new Loop();
const lidLoop = new Loop();
const shell = new Shell();
const baseVertices = basePath.points.map(p => new Vertex(p));
const lidVertices = lidPath.points.map(p => new Vertex(p));
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]);
baseHalfEdge.edge = new Edge(basePath.curves[i]);
lidHalfEdge.edge = new Edge(lidPath.curves[i]);
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;
}
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;
}
function createFace(surface, loop) {
const face = new Face(surface);
face.outerLoop = loop;
loop.face = face;
return face;
}
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 plane = createPlaneForLoop();
const face = new Face(plane);
face.outerLoop = loop;
loop.face = face;
return face;
}
export function linkHalfEdges(edge, halfEdge1, halfEdge2) {
halfEdge1.edge = edge;
halfEdge2.edge = edge;
edge.halfEdge1 = halfEdge1;
edge.halfEdge2 = halfEdge2;
}
export function createHalfEdge(loop, vertexA, vertexB) {
const halfEdge = new HalfEdge();
halfEdge.loop = loop;
halfEdge.vertexA = vertexA;
halfEdge.vertexB = vertexB;
loop.halfEdges.push(halfEdge);
return halfEdge;
}
export function linkSegments(halfEdges) {
iterateSegments(halfEdges, (prev, next) => {
prev.next = next;
next.prev = prev;
});
}
export function point(x, y, z) {
return new Point(x, y, z);
}
export function iterateSegments(items, callback) {
let length = items.length;
for (let i = 0; i < length; i++) {
let j = (i + 1) % length;
callback(items[i], items[j], i, j);
}
}
export function invertLoop(loop) {
for (let halfEdge of loop.halfEdges) {
const t = halfEdge.vertexA;
halfEdge.vertexA = halfEdge.vertexB;
halfEdge.vertexB = t;
}
loop.halfEdges.reverse();
linkSegments(loop.halfEdges);
}
export function createPlaneLoop(vertices, curves) {
const loop = new Loop();
iterateSegments(vertices, (a, b, i) => {
const halfEdge = createHalfEdge(loop, a, b);
halfEdge.edge = new Edge(curves[i] ? curves[i] : Line.fromSegment(a.point, b.point));
return halfEdge;
});
linkSegments(loop.halfEdges);
return loop;
}
function bothClassOf(o1, o2, className) {
return o1.constructor.name == className && o2.constructor.name == className;
}
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 (bothClassOf(e1.edge.curve, e2.edge.curve, 'Line')) {
const normal = cad_utils.normalOfCCWSeq(loop.halfEdges.map(e => e.vertexA.point));
surface = createPlaneForLoop(normal, loop);
} else if (bothClassOf(e1.edge.curve, e2.edge.curve, 'NurbsCurve')) {
const verbSurface = verb.geom.NurbsSurface.byLoftingCurves([e1.edge.curve.verb, e2.edge.curve.verb], 2);
surface = new NurbsSurface(verbSurface);
} else if (bothClassOf(e1.edge.curve, e2.edge.curve, 'ApproxCurve')) {
const chunk1 = e1.edge.curve.getChunk(e1.vertexA.point, e1.vertexB.point);
const chunk2 = e2.edge.curve.getChunk(e2.vertexA.point, e2.vertexB.point);
const n = chunk1.length;
if (n != chunk2.length) {
throw 'unsupported';
}
const mesh = [];
for (let p = n - 1, q = 0; q < n; p = q ++) {
mesh.push([ chunk1[p], chunk1[q], chunk2[q], chunk2[p] ]);
}
surface = new ApproxSurface(mesh);
} else {
throw 'unsupported';
}
linkSegments(loop.halfEdges);
const face = new Face(surface);
face.outerLoop = loop;
loop.face = face;
return face;
}