diff --git a/web/app/3d/cad-utils.js b/web/app/3d/cad-utils.js index 1f1e53ce..a0728e84 100644 --- a/web/app/3d/cad-utils.js +++ b/web/app/3d/cad-utils.js @@ -5,7 +5,7 @@ import {Graph} from '../math/graph' import * as math from '../math/math' import {Matrix3, AXIS, ORIGIN} from '../math/l3space' import Counters from './counters' -import {Solid} from './solid' +import {MeshSceneSolid} from './scene/mesh-scene-object' import DPR from '../utils/dpr' export const FACE_COLOR = 0xB0C4DE; @@ -143,8 +143,7 @@ export function createSolidMaterial() { } export function createSolid(csg, id) { - var material = createSolidMaterial(); - return new Solid(csg, material, undefined, id); + return new MeshSceneSolid(csg, undefined, id); } export function intercept(obj, methodName, aspect) { @@ -174,7 +173,7 @@ export function createPlane(basis, depth) { var currentBounds = new BBox(); var points = boundingPolygon.map(function(p) { p.z = depth; return tr._apply(p); }); var polygon = new CSG.Polygon(points.map(function(p){return new CSG.Vertex(csgVec(p))}), shared); - var plane = new Solid(CSG.fromPolygons([polygon]), material, 'PLANE'); + var plane = new MeshSceneSolid(CSG.fromPolygons([polygon]), 'PLANE'); plane.wireframeGroup.visible = false; plane.mergeable = false; @@ -190,7 +189,7 @@ export function createPlane(basis, depth) { bb.checkBounds( 400, 400); setBounds(bb); - var sketchFace = plane.polyFaces[0]; + var sketchFace = plane.sceneFaces[0]; intercept(sketchFace, 'syncSketches', function(invocation, args) { var geom = args[0]; invocation(geom); diff --git a/web/app/3d/modeler-app.js b/web/app/3d/modeler-app.js index be67b4be..4ef3f018 100644 --- a/web/app/3d/modeler-app.js +++ b/web/app/3d/modeler-app.js @@ -19,7 +19,7 @@ import '../../css/app3d.less' import * as BREPPrimitives from '../brep/brep-primitives' import * as BREPBool from '../brep/operations/boolean' import {BREPValidator} from '../brep/brep-validator' -import {SceneSolid} from '../brep/viz/scene-solid' +import {BREPSceneSolid} from './scene/brep-scene-object' function App() { this.id = this.processHints(); @@ -77,7 +77,7 @@ App.prototype.BREPTest = function() { App.prototype.BREPTestImpl1 = function() { const addToScene = (shell) => { - const sceneSolid = new SceneSolid(shell); + const sceneSolid = new BREPSceneSolid(shell); this.viewer.workGroup.add(sceneSolid.cadGroup); }; const box1 = BREPPrimitives.box(500, 500, 500); @@ -100,7 +100,7 @@ App.prototype.BREPTestImpl1 = function() { App.prototype.BREPTestImpl = function() { const addToScene = (shell) => { - const sceneSolid = new SceneSolid(shell); + const sceneSolid = new BREPSceneSolid(shell); this.viewer.workGroup.add(sceneSolid.cadGroup); }; const box1 = BREPPrimitives.box(500, 500, 500); @@ -158,8 +158,8 @@ App.prototype.findFace = function(faceId) { var solids = this.findAllSolids(); for (var i = 0; i < solids.length; i++) { var solid = solids[i]; - for (var j = 0; j < solid.polyFaces.length; j++) { - var face = solid.polyFaces[j]; + for (var j = 0; j < solid.sceneFaces.length; j++) { + var face = solid.sceneFaces[j]; if (face.id == faceId) { return face; } @@ -196,8 +196,8 @@ App.prototype.indexEntities = function() { for (var i = 0; i < solids.length; i++) { var solid = solids[i]; out.solids[solid.tCadId] = solid; - for (var j = 0; j < solid.polyFaces.length; j++) { - var face = solid.polyFaces[j]; + for (var j = 0; j < solid.sceneFaces.length; j++) { + var face = solid.sceneFaces[j]; out.faces[face.id] = face; } } @@ -223,8 +223,8 @@ App.prototype.editFace = function() { this.sketchFace(polyFace); }; -App.prototype.sketchFace = function(polyFace) { - var faceStorageKey = this.faceStorageKey(polyFace.id); +App.prototype.sketchFace = function(sceneFace) { + var faceStorageKey = this.faceStorageKey(sceneFace.id); var savedFace = localStorage.getItem(faceStorageKey); var data; @@ -241,10 +241,10 @@ App.prototype.sketchFace = function(polyFace) { return a.sketchConnectionObject.id === b.sketchConnectionObject.id; } - var paths = workbench.reconstructSketchBounds(polyFace.solid.csg, polyFace); + var paths = sceneFace.getBounds(); - //polyFace.polygon.collectPaths(paths); - var _3dTransformation = new Matrix3().setBasis(polyFace.basis()); + //sceneFace.polygon.collectPaths(paths); + var _3dTransformation = new Matrix3().setBasis(sceneFace.basis()); var _2dTr = _3dTransformation.invert(); function addSegment(a, b) { @@ -339,7 +339,7 @@ App.prototype.sketchFace = function(polyFace) { } for (var i = 0; i < paths.length; i++) { - var path = paths[i].vertices; + var path = paths[i]; if (path.length < 3) continue; var shift = 0; if (isCircle(path)) { @@ -385,7 +385,7 @@ App.prototype.sketchFace = function(polyFace) { localStorage.setItem(faceStorageKey, JSON.stringify(data)); var sketchURL = faceStorageKey.substring(App.STORAGE_PREFIX.length); - this.tabSwitcher.showSketch(sketchURL, polyFace.id); + this.tabSwitcher.showSketch(sketchURL, sceneFace.id); }; App.prototype.extrude = function() { @@ -445,15 +445,15 @@ App.prototype._refreshSketches = function() { var allSolids = this.findAllSolids(); for (var oi = 0; oi < allSolids.length; ++oi) { var obj = allSolids[oi]; - for (var i = 0; i < obj.polyFaces.length; i++) { - var sketchFace = obj.polyFaces[i]; + for (var i = 0; i < obj.sceneFaces.length; i++) { + var sketchFace = obj.sceneFaces[i]; this.refreshSketchOnFace(sketchFace); } } }; App.prototype.findSketches = function(solid) { - return solid.polyFaces.filter(f => this.faceStorageKey(f.id) in localStorage).map(f => f.id); + return solid.sceneFaces.filter(f => this.faceStorageKey(f.id) in localStorage).map(f => f.id); }; App.prototype.refreshSketchOnFace = function(sketchFace) { diff --git a/web/app/brep/viz/scene-solid.js b/web/app/3d/scene/brep-scene-object.js similarity index 66% rename from web/app/brep/viz/scene-solid.js rename to web/app/3d/scene/brep-scene-object.js index ac9162a6..90787a09 100644 --- a/web/app/brep/viz/scene-solid.js +++ b/web/app/3d/scene/brep-scene-object.js @@ -1,22 +1,20 @@ import Vector from '../../math/vector' import {Triangulate} from '../../3d/triangulation' +import {SceneSolid, SceneFace} from './scene-object' -export class SceneSolid { - - constructor(shell) { +export class BREPSceneSolid extends SceneSolid { + + constructor(shell, type) { + super(type); this.shell = shell; + this.createGeometry(); + } - this.cadGroup = new THREE.Object3D(); - this.cadGroup.__tcad_solid = this; - this.wireframeGroup = new THREE.Object3D(); - this.cadGroup.add(this.wireframeGroup); - + createGeometry() { const geometry = new THREE.Geometry(); geometry.dynamic = true; - this.mesh = new THREE.Mesh(geometry, createSolidMaterial()); + this.mesh = new THREE.Mesh(geometry, this.material); this.cadGroup.add(this.mesh); - - this.sceneFaces = []; this.createFaces(); this.createEdges(); this.createVertices(); @@ -27,7 +25,7 @@ export class SceneSolid { let gIdx = 0; const geom = this.mesh.geometry; for (let brepFace of this.shell.faces) { - const sceneFace = new SceneFace(brepFace, this); + const sceneFace = new BREPSceneFace(brepFace, this); this.sceneFaces.push(sceneFace); const polygons = triangulate(brepFace); for (let p = 0; p < polygons.length; ++p) { @@ -40,13 +38,10 @@ export class SceneSolid { const normal = threeV(brepFace.surface.normal); for (let i = 2; i < vLength; i++) { geom.vertices.push(threeV(poly[i])); - const a = off; const b = i - 1 + off; const c = i + off; - const face = new THREE.Face3(a, b, c); - sceneFace.meshFaces.push(face); - face.__TCAD_TOPO = sceneFace; + const face = sceneFace.createMeshFace(a, b, c); face.normal = normal; face.materialIndex = gIdx ++; geom.faces.push(face); @@ -58,7 +53,7 @@ export class SceneSolid { off = geom.vertices.length; } } - geom.mergeVertices(); + geom.mergeVertices(); } createEdges() { @@ -68,34 +63,36 @@ export class SceneSolid { if (!visited.has(halfEdge.edge)) { visited.add(halfEdge.edge); this.addLineToScene(halfEdge.vertexA.point.three(), halfEdge.vertexB.point.three(), halfEdge.edge); - } + } } } } - + createVertices() { - } - - addLineToScene(a, b, edge) { - const lg = new THREE.Geometry(); - lg.vertices.push(a); - lg.vertices.push(b); - const line = new THREE.Line(lg, WIREFRAME_MATERIAL); - line.__TCAD_edge = edge; - this.wireframeGroup.add(line); - }; - } -const WIREFRAME_MATERIAL = new THREE.LineBasicMaterial({color: 0xff0000, linewidth: 10}); - - -class SceneFace { +class BREPSceneFace extends SceneFace { constructor(brepFace, solid) { - this.solid = solid; + super(solid); this.brepFace = brepFace; - this.meshFaces = []; + } + + + normal() { + return this.brepFace.surface.normal; + } + + depth() { + return this.brepFace.surface.w; + } + + getBounds() { + const bounds = []; + for (let loop of this.brepFace.loops) { + bounds.push(loop.asPolygon().map(p => new Vector().setV(p))); + } + return bounds; } } @@ -108,10 +105,10 @@ function triangulate(face) { } const triangled = []; - const contours = []; + const contours = []; for (let loop of face.loops) { contours.push(loop.asPolygon().map(point => data(point))); - } + } let vertices = Triangulate(contours, data(face.surface.normal)); for (let i = 0; i < vertices.length; i += 3 ) { @@ -124,17 +121,4 @@ function triangulate(face) { } - -function createSolidMaterial() { - return new THREE.MeshPhongMaterial({ - vertexColors: THREE.FaceColors, - color: 0xB0C4DE, - shininess: 0, - polygonOffset : true, - polygonOffsetFactor : 1, - polygonOffsetUnits : 2, - //side : THREE.DoubleSide - }); -} - function threeV(v) {return new THREE.Vector3( v.x, v.y, v.z )} diff --git a/web/app/3d/scene/mesh-scene-object.js b/web/app/3d/scene/mesh-scene-object.js new file mode 100644 index 00000000..44768432 --- /dev/null +++ b/web/app/3d/scene/mesh-scene-object.js @@ -0,0 +1,220 @@ +import {HashTable} from '../../utils/hashmap' +import Vector from '../../math/vector' +import Counters from '../counters' +import {findOutline, segmentsToPaths, reconstructSketchBounds} from '../workbench' +import {Matrix3, AXIS} from '../../math/l3space' +import {arrFlatten1L, isCurveClass} from '../cad-utils' +import DPR from '../../utils/dpr' +import {SceneSolid, SceneFace} from './scene-object' + +export class MeshSceneSolid extends SceneSolid { + + constructor(csg, type, id) { + super(type, id); + csg = csg.reTesselated().canonicalized(); + this.csg = csg; + this.createGeometry(); + } + + createGeometry() { + const geometry = new THREE.Geometry(); + geometry.dynamic = true; + this.mesh = new THREE.Mesh(geometry, this.material); + this.cadGroup.add(this.mesh); + + this.wires = HashTable.forEdge(); + this.curvedSurfaces = {}; + + this.setupGeometry(); + } + + dropGeometry() { + this.cadGroup.remove( this.mesh ); + this.mesh.geometry.dispose(); + for(let i = this.wireframeGroup.children.length-1; i >=0 ; i--){ + this.wireframeGroup.remove(this.wireframeGroup.children[i]); + } + } + + setupGeometry() { + function threeV(v) {return new THREE.Vector3( v.x, v.y, v.z )} + + var off = 0; + var groups = groupCSG(this.csg); + var geom = this.mesh.geometry; + for (var gIdx in groups) { + var group = groups[gIdx]; + if (group.shared.__tcad === undefined) group.shared.__tcad = {}; + var sceneFace = new MeshSceneFace(this, group); + this.sceneFaces.push(sceneFace); + for (var p = 0; p < group.polygons.length; ++p) { + var poly = group.polygons[p]; + var vLength = poly.vertices.length; + if (vLength < 3) continue; + var firstVertex = poly.vertices[0]; + geom.vertices.push(threeV(firstVertex.pos)); + geom.vertices.push(threeV(poly.vertices[1].pos)); + var normal = threeV(poly.plane.normal); + for (var i = 2; i < vLength; i++) { + geom.vertices.push(threeV(poly.vertices[i].pos)); + + var a = off; + var b = i - 1 + off; + var c = i + off; + const face = sceneFace.createMeshFace(a, b, c); + face.normal = normal; + face.materialIndex = gIdx; + geom.faces.push(face); + //face.color.set(new THREE.Color().setRGB( Math.random(), Math.random(), Math.random())); + } + //view.setFaceColor(sceneFace, utils.isSmoothPiece(group.shared) ? 0xFF0000 : null); + off = geom.vertices.length; + } + this.collectCurvedSurface(sceneFace); + this.collectWires(sceneFace, group.polygons); + } + + geom.mergeVertices(); + + this.processWires(); + }; + + vanish () { + this.cadGroup.parent.remove( this.cadGroup ); + this.material.dispose(); + this.mesh.geometry.dispose(); + } + + collectCurvedSurface(face) { + var derivedFrom = getDerivedFrom(face.csgGroup.shared); + if (derivedFrom === null || !isCurveClass(derivedFrom._class)) return; + var surfaces = this.curvedSurfaces[derivedFrom.id]; + if (surfaces === undefined) { + surfaces = []; + this.curvedSurfaces[derivedFrom.id] = surfaces; + } + surfaces.push(face); + face.curvedSurfaces = surfaces; + } + + collectWires(face, facePolygons) { + + function contains(planes, plane) { + for (var j = 0; j < planes.length; j++) { + if (planes[j].equals(plane)) { + return true; + } + } + return false; + } + + const outline = findOutline(facePolygons); + const paths = segmentsToPaths(outline); + + for (var i = 0; i < paths.length; i++) { + var path = paths[i]; + var p, q, n = path.vertices.length; + for (q = 0, p = n - 1; q < n; p = q++) { + var edge = [path.vertices[p], path.vertices[q]]; + var data = this.wires.get(edge); + + if (data === null) { + data = { + sharedPlanes : [face.csgGroup.plane], + sharedFaces : [face] + }; + this.wires.put(edge, data); + } else { + if (!contains(data.sharedPlanes, face.csgGroup.plane)) { + data.sharedPlanes.push(face.csgGroup.plane); + } + data.sharedFaces.push(face); + } + } + } + } + + processWires() { + var solid = this; + this.wires.entries(function(edge, data) { + if (data.sharedPlanes.length > 1) { + var plane0 = data.sharedPlanes[0]; + var plane1 = data.sharedPlanes[1]; + var angle = Math.acos(plane0.normal.dot(plane1.normal)); + if (angle < SMOOTH_LIMIT) { + return; + } + } + for (var i = 0; i < data.sharedFaces.length; ++i) { + for (var j = i + 1; j < data.sharedFaces.length; ++j) { + var face0 = data.sharedFaces[0]; + var face1 = data.sharedFaces[1]; + if (sameID(getDerivedID(face0.csgGroup.shared), getDerivedID(face1.csgGroup.shared))) { + return; + } + } + } + + solid.addLineToScene(edge[0], edge[1]); + }); + } +} + +function groupCSG(csg) { + var csgPolygons = csg.toPolygons(); + var groups = {}; + for (var i = 0; i < csgPolygons.length; i++) { + var p = csgPolygons[i]; + var tag = p.shared.getTag(); + if (groups[tag] === undefined) { + groups[tag] = { + tag : tag, + polygons : [], + shared : p.shared, + plane : p.plane + }; + } + groups[tag].polygons.push(p); + } + return groups; +} + +const SMOOTH_LIMIT = 10 * Math.PI / 180; + +class MeshSceneFace extends SceneFace { + constructor(solid, csgGroup) { + super(solid, csgGroup.shared.__tcad.faceId); + csgGroup.__face = this; + csgGroup.shared.__tcad.faceId = this.id; + + this.csgGroup = csgGroup; + this.curvedSurfaces = null; + } + + normal() { + return this.csgGroup.plane.normal; + } + + depth() { + return this.csgGroup.plane.w; + } + + getBounds() { + return reconstructSketchBounds(this.solid.csg, this); + } +} + +function sameID(id1, id2) { + if (id1 === null || id2 === null) { + return false; + } + return id1 === id2; +} + +function getDerivedID(shared) { + return shared.__tcad && !!shared.__tcad.csgInfo && !!shared.__tcad.csgInfo.derivedFrom ? shared.__tcad.csgInfo.derivedFrom.id : null; +} + +function getDerivedFrom(shared) { + return shared.__tcad && !!shared.__tcad.csgInfo && !!shared.__tcad.csgInfo.derivedFrom ? shared.__tcad.csgInfo.derivedFrom : null; +} \ No newline at end of file diff --git a/web/app/3d/scene/scene-object.js b/web/app/3d/scene/scene-object.js new file mode 100644 index 00000000..77315b34 --- /dev/null +++ b/web/app/3d/scene/scene-object.js @@ -0,0 +1,176 @@ +import {HashTable} from '../../utils/hashmap' +import Vector from '../../math/vector' +import Counters from '../counters' +import {findOutline, segmentsToPaths} from '../workbench' +import {Matrix3, AXIS} from '../../math/l3space' +import {arrFlatten1L, isCurveClass} from '../cad-utils' +import DPR from '../../utils/dpr' + +export class SceneSolid { + + constructor(type, id) { + this.tCadType = type || 'SOLID'; + + this.cadGroup = new THREE.Object3D(); + this.cadGroup.__tcad_solid = this; + + this.tCadId = Counters.solid ++; + this.id = id === undefined ? this.tCadId : id; // to keep identity through the history + this.faceCounter = 0; + + this.wireframeGroup = new THREE.Object3D(); + this.cadGroup.add(this.wireframeGroup); + this.mergeable = true; + this.sceneFaces = []; + + this.material = createSolidMaterial(); + } + + addLineToScene(a, b) { + var lg = new THREE.Geometry(); + lg.vertices.push(a); + lg.vertices.push(b); + var line = new THREE.Line(lg, WIREFRAME_MATERIAL); + this.wireframeGroup.add(line); + } + + createGeometry() { + throw 'not implemented'; + } + + dropGeometry() { + throw 'not implemented'; + } + + vanish() { + throw 'not implemented'; + } +} + +function createSolidMaterial() { + return new THREE.MeshPhongMaterial({ + vertexColors: THREE.FaceColors, + color: 0xB0C4DE, + shininess: 0, + polygonOffset : true, + polygonOffsetFactor : 1, + polygonOffsetUnits : 2, + //side : THREE.DoubleSide + }); +} + +export class SceneFace { + constructor(solid, propagatedId) { + if (propagatedId === undefined) { + this.id = solid.tCadId + ":" + (solid.faceCounter++); + } else { + this.id = propagatedId; + } + + this.solid = solid; + this.meshFaces = []; + this.sketch3DGroup = null; + } + + normal() { + throw 'not implemented'; + } + + depth() { + throw 'not implemented'; + } + + getBounds() { + throw 'not implemented'; + } + + calcBasis() { + 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); + } + y = alignPlane.cross(normal); + x = y.cross(normal); + return [x, y, normal]; + }; + + basis() { + if (!this._basis) { + this._basis = this.calcBasis(); + } + return this._basis; + } + + createMeshFace(a, b, c) { + const face = new THREE.Face3(a, b, c); + this.meshFaces.push(face); + face.__TCAD_SceneFace = this; + return face; + } + + 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]); + } + } else { + this.sketch3DGroup = new THREE.Object3D(); + this.solid.cadGroup.add(this.sketch3DGroup); + } + + const basis = this.basis(); + 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]; + + 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); + } + 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()); + } + + } + 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); + } + } + + findById(sketchObjectId) { + return this.sketch3DGroup.children.find(o => o.__TCAD_SketchObject && o.__TCAD_SketchObject.id == sketchObjectId); + } + + getSketchObjectVerticesIn3D(sketchObjectId) { + const object = this.findById(sketchObjectId); + if (!object) { + return undefined; + } + return object.geometry.vertices; + } +} + +const SKETCH_MATERIAL = new THREE.LineBasicMaterial({color: 0xFFFFFF, linewidth: 3/DPR}); +const SKETCH_CONSTRUCTION_MATERIAL = new THREE.LineBasicMaterial({color: 0x777777, linewidth: 2/DPR}); +const WIREFRAME_MATERIAL = new THREE.LineBasicMaterial({color: 0x2B3856, linewidth: 3/DPR}); diff --git a/web/app/3d/selection.js b/web/app/3d/selection.js index d00e9a55..fc655f67 100644 --- a/web/app/3d/selection.js +++ b/web/app/3d/selection.js @@ -79,22 +79,22 @@ export class SelectionManager extends AbstractSelectionManager { this.planeSelection = []; } - select(sketchFace) { + select(sceneFace) { this.clear(); - if (sketchFace.curvedSurfaces !== null) { - for (var i = 0; i < sketchFace.curvedSurfaces.length; i++) { - var face = sketchFace.curvedSurfaces[i]; + if (!!sceneFace.curvedSurfaces) { + for (var i = 0; i < sceneFace.curvedSurfaces.length; i++) { + var face = sceneFace.curvedSurfaces[i]; this.selection.push(face); - setFacesColor(face.faces, this.readOnlyColor); + setFacesColor(face.meshFaces, this.readOnlyColor); } } else { - this.selection.push(sketchFace); - this.viewer.updateBasis(sketchFace.basis(), sketchFace.depth()); + this.selection.push(sceneFace); + this.viewer.updateBasis(sceneFace.basis(), sceneFace.depth()); this.viewer.showBasis(); - setFacesColor(sketchFace.faces, this.selectionColor); + setFacesColor(sceneFace.meshFaces, this.selectionColor); } - sketchFace.solid.mesh.geometry.colorsNeedUpdate = true; - this.viewer.bus.notify('selection', sketchFace); + sceneFace.solid.mesh.geometry.colorsNeedUpdate = true; + this.viewer.bus.notify('selection', sceneFace); this.viewer.render(); } @@ -104,41 +104,6 @@ export class SelectionManager extends AbstractSelectionManager { this.viewer.render(); } - clear() { - for (let selectee of this.selection) { - setFacesColor(selectee.faces, this.defaultColor); - selectee.solid.mesh.geometry.colorsNeedUpdate = true; - } - this.viewer.hideBasis(); - this.selection.length = 0; - } -} - -export class BREPFaceSelectionManager extends AbstractSelectionManager { - - constructor(viewer, selectionColor, readOnlyColor, defaultColor) { - super(viewer); - this.selectionColor = selectionColor; - this.defaultColor = defaultColor; - this.readOnlyColor = readOnlyColor; - } - - select(face) { - this.clear(); - this.selection.push(face); - setFacesColor(face.meshFaces, this.selectionColor); - - face.solid.mesh.geometry.colorsNeedUpdate = true; - this.viewer.bus.notify('selection', face); - this.viewer.render(); - } - - deselectAll() { - this.clear(); - this.viewer.bus.notify('selection', null); - this.viewer.render(); - } - clear() { for (let selectee of this.selection) { setFacesColor(selectee.meshFaces, this.defaultColor); @@ -147,7 +112,6 @@ export class BREPFaceSelectionManager extends AbstractSelectionManager { this.viewer.hideBasis(); this.selection.length = 0; } - } function setFacesColor(faces, color) { diff --git a/web/app/3d/solid.js b/web/app/3d/solid.js deleted file mode 100644 index 3b1ee291..00000000 --- a/web/app/3d/solid.js +++ /dev/null @@ -1,325 +0,0 @@ -import {HashTable} from '../utils/hashmap' -import Vector from '../math/vector' -import Counters from './counters' -import {findOutline, segmentsToPaths} from './workbench' -import {Matrix3, AXIS} from '../math/l3space' -import {arrFlatten1L, isCurveClass} from './cad-utils' -import DPR from '../utils/dpr' - -/** @constructor */ -export function Solid(csg, material, type, id) { - csg = csg.reTesselated().canonicalized(); - this.tCadType = type || 'SOLID'; - this.csg = csg; - - this.cadGroup = new THREE.Object3D(); - this.cadGroup.__tcad_solid = this; - - this.tCadId = Counters.solid ++; - this.id = id === undefined ? this.tCadId : id; // to keep identity through the history - this.faceCounter = 0; - - this.wireframeGroup = new THREE.Object3D(); - this.cadGroup.add(this.wireframeGroup); - this.mergeable = true; - this.material = material; - this.createGeometry(); -} - -Solid.prototype.createGeometry = function() { - const geometry = new THREE.Geometry(); - geometry.dynamic = true; - this.mesh = new THREE.Mesh(geometry, this.material); - this.cadGroup.add(this.mesh); - - this.polyFaces = []; - this.wires = HashTable.forEdge(); - this.curvedSurfaces = {}; - - this.setupGeometry(); -}; - -Solid.prototype.dropGeometry = function() { - this.cadGroup.remove( this.mesh ); - this.mesh.geometry.dispose(); - for(let i = this.wireframeGroup.children.length-1; i >=0 ; i--){ - this.wireframeGroup.remove(this.wireframeGroup.children[i]); - } -}; - -function groupCSG(csg) { - var csgPolygons = csg.toPolygons(); - var groups = {}; - for (var i = 0; i < csgPolygons.length; i++) { - var p = csgPolygons[i]; - var tag = p.shared.getTag(); - if (groups[tag] === undefined) { - groups[tag] = { - tag : tag, - polygons : [], - shared : p.shared, - plane : p.plane - }; - } - groups[tag].polygons.push(p); - } - return groups; -} - -Solid.prototype.setupGeometry = function() { - function threeV(v) {return new THREE.Vector3( v.x, v.y, v.z )} - - var off = 0; - var groups = groupCSG(this.csg); - var geom = this.mesh.geometry; - for (var gIdx in groups) { - var group = groups[gIdx]; - if (group.shared.__tcad === undefined) group.shared.__tcad = {}; - var polyFace = new SketchFace(this, group); - this.polyFaces.push(polyFace); - for (var p = 0; p < group.polygons.length; ++p) { - var poly = group.polygons[p]; - var vLength = poly.vertices.length; - if (vLength < 3) continue; - var firstVertex = poly.vertices[0]; - geom.vertices.push(threeV(firstVertex.pos)); - geom.vertices.push(threeV(poly.vertices[1].pos)); - var normal = threeV(poly.plane.normal); - for (var i = 2; i < vLength; i++) { - geom.vertices.push(threeV(poly.vertices[i].pos)); - - var a = off; - var b = i - 1 + off; - var c = i + off; - var face = new THREE.Face3(a, b, c); - polyFace.faces.push(face); - face.__TCAD_polyFace = polyFace; - face.normal = normal; - face.materialIndex = gIdx; - geom.faces.push(face); - //face.color.set(new THREE.Color().setRGB( Math.random(), Math.random(), Math.random())); - } - //view.setFaceColor(polyFace, utils.isSmoothPiece(group.shared) ? 0xFF0000 : null); - off = geom.vertices.length; - } - this.collectCurvedSurface(polyFace); - this.collectWires(polyFace, group.polygons); - } - - geom.mergeVertices(); - - this.processWires(); -}; - -Solid.prototype.vanish = function() { - this.cadGroup.parent.remove( this.cadGroup ); - this.material.dispose(); - this.mesh.geometry.dispose(); -}; - -Solid.prototype.collectCurvedSurface = function(face) { - var derivedFrom = getDerivedFrom(face.csgGroup.shared); - if (derivedFrom === null || !isCurveClass(derivedFrom._class)) return; - var surfaces = this.curvedSurfaces[derivedFrom.id]; - if (surfaces === undefined) { - surfaces = []; - this.curvedSurfaces[derivedFrom.id] = surfaces; - } - surfaces.push(face); - face.curvedSurfaces = surfaces; -}; - -Solid.prototype.collectWires = function(face, facePolygons) { - - function contains(planes, plane) { - for (var j = 0; j < planes.length; j++) { - if (planes[j].equals(plane)) { - return true; - } - } - return false; - } - - const outline = findOutline(facePolygons); - const paths = segmentsToPaths(outline); - - for (var i = 0; i < paths.length; i++) { - var path = paths[i]; - var p, q, n = path.vertices.length; - for (q = 0, p = n - 1; q < n; p = q++) { - var edge = [path.vertices[p], path.vertices[q]]; - var data = this.wires.get(edge); - - if (data === null) { - data = { - sharedPlanes : [face.csgGroup.plane], - sharedFaces : [face] - }; - this.wires.put(edge, data); - } else { - if (!contains(data.sharedPlanes, face.csgGroup.plane)) { - data.sharedPlanes.push(face.csgGroup.plane); - } - data.sharedFaces.push(face); - } - } - } -}; - -Solid.SMOOTH_LIMIT = 10 * Math.PI / 180; - -Solid.prototype.processWires = function() { - var solid = this; - this.wires.entries(function(edge, data) { - if (data.sharedPlanes.length > 1) { - var plane0 = data.sharedPlanes[0]; - var plane1 = data.sharedPlanes[1]; - var angle = Math.acos(plane0.normal.dot(plane1.normal)); - if (angle < Solid.SMOOTH_LIMIT) { - return; - } - } - for (var i = 0; i < data.sharedFaces.length; ++i) { - for (var j = i + 1; j < data.sharedFaces.length; ++j) { - var face0 = data.sharedFaces[0]; - var face1 = data.sharedFaces[1]; - if (sameID(getDerivedID(face0.csgGroup.shared), getDerivedID(face1.csgGroup.shared))) { - return; - } - } - } - - solid.addLineToScene(edge[0], edge[1]); - }); -}; - -Solid.prototype.addLineToScene = function(a, b) { - var lg = new THREE.Geometry(); - lg.vertices.push(a); - lg.vertices.push(b); - var line = new THREE.Line(lg, SketchFace.prototype.WIREFRAME_MATERIAL); - this.wireframeGroup.add(line); -}; - -/** @constructor */ -function SketchFace(solid, csgGroup) { - csgGroup.__face = this; - if (csgGroup.shared.__tcad.faceId === undefined) { - this.id = solid.tCadId + ":" + (solid.faceCounter++); - } else { - this.id = csgGroup.shared.__tcad.faceId; - } - csgGroup.shared.__tcad.faceId = this.id; - - this.solid = solid; - this.csgGroup = csgGroup; - this.faces = []; - this.sketch3DGroup = null; - this.curvedSurfaces = null; -} - -SketchFace.prototype.SKETCH_MATERIAL = new THREE.LineBasicMaterial({color: 0xFFFFFF, linewidth: 3/DPR}); -SketchFace.prototype.SKETCH_CONSTRUCTION_MATERIAL = new THREE.LineBasicMaterial({color: 0x777777, linewidth: 2/DPR}); -SketchFace.prototype.WIREFRAME_MATERIAL = new THREE.LineBasicMaterial({color: 0x2B3856, linewidth: 3/DPR}); - -SketchFace.prototype.calcBasis = function() { - var normal = new Vector().setV(this.csgGroup.plane.normal); - var alignPlane, x, y; - if (Math.abs(normal.dot(AXIS.Y)) < 0.5) { - alignPlane = normal.cross(AXIS.Y); - } else { - alignPlane = normal.cross(AXIS.Z); - } - y = alignPlane.cross(normal); - x = y.cross(normal); - return [x, y, normal]; -}; - -SketchFace.prototype.basis = function() { - if (!this._basis) { - this._basis = this.calcBasis(); - } - return this._basis; - //return someBasis(this.csgGroup.polygons[0].vertices.map(function (v) { - // return vec(v.pos) - //}), vec(this.csgGroup.plane.normal)); -}; - -SketchFace.prototype.depth = function() { - return this.csgGroup.plane.w; -}; - -SketchFace.prototype.syncSketches = function(geom) { - const normal = this.csgGroup.plane.normal; - const offVector = normal.scale(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]); - } - } else { - this.sketch3DGroup = new THREE.Object3D(); - this.solid.cadGroup.add(this.sketch3DGroup); - } - - const basis = this.basis(); - const _3dTransformation = new Matrix3().setBasis(basis); - //we lost depth or z off in 2d sketch, calculate it again - const depth = this.csgGroup.plane.w; - const polyLines = new Map(); - function addSketchConnections(connections, material) { - for (let i = 0; i < connections.length; ++i) { - const l = connections[i]; - - 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); - } - 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()); - } - - } - addSketchConnections(geom.constructionSegments, this.SKETCH_CONSTRUCTION_MATERIAL); - addSketchConnections(geom.connections, this.SKETCH_MATERIAL); - addSketchConnections(arrFlatten1L(geom.loops), this.SKETCH_MATERIAL); - - for (let line of polyLines.values()) { - this.sketch3DGroup.add(line); - } -}; - - -SketchFace.prototype.findById = function(sketchObjectId) { - return this.sketch3DGroup.children.find(o => o.__TCAD_SketchObject && o.__TCAD_SketchObject.id == sketchObjectId); -}; - -SketchFace.prototype.getSketchObjectVerticesIn3D = function(sketchObjectId) { - const object = this.findById(sketchObjectId); - if (!object) { - return undefined; - } - return object.geometry.vertices;; -}; - -function sameID(id1, id2) { - if (id1 === null || id2 === null) { - return false; - } - return id1 === id2; -} - -function getDerivedID(shared) { - return shared.__tcad && !!shared.__tcad.csgInfo && !!shared.__tcad.csgInfo.derivedFrom ? shared.__tcad.csgInfo.derivedFrom.id : null; -} - -function getDerivedFrom(shared) { - return shared.__tcad && !!shared.__tcad.csgInfo && !!shared.__tcad.csgInfo.derivedFrom ? shared.__tcad.csgInfo.derivedFrom : null; -} \ No newline at end of file diff --git a/web/app/3d/ui/ctrl.js b/web/app/3d/ui/ctrl.js index 02cb4b72..6b26932e 100644 --- a/web/app/3d/ui/ctrl.js +++ b/web/app/3d/ui/ctrl.js @@ -45,8 +45,8 @@ function UI(app) { this.app.bus.subscribe("showSketches", (enabled) => { var solids = app.findAllSolids(); for (var i = 0; i < solids.length; i++) { - for (var j = 0; j < solids[i].polyFaces.length; j++) { - var face = solids[i].polyFaces[j]; + for (var j = 0; j < solids[i].sceneFaces.length; j++) { + var face = solids[i].sceneFaces[j]; if (face.sketch3DGroup != null) face.sketch3DGroup.visible = enabled; } } diff --git a/web/app/3d/viewer.js b/web/app/3d/viewer.js index 5f9833bd..b9654a33 100644 --- a/web/app/3d/viewer.js +++ b/web/app/3d/viewer.js @@ -2,7 +2,7 @@ import * as cad_utils from './cad-utils' import {Matrix3, AXIS, ORIGIN} from '../math/l3space' import DPR from '../utils/dpr' import * as mask from '../utils/mask'; -import {SelectionManager, BREPFaceSelectionManager, SketchSelectionManager} from './selection' +import {SelectionManager, SketchSelectionManager} from './selection' function Viewer(bus, container) { this.bus = bus; @@ -94,7 +94,6 @@ function Viewer(bus, container) { this.scene.add(this.workGroup); this.createBasisGroup(); this.selectionMgr = new SelectionManager( this, 0xFAFAD2, 0xFF0000, null); - this.brepSelectionMgr = new BREPFaceSelectionManager( this, 0xFAFAD2, 0xFF0000, null); this.sketchSelectionMgr = new SketchSelectionManager( this, new THREE.LineBasicMaterial({color: 0xFF0000, linewidth: 6/DPR})); var viewer = this; @@ -238,17 +237,15 @@ export const PICK_KIND = { FACE: mask.type(1), SKETCH: mask.type(2), EDGE: mask.type(3), - VERTEX: mask.type(4), - TOPO_FACE: mask.type(5), - TOPO_EDGE: mask.type(6) + VERTEX: mask.type(4) }; Viewer.prototype.raycastObjects = function(event, kind, visitor) { let pickResults = this.raycast(event); for (let i = 0; i < pickResults.length; i++) { const pickResult = pickResults[i]; - if (mask.is(kind, PICK_KIND.FACE) && !!pickResult.face && pickResult.face.__TCAD_polyFace !== undefined) { - const sketchFace = pickResult.face.__TCAD_polyFace; + if (mask.is(kind, PICK_KIND.FACE) && !!pickResult.face && pickResult.face.__TCAD_SceneFace !== undefined) { + const sketchFace = pickResult.face.__TCAD_SceneFace; if (!visitor(sketchFace, PICK_KIND.FACE)) { break; } @@ -257,20 +254,12 @@ Viewer.prototype.raycastObjects = function(event, kind, visitor) { if (!visitor(pickResult.object, PICK_KIND.SKETCH)) { break; } - } else if (mask.is(kind, PICK_KIND.TOPO_FACE) && !!pickResult.face && pickResult.face.__TCAD_TOPO) { - if (!visitor(pickResult.face.__TCAD_TOPO, PICK_KIND.TOPO_FACE)) { - break; - } - } else if (mask.is(kind, PICK_KIND.TOPO_EDGE) && pickResult.object instanceof THREE.Line && pickResult.object.__TCAD_TOPO) { - if (!visitor(pickResult.object.__TCAD_TOPO, PICK_KIND.TOPO_EDGE)) { - break; - } } } }; Viewer.prototype.handlePick = function(event) { - this.raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.TOPO_FACE | PICK_KIND.TOPO_EDGE, (object, kind) => { + this.raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH, (object, kind) => { if (kind == PICK_KIND.FACE) { if (this.selectionMgr.pick(object)) { return false; @@ -279,10 +268,6 @@ Viewer.prototype.handlePick = function(event) { if (this.sketchSelectionMgr.pick(object)) { return false; } - } else if (kind == PICK_KIND.TOPO_FACE) { - this.brepSelectionMgr.select(object); - } else if (kind == PICK_KIND.TOPO_EDGE) { - //this.brepSelectionMgr.selectEdge(object); } return true; }); diff --git a/web/app/3d/workbench.js b/web/app/3d/workbench.js index b020becf..3579d797 100644 --- a/web/app/3d/workbench.js +++ b/web/app/3d/workbench.js @@ -395,7 +395,7 @@ export function reconstructSketchBounds(csg, face, strict) { pickUpCraftInfo(outline, outerEdges); - return segmentsToPaths(outline); + return segmentsToPaths(outline).map(poly => poly.vertices); } function pickUpCraftInfo(outline, outerEdges) { diff --git a/web/app/brep/topo/loop.js b/web/app/brep/topo/loop.js index 2ed9124a..e3b02ea4 100644 --- a/web/app/brep/topo/loop.js +++ b/web/app/brep/topo/loop.js @@ -1,6 +1,5 @@ import {TopoObject} from './topo-object' -import {Matrix3} from '../../math/l3space' import * as math from '../../math/math' export class Loop extends TopoObject { @@ -12,7 +11,7 @@ export class Loop extends TopoObject { } isCCW(surface) { - const tr = new Matrix3().setBasis(surface.calculateBasis()); + const tr = surface.get2DTransformation(); const polygon = this.asPolygon(); const polygon2d = polygon.map(p => tr.apply(p)); const lowestLeftIdx = math.findLowestLeftPoint(polygon2d);