diff --git a/web/app/3d/modeler-app.js b/web/app/3d/modeler-app.js index 69b8cc2e..2d6a86cc 100644 --- a/web/app/3d/modeler-app.js +++ b/web/app/3d/modeler-app.js @@ -16,6 +16,9 @@ import {AddDebugSupport} from './debug' import {init as initSample} from './sample' import '../../css/app3d.less' +import * as BREPBuilder from '../brep/brep-builder' +import {SceneSolid} from '../brep/viz/scene-solid' + function App() { this.id = this.processHints(); this.bus = new Bus(); @@ -63,8 +66,23 @@ function App() { } app._refreshSketches(); }); + this.BREPTest(); } +App.prototype.BREPTest = function() { + + const box = BREPBuilder.createPrism([ + BREPBuilder.point(-250, -250, 250), + BREPBuilder.point(250, -250, 250), + BREPBuilder.point(250, 250, 250), + BREPBuilder.point(-250, 250, 250), + ], 500); + + const sceneSolid = new SceneSolid(box); + this.viewer.workGroup.add(sceneSolid.cadGroup); + this.viewer.render() +}; + App.prototype.processHints = function() { let id = window.location.hash.substring(1); if (!id) { diff --git a/web/app/brep/brep-builder.js b/web/app/brep/brep-builder.js new file mode 100644 index 00000000..9be57462 --- /dev/null +++ b/web/app/brep/brep-builder.js @@ -0,0 +1,99 @@ +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 {Plane} from './geom/impl/plane' +import {Point} from './geom/point' +import * as cad_utils from '../3d/cad-utils' + + +export function createPrism(basePoints, height) { + const normal = cad_utils.normalOfCCWSeq(basePoints); + const baseLoop = createPlaneLoop(basePoints.map(p => new Vertex(p))); + const baseFace = createPlaneFace(normal, baseLoop); + + const lidNormal = normal.multiply(-1); + const offVector = lidNormal.multiply(height); + const lidPoints = basePoints.map(p => p.plus(offVector)); + const lidLoop = createPlaneLoop(lidPoints.map(p => new Vertex(p))); + const lidFace = createPlaneFace(lidNormal, lidLoop); + + const shell = new Shell(); + + for (let i = 0; i < baseLoop.halfEdges.length; i++) { + const baseHalfEdge = baseLoop.halfEdges[i]; + const lidHalfEdge = lidLoop.halfEdges[i]; + const wallLoop = createPlaneLoop([baseHalfEdge.vertexA, baseHalfEdge.vertexB, lidHalfEdge.vertexB, lidHalfEdge.vertexA]); + + const baseEdge = new Edge(new Line()); + linkHalfEdges(baseEdge, baseHalfEdge, wallLoop.halfEdges[0]); + + const lidEdge = new Edge(new Line()); + linkHalfEdges(lidEdge, lidHalfEdge, wallLoop.halfEdges[2]); + + const wallNormal = cad_utils.normalOfCCWSeq([baseHalfEdge.vertexA.point, baseHalfEdge.vertexB.point, lidHalfEdge.vertexB.point]); + + const wallFace = createPlaneFace(wallNormal, wallLoop); + + shell.faces.push(wallFace); + } + + iterateSegments(shell.faces, (a, b) => { + linkHalfEdges(new Edge(new Line()), a.outerLoop.halfEdges[1], b.outerLoop.halfEdges[3]); + }); + shell.faces.push(baseFace, lidFace); + return shell; +} + +function createPlaneFace(normal, loop) { + const w = loop.halfEdges[0].vertexA.point.dot(normal); + const plane = new Plane(normal, w); + const face = new Face(plane); + face.outerLoop = loop; + return face; +} + + +export function linkHalfEdges(edge, halfEdge1, halfEdge2) { + halfEdge1.edge = edge; + halfEdge2.edge = edge; + edge.halfEdge1 = halfEdge1; + edge.halfEdge2 = halfEdge2; + halfEdge1.vertexA.edges.push(edge); + halfEdge1.vertexB.edges.push(edge); +} + +export function createPlaneLoop(vertices) { + + const loop = new Loop(); + + iterateSegments(vertices, (a, b) => { + const halfEdge = new HalfEdge(); + halfEdge.loop = loop; + halfEdge.vertexA = a; + halfEdge.vertexB = b; + loop.halfEdges.push(halfEdge); + }); + + iterateSegments(loop.halfEdges, (prev, next) => { + prev.next = next; + next.prev = prev; + }); + + return loop; +} + +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); + } +} + diff --git a/web/app/brep/geom/curve.js b/web/app/brep/geom/curve.js new file mode 100644 index 00000000..a39cfb17 --- /dev/null +++ b/web/app/brep/geom/curve.js @@ -0,0 +1,8 @@ + +export class Curve { + + constructor() { + + } + +} \ No newline at end of file diff --git a/web/app/brep/geom/impl/line.js b/web/app/brep/geom/impl/line.js new file mode 100644 index 00000000..c1438727 --- /dev/null +++ b/web/app/brep/geom/impl/line.js @@ -0,0 +1,9 @@ +import {Curve} from '../curve' + +export class Line extends Curve { + + constructor() { + super(); + } + +} \ No newline at end of file diff --git a/web/app/brep/geom/impl/plane.js b/web/app/brep/geom/impl/plane.js new file mode 100644 index 00000000..214131a6 --- /dev/null +++ b/web/app/brep/geom/impl/plane.js @@ -0,0 +1,24 @@ +import {Surface} from '../surface' +import {AXIS} from '../../../math/l3space' + +export class Plane extends Surface { + + constructor(normal, w) { + super(); + this.normal = normal; + this.w = w; + } + + 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); + } + y = alignPlane.cross(normal); + x = y.cross(normal); + return [x, y, normal]; + } +} \ No newline at end of file diff --git a/web/app/brep/geom/point.js b/web/app/brep/geom/point.js new file mode 100644 index 00000000..b05c851b --- /dev/null +++ b/web/app/brep/geom/point.js @@ -0,0 +1,3 @@ +import Vector from '../../math/vector' + +export const Point = Vector; \ No newline at end of file diff --git a/web/app/brep/geom/surface.js b/web/app/brep/geom/surface.js new file mode 100644 index 00000000..423675f6 --- /dev/null +++ b/web/app/brep/geom/surface.js @@ -0,0 +1,8 @@ + +export class Surface { + + constructor() { + + } + +} \ No newline at end of file diff --git a/web/app/brep/topo/Edge.js b/web/app/brep/topo/Edge.js new file mode 100644 index 00000000..cc4796ba --- /dev/null +++ b/web/app/brep/topo/Edge.js @@ -0,0 +1,26 @@ + +export class Edge { + + constructor(curve) { + this.curve = curve; + this.halfEdge1 = null; + this.halfEdge2 = null; + } +} + +export class HalfEdge { + + constructor() { + this.edge = null; + this.vertexA = null; + this.vertexB = null; + this.loop = null; + this.next = null; + this.prev = null; + } + + twin() { + return this.edge.halfEdge1 == this ? this.edge.halfEdge2 : this.edge.halfEdge1; + } + +} \ No newline at end of file diff --git a/web/app/brep/topo/face.js b/web/app/brep/topo/face.js new file mode 100644 index 00000000..db4a6079 --- /dev/null +++ b/web/app/brep/topo/face.js @@ -0,0 +1,10 @@ + +export class Face { + + constructor(surface) { + this.surface = surface; + this.outerLoop = null; + this.innerLoops = []; + } + +} \ No newline at end of file diff --git a/web/app/brep/topo/loop.js b/web/app/brep/topo/loop.js new file mode 100644 index 00000000..8ae71af1 --- /dev/null +++ b/web/app/brep/topo/loop.js @@ -0,0 +1,12 @@ + +export class Loop { + + constructor() { + this.face = null; + this.halfEdges = []; + } + + asPolygon() { + return this.halfEdges.map(e => e.vertexA.point); + } +} \ No newline at end of file diff --git a/web/app/brep/topo/shell.js b/web/app/brep/topo/shell.js new file mode 100644 index 00000000..72fceda5 --- /dev/null +++ b/web/app/brep/topo/shell.js @@ -0,0 +1,8 @@ + +export class Shell { + + constructor() { + this.faces = [] + } + +} \ No newline at end of file diff --git a/web/app/brep/topo/vertex.js b/web/app/brep/topo/vertex.js new file mode 100644 index 00000000..b1dda347 --- /dev/null +++ b/web/app/brep/topo/vertex.js @@ -0,0 +1,9 @@ + +export class Vertex { + + constructor(point) { + this.point = point; + this.edges = []; + } + +} \ No newline at end of file diff --git a/web/app/brep/viz/scene-solid.js b/web/app/brep/viz/scene-solid.js new file mode 100644 index 00000000..110919fe --- /dev/null +++ b/web/app/brep/viz/scene-solid.js @@ -0,0 +1,135 @@ +import Vector from '../../math/vector' +import {Triangulate} from '../../3d/triangulation' + +export class SceneSolid { + + constructor(shell) { + this.shell = shell; + + this.cadGroup = new THREE.Object3D(); + this.cadGroup.__tcad_solid = this; + this.wireframeGroup = new THREE.Object3D(); + this.cadGroup.add(this.wireframeGroup); + + const geometry = new THREE.Geometry(); + geometry.dynamic = true; + this.mesh = new THREE.Mesh(geometry, createSolidMaterial()); + this.cadGroup.add(this.mesh); + + this.polyFaces = []; + this.createFaces(); + this.createEdges(); + this.createVertices(); + } + + createFaces() { + let off = 0; + let gIdx = 0; + const geom = this.mesh.geometry; + for (let brepFace of this.shell.faces) { + const polyFace = new SceneFace(brepFace); + this.polyFaces.push(polyFace); + const polygons = triangulate(brepFace); + for (let p = 0; p < polygons.length; ++p) { + const poly = polygons[p]; + const vLength = poly.length; + if (vLength < 3) continue; + const firstVertex = poly[0]; + geom.vertices.push(threeV(firstVertex)); + geom.vertices.push(threeV(poly[1])); + 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); + 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; + } + } + geom.mergeVertices(); + } + + createEdges() { + const visited = new Set(); + for (let face of this.shell.faces) { + for (let halfEdge of face.outerLoop.halfEdges) { + if (!visited.has(halfEdge.edge)) { + visited.add(halfEdge.edge); + this.addLineToScene(halfEdge.vertexA.point, halfEdge.vertexB.point, 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 { + constructor(brepFace) { + this.brepFace = brepFace; + this.faces = []; + } +} + +function triangulate(face) { + function csgVert(data) { + return new Vector(data[0], data[1], data[2]); + } + function data(v) { + return [v.x, v.y, v.z]; + } + + var triangled = []; + const polygons = [face.outerLoop.asPolygon()]; + for (let poly of polygons) { + let vertices = Triangulate([poly.map(v => data(v))], data(face.surface.normal)); + for (let i = 0; i < vertices.length; i += 3 ) { + var a = csgVert(vertices[i]); + var b = csgVert(vertices[i + 1]); + var c = csgVert(vertices[i + 2]); + triangled.push([a, b, c]); + } + } + return triangled; + +} + + +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 )}