From 6dc0f99746693a3e736c0ba79adcfcef769a59a1 Mon Sep 17 00:00:00 2001 From: Val Erastov Date: Tue, 27 Jun 2017 22:14:45 -0700 Subject: [PATCH] nurbs boolean --- web/app/3d/craft/brep/wizards/revolve.js | 55 ++++++++++++++++ web/app/3d/craft/brep/wizards/wizard.js | 27 +++++--- web/app/3d/craft/sketch/sketch-model.js | 4 +- web/app/3d/ui/ctrl.js | 2 +- web/app/brep/brep-builder.js | 9 ++- web/app/brep/geom/impl/nurbs.js | 23 ++++++- web/app/brep/geom/impl/plane.js | 40 ++++++++---- web/app/brep/geom/surface.js | 58 +++++++++++++---- web/app/brep/operations/boolean.js | 80 ++++++++++++------------ 9 files changed, 217 insertions(+), 81 deletions(-) create mode 100644 web/app/3d/craft/brep/wizards/revolve.js diff --git a/web/app/3d/craft/brep/wizards/revolve.js b/web/app/3d/craft/brep/wizards/revolve.js new file mode 100644 index 00000000..dce9c0e4 --- /dev/null +++ b/web/app/3d/craft/brep/wizards/revolve.js @@ -0,0 +1,55 @@ +import {CURRENT_SELECTION as S} from './wizard' +import {PreviewWizard, SketchBasedPreviewer} from './preview-wizard' +import {TriangulatePolygons} from '../../../triangulation' +import Vector from '../../../../math/vector' + + +const METADATA = [ + ['angle' , 'number', 5, {min: -360, max: 360, step: 10}], + ['pivot' , 'sketch' , S ] +]; + +export class RevolveWizard extends PreviewWizard { + constructor(app, initialState) { + super(app, 'REVOLVE', METADATA, initialState) + } + + createPreviewObject(app, params) { + return CUT_PREVIEWER.create(app, params); + } + + uiLabel(name) { + if ('value' == name) return 'depth'; + return super.uiLabel(name); + } +} + + + +export class ExtrudePreviewer extends SketchBasedPreviewer { + + createImpl(app, params, sketch, face) { + const triangles = []; + + sketch. + + 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] ]); + } + + 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; + } +} + diff --git a/web/app/3d/craft/brep/wizards/wizard.js b/web/app/3d/craft/brep/wizards/wizard.js index 6bb34186..b738e034 100644 --- a/web/app/3d/craft/brep/wizards/wizard.js +++ b/web/app/3d/craft/brep/wizards/wizard.js @@ -112,20 +112,27 @@ export class Wizard { }); return new Field(radio, () => radio.getValue(), (v) => radio.setValue(v)); } else if (type == 'face') { - const face = new tk.Text(label, initValue); - face.input.on('change', () => this.onUIChange(name)); - return Field.fromInput(face, undefined, (faceId) => { - if (faceId === CURRENT_SELECTION) { - let selection = this.app.viewer.selectionMgr.selection[0]; - return selection ? selection.id : ''; - } else { - return faceId; - } - }); + return selectionWidget(name, label, initValue, this.app.viewer.selectionMgr); + } else if (type == 'sketch.segment') { + return selectionWidget(name, label, initValue, this.app.viewer.sketchSelectionMgr); } } } +function selectionWidget(name, label, initValue, selectionManager) { + const obj = new tk.Text(label, initValue); + obj.input.on('change', () => this.onUIChange(name)); + return Field.fromInput(obj, undefined, (objId) => { + if (objId === CURRENT_SELECTION) { + let selection = selectionManager.selection[0]; + return selection ? selection.id : ''; + } else { + return objId; + } + }); + +} + function FaceSelectionListener() { this.callbacks = []; } diff --git a/web/app/3d/craft/sketch/sketch-model.js b/web/app/3d/craft/sketch/sketch-model.js index a13382af..ae51b5e2 100644 --- a/web/app/3d/craft/sketch/sketch-model.js +++ b/web/app/3d/craft/sketch/sketch-model.js @@ -226,7 +226,9 @@ const USE_APPROX_FOR = new Set(); const USE_NURBS_FOR = new Set(); USE_NURBS_FOR.add('Arc'); USE_NURBS_FOR.add('Circle'); - +//USE_NURBS_FOR.add('Ellipse'); +//USE_NURBS_FOR.add('EllipticalArc'); +//USE_NURBS_FOR.add('BezierCurve'); export class Contour { diff --git a/web/app/3d/ui/ctrl.js b/web/app/3d/ui/ctrl.js index 8cf76e86..34056bd3 100644 --- a/web/app/3d/ui/ctrl.js +++ b/web/app/3d/ui/ctrl.js @@ -8,7 +8,7 @@ import * as Operations from '../craft/operations' import Menu from '../menu/menu' import {ExtrudeWizard, CutWizard} from '../craft/brep/wizards/cut-extrude' -import {RevolveWizard} from '../craft/mesh/wizards/revolve' +import {RevolveWizard} from '../craft/brep/wizards/revolve' import {PlaneWizard} from '../craft/brep/wizards/plane' import {BoxWizard} from '../craft/brep/wizards/box' import {SphereWizard} from '../craft/mesh/wizards/sphere' diff --git a/web/app/brep/brep-builder.js b/web/app/brep/brep-builder.js index 027e1ba2..32a38554 100644 --- a/web/app/brep/brep-builder.js +++ b/web/app/brep/brep-builder.js @@ -48,7 +48,7 @@ export function createPrism(basePoints, height) { } -export function enclose(basePath, lidPath, baseSurface, lidSurface, onWallF) { +export function enclose(basePath, lidPath, baseSurface, lidSurface) { if (basePath.points.length != lidPath.points.length) { throw 'illegal arguments'; @@ -125,6 +125,13 @@ export function enclose(basePath, lidPath, baseSurface, lidSurface, onWallF) { return shell; } +export function revolve(basePath, baseSurface) { + const baseLoop = new Loop(); + + const shell = new Shell(); + new verb.geom.RevolvedSurface( prof, [0,0,0], [1,0,0], 2* Math.PI); +} + function createTwin(halfEdge) { const twin = new HalfEdge(); twin.vertexA = halfEdge.vertexB; diff --git a/web/app/brep/geom/impl/nurbs.js b/web/app/brep/geom/impl/nurbs.js index d6582529..aec22fa3 100644 --- a/web/app/brep/geom/impl/nurbs.js +++ b/web/app/brep/geom/impl/nurbs.js @@ -85,11 +85,30 @@ export class NurbsSurface { this.verb = verbSurface; } + isCognateCurve(curve) { + return curve.constructor.name == 'NurbsCurve'; + } + toNurbs() { return this; } - coplanarUnsignedForSameClass(other, tolerance) { - throw 'not implemented' + coplanarUnsignedForSameClass(other, tol) { + //throw 'not implemented' + return false; + } + + intersectForSameClass(other, tol) { + const curves = verb.geom.Intersect.surfaces(this.verb, other.verb, tol); + return curves.map(curve => new NurbsCurve(curve)); + } + + classifyCognateCurve(line, tol) { + const parallel = math.areEqual(line.v.dot(this.normal), 0, tol); + const pointOnPlane = math.areEqual(this.normal.dot(line.p0), this.w, tol); + return { + hit: !parallel || pointOnPlane, + parallel + } } } \ No newline at end of file diff --git a/web/app/brep/geom/impl/plane.js b/web/app/brep/geom/impl/plane.js index 8a93fb18..da5ad53b 100644 --- a/web/app/brep/geom/impl/plane.js +++ b/web/app/brep/geom/impl/plane.js @@ -3,7 +3,7 @@ import {Point} from '../point' import {Line} from './line' import {Matrix3, AXIS, BasisForPlane} from '../../../math/l3space' import * as math from '../../../math/math' - + export class Plane extends Surface { constructor(normal, w) { @@ -12,22 +12,23 @@ export class Plane extends Surface { this.w = w; } + isCognateCurve(curve) { + return curve.constructor.name == 'Line'; + } + calculateBasis() { return BasisForPlane(this.normal); } - + basis() { if (!this._basis) { this._basis = this.calculateBasis(); } return this._basis; } - - intersect(other) { - if (other.isPlane) { - return new Line.fromTwoPlanesIntersection(this, other); - } - return super.intersect(); + + intersectForSameClass(other) { + return new Line.fromTwoPlanesIntersection(this, other); } translate(vector) { @@ -40,14 +41,18 @@ export class Plane extends Surface { get2DTransformation() { if (!this.__2dTr) { - this.__2dTr = this.get3DTransformation().invert(); + this.__2dTr = this.get3DTransformation().invert(); } return this.__2dTr; } get3DTransformation() { if (!this.__3dTr) { - this.__3dTr = new Matrix3().setBasis(this.basis()); + const basis = new Matrix3().setBasis(this.basis()); + const translate = new Matrix3(); + translate.tz = this.w + this.__3dTr = basis.combine(translate); +// this.__3dTr.tz = this.w; } return this.__3dTr; } @@ -69,7 +74,7 @@ export class Plane extends Surface { } return this.__parametricForm; } - + toUV(point) { return this.get2DTransformation().apply(point); } @@ -81,10 +86,19 @@ export class Plane extends Surface { domainU() { return [Number.MIN_VALUE, Number.MAX_VALUE]; } - + domainV() { return [Number.MIN_VALUE, Number.MAX_VALUE]; } + + classifyCognateCurve(line, tol) { + const parallel = math.areEqual(line.v.dot(this.normal), 0, tol); + const pointOnPlane = math.areEqual(this.normal.dot(line.p0), this.w, tol); + return { + hit: !parallel || pointOnPlane, + parallel + } + } } Plane.prototype.isPlane = true; @@ -100,4 +114,4 @@ class ParametricPlane { equation(u, v) { return this.r0 + this.r1.multiply(u) + this.r2.multiply(v); } -} \ No newline at end of file +} diff --git a/web/app/brep/geom/surface.js b/web/app/brep/geom/surface.js index 1f81b7ab..a6485595 100644 --- a/web/app/brep/geom/surface.js +++ b/web/app/brep/geom/surface.js @@ -4,27 +4,39 @@ export class Surface { constructor() { } - - intersect(other) { - return this.toNurbs.intersect(other.toNurbs()); - }; - toNurbs() { + //-------------------------------------------------------------------------------------------------------------------- + + classifyCognateCurve(curve, tol) { throw 'not implemented'; } + classifyCurve(curve, tol) { + if (this.isCognateCurve(curve)) { + return this.classifyCognateCurve(curve, tol) + } + return this.toNurbs().classifyCognateCurve(curve.toNurbs(), tol); + }; + + //-------------------------------------------------------------------------------------------------------------------- + + intersectForSameClass() { + throw 'not implemented'; + } + + intersect(other, tol) { + if (this.isSameClass(other)) { + return this.intersectForSameClass(other, tol) + } + return this.toNurbs().intersectForSameClass(other.toNurbs(), tol); + }; + + //-------------------------------------------------------------------------------------------------------------------- + coplanarUnsignedForSameClass(other, tol) { throw 'not implemented'; } - equalsUnsignedForSameClass(other, tol) { - throw 'not implemented'; - } - - isSameClass(other) { - return this.constructor.name == other.constructor.name; - } - coplanarUnsigned(other, tol) { if (this.isSameClass(other)) { return this.coplanarUnsignedForSameClass(other, tol) @@ -32,11 +44,31 @@ export class Surface { return this.toNurbs().coplanarUnsignedForSameClass(other.toNurbs()); } + //-------------------------------------------------------------------------------------------------------------------- + + equalsForSameClass(other, tol) { + throw 'not implemented'; + } + equals(other, tol) { if (this.isSameClass(other)) { return this.equalsForSameClass(other, tol) } return this.toNurbs().equalsForSameClass(other.toNurbs()); } + + //-------------------------------------------------------------------------------------------------------------------- + + toNurbs() { + throw 'not implemented'; + } + + isSameClass(other) { + return this.constructor.name == other.constructor.name; + } + + isCognateCurve(curve) { + return false; + } } Surface.prototype.isPlane = false; diff --git a/web/app/brep/operations/boolean.js b/web/app/brep/operations/boolean.js index 05093f52..cbac0bfe 100644 --- a/web/app/brep/operations/boolean.js +++ b/web/app/brep/operations/boolean.js @@ -10,7 +10,7 @@ import Vector from '../../math/vector'; import * as math from '../../math/math'; export const TOLERANCE = 1e-8; -export const TOLERANCE_SQ = TOLERANCE * TOLERANCE; + export const TOLERANCE_SQ = TOLERANCE * TOLERANCE; export const TOLERANCE_HALF = TOLERANCE * 0.5; const DEBUG = { @@ -70,7 +70,7 @@ export function BooleanAlgorithm( shell1, shell2, type ) { initSolveData(shell1, facesData); initSolveData(shell2, facesData); - + markOverlappingFaces(shell1, shell2); intersectFaces(shell1, shell2, type !== TYPE.UNION); @@ -86,7 +86,7 @@ export function BooleanAlgorithm( shell1, shell2, type ) { for (let faceData of facesData) { initGraph(faceData); } - + facesData = facesData.filter(fd => fd.merged !== true); const allFaces = []; @@ -140,7 +140,7 @@ function detectLoops(face) { } const loop = new Loop(); loop.face = face; - let surface = EdgeSolveData.get(edge).transferedSurface; + let surface = EdgeSolveData.get(edge).transferedSurface; if (!surface) { surface = face.surface; } @@ -170,7 +170,7 @@ function detectLoops(face) { } } return loops; -} +} function initGraph(faceData) { faceData.vertexToEdge.clear(); @@ -180,11 +180,11 @@ function initGraph(faceData) { } function sew(allFaces) { - + const sewed = new Set(); const sewedFaces = []; const analyzedNeighbors = new Map(); - FACES: + FACES: for (let face of allFaces) { if (DEBUG.SEWING) { __DEBUG__.Clear(); @@ -197,7 +197,7 @@ function sew(allFaces) { if (DEBUG.SEWING) { __DEBUG__.AddHalfEdge(h1); } - + if (sewed.has(h1)) { continue; } @@ -205,7 +205,7 @@ function sew(allFaces) { if (neighborhood.all.length == 1) { continue FACES; } - + let h2; if (neighborhood.all.length == 2 && neighborhood.side2.length == 1) { h2 = neighborhood.side2[0]; @@ -223,7 +223,7 @@ function sew(allFaces) { if (sewed.has(h2)) { throw 'illegal state. already sewed' } - + const edge = new Edge(h1.edge.curve); edge.halfEdge1 = h1; @@ -243,7 +243,7 @@ function edgeV(edge) { } function neighborhoodAnalysis(neighborhood, analized) { - + function encloses(e1, e2, testeeE) { const f1 = e1.loop.face; const f2 = e2.loop.face; @@ -263,7 +263,7 @@ function neighborhoodAnalysis(neighborhood, analized) { const testAngle = leftTurningMeasure(t1, t3, normal); return testAngle > angle; } - + let paired = new Set(); for (let e1 of neighborhood.side1) { SIDE_2: @@ -274,7 +274,7 @@ function neighborhoodAnalysis(neighborhood, analized) { continue; } if (encloses(e1, e2, t)) { - continue SIDE_2; + continue SIDE_2; } } analized.set(e1, e2); @@ -283,21 +283,21 @@ function neighborhoodAnalysis(neighborhood, analized) { paired.add(e2); } } - + for (let e of neighborhood.all) { if (!paired.has(e)) { analized.set(e, null); } } -} - +} + function findNeighborhood(allFaces, skipFace, forEdge) { const result = { side1: [forEdge], side2: [], all: [forEdge] }; - + for (let face of allFaces) { if (face == skipFace) continue; for (let e of face.edges) { @@ -355,7 +355,7 @@ function markOverlapping(face1, face2) { } function mergeOverlappingFaces(shell1, shell2) { - const merged = new Set(); + const merged = new Set(); for (let face1 of shell1.faces) { if (merged.has(face1)) continue; const data1 = face1.data[MY]; @@ -378,11 +378,11 @@ function mergeOverlappingFaces(shell1, shell2) { function doMergeOverlappingFaces(face1, face2, keepNew) { const data2 = face2.data[MY]; - + let allEdges = []; for (let e of face1.edges) { allEdges.push(e); - } + } for (let e of face2.edges) { const coi = findCoincidentEdge(e, allEdges); if (coi == null) { @@ -414,7 +414,7 @@ function doMergeOverlappingFaces(face1, face2, keepNew) { nullifyOppositeEdges(allEdges); allEdges = allEdges.filter(e => e != null); sort(allEdges); - squash(face1, allEdges); + squash(face1, allEdges); data2.merged = true; } @@ -476,9 +476,9 @@ function merge(face, newEdges) { allEdges.push(e); } } - + nullifyOppositeEdges(newEdges); - + for (let e of newEdges) { if (e == null) continue; const existingEdge = findCoincidentEdge(e, allEdges); @@ -488,9 +488,9 @@ function merge(face, newEdges) { EdgeSolveData.createIfEmpty(existingEdge).newEdgeFlag = true } } - + nullifyOppositeEdges(allEdges); - + allEdges = allEdges.filter(e => e != null); //put new edges to the tail bringNewEdgesToTheTail(allEdges); @@ -515,7 +515,7 @@ function nullifyOppositeEdges(edges) { if (i == j) continue; if (edges[j] == null) continue; if (areEdgesOpposite(edges[i], edges[j])) { - edges[i] = null; + edges[i] = null; edges[j] = null; continue main; } @@ -629,7 +629,7 @@ export function loopsToFaces(originFace, loops, out) { return originSurface; } } - + function createFaces(nestedLoop, surface, level) { if (!nestedLoop.loop.isCCW(surface)) { surface = invertSurface(surface); @@ -651,7 +651,7 @@ export function loopsToFaces(originFace, loops, out) { newFace.innerLoops.push(child.loop); } else { createFaces(child, surface, level + 1); - } + } } } } @@ -706,7 +706,7 @@ function initSolveData(shell, facesData) { for (let he of face.edges) { EdgeSolveData.clear(he); } - } + } } function cleanUpSolveData(shell) { @@ -736,7 +736,7 @@ function leftTurningMeasure(v1, v2, normal) { measure = -(2 + measure); } measure -= 1;//shift to the zero - + //make it positive all the way return -measure; } @@ -762,11 +762,11 @@ function intersectFaces(shell1, shell2, inverseCrossEdgeDirection) { continue; } const curve = face1.surface.intersect(face2.surface); - + const nodes = []; collectNodesOfIntersectionOfFace(face2, face1, nodes); collectNodesOfIntersectionOfFace(face1, face2, nodes); - + const newEdges = []; const direction = face1.surface.normal.cross(face2.surface.normal); if (inverseCrossEdgeDirection) { @@ -816,7 +816,7 @@ function filterNodes(nodes) { if (node2.vertex == node1.vertex) { if (node1.normal + node2.normal == 0) { nodes[i] = null - } + } nodes[j] = null } } @@ -885,7 +885,7 @@ function split(nodes, result, onCurve, direction) { const halfEdge1 = new HalfEdge(); halfEdge1.vertexA = inNode.vertex; halfEdge1.vertexB = outNode.vertex; - + const halfEdge2 = new HalfEdge(); halfEdge2.vertexB = halfEdge1.vertexA; halfEdge2.vertexA = halfEdge1.vertexB; @@ -908,7 +908,7 @@ function split(nodes, result, onCurve, direction) { edge.halfEdge2 = halfEdgeSameDir; halfEdgeNegativeDir.edge = edge; halfEdgeSameDir.edge = edge; - + //check for corner case when to faces only intersects in edges if (!containsEdges(result, edge)) { result.push(edge); @@ -931,7 +931,7 @@ function isSameEdge(e1, e2) { function splitEdgeByVertex(originHalfEdge, vertex, splittingFace) { - + function splitHalfEdge(h) { const newEdge = new HalfEdge(); newEdge.vertexA = vertex; @@ -946,20 +946,20 @@ function splitEdgeByVertex(originHalfEdge, vertex, splittingFace) { if (orig.vertexA == vertex || orig.vertexB == vertex) { return; } - + const newOrig = splitHalfEdge(orig); const newTwin = splitHalfEdge(twin); BREPBuilder.linkHalfEdges(orig.edge, orig, newTwin); BREPBuilder.linkHalfEdges(new Edge(orig.edge.curve), twin, newOrig); - + orig.loop.halfEdges.splice(orig.loop.halfEdges.indexOf(orig) + 1, 0, newOrig); twin.loop.halfEdges.splice(twin.loop.halfEdges.indexOf(twin) + 1, 0, newTwin); newOrig.loop = orig.loop; newTwin.loop = twin.loop; - + EdgeSolveData.transfer(orig, newOrig); EdgeSolveData.transfer(twin, newTwin); @@ -1009,7 +1009,7 @@ function intersectFaceWithEdge(face, edge, result) { const ab = edge.vertexB.point.minus(p0); const length = ab.length(); const v = ab._multiply(1 / length); - + if (math.areEqual(edge.edge.curve.v.dot(face.surface.normal), 0, TOLERANCE)) { if (math.areEqual(face.surface.normal.dot(edge.vertexA.point), face.surface.w, TOLERANCE)) { classifyAndAdd(edge.vertexA.point, true, false);