From 1e40bb624fbb60c12c0360e6fae06925e8360605 Mon Sep 17 00:00:00 2001 From: ChenX Date: Fri, 25 May 2018 11:09:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AE=9E=E4=BD=93=E5=B7=AE?= =?UTF-8?q?=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ThreeCSG.ts | 702 +++++++++++++++++++++++++++++++++++++++++++++ src/Utils.ts | 10 +- src/createBoard.ts | 40 ++- tsconfig.json | 2 + 4 files changed, 740 insertions(+), 14 deletions(-) create mode 100644 src/ThreeCSG.ts diff --git a/src/ThreeCSG.ts b/src/ThreeCSG.ts new file mode 100644 index 0000000..61b975f --- /dev/null +++ b/src/ThreeCSG.ts @@ -0,0 +1,702 @@ +import * as THREE from "three"; + +/*jshint esversion: 6 */ + +const EPSILON = 1e-5, + COPLANAR = 0, //共面 + FRONT = 1, //前 + BACK = 2, + SPANNING = 3; + +export class ThreeBSP +{ + tree: Node; + matrix: THREE.Matrix4; + Node: typeof Node; + Vertex: typeof Vertex; + Polygon: typeof Polygon; + constructor(geometry) + { + // Convert THREE.Geometry to ThreeBSP + let i, _length_i, + face, vertex, faceVertexUvs, uvs, + polygon, + polygons = [], + tree; + + this.Polygon = Polygon; + this.Vertex = Vertex; + this.Node = Node; + if (geometry instanceof THREE.Geometry) + { + this.matrix = new THREE.Matrix4(); + } else if (geometry instanceof THREE.Mesh) + { + // #todo: add hierarchy support + geometry.updateMatrix(); + this.matrix = geometry.matrix.clone(); + geometry = geometry.geometry; + } else if (geometry instanceof Node) + { + this.tree = geometry; + this.matrix = new THREE.Matrix4(); + return this; + } else + { + throw 'ThreeBSP: Given geometry is unsupported'; + } + + for (i = 0, _length_i = geometry.faces.length; i < _length_i; i++) + { + face = geometry.faces[i]; + faceVertexUvs = geometry.faceVertexUvs[0][i]; + polygon = new Polygon(); + + if (face instanceof THREE.Face3) + { + vertex = geometry.vertices[face.a]; + uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[0].x, faceVertexUvs[0].y) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[0], uvs); + vertex.applyMatrix4(this.matrix); + polygon.vertices.push(vertex); + + vertex = geometry.vertices[face.b]; + uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[1].x, faceVertexUvs[1].y) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[1], uvs); + vertex.applyMatrix4(this.matrix); + polygon.vertices.push(vertex); + + vertex = geometry.vertices[face.c]; + uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[2].x, faceVertexUvs[2].y) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[2], uvs); + vertex.applyMatrix4(this.matrix); + polygon.vertices.push(vertex); + } else if (typeof THREE.Face4) + { + vertex = geometry.vertices[face.a]; + uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[0].x, faceVertexUvs[0].y) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[0], uvs); + vertex.applyMatrix4(this.matrix); + polygon.vertices.push(vertex); + + vertex = geometry.vertices[face.b]; + uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[1].x, faceVertexUvs[1].y) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[1], uvs); + vertex.applyMatrix4(this.matrix); + polygon.vertices.push(vertex); + + vertex = geometry.vertices[face.c]; + uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[2].x, faceVertexUvs[2].y) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[2], uvs); + vertex.applyMatrix4(this.matrix); + polygon.vertices.push(vertex); + + vertex = geometry.vertices[face.d]; + uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[3].x, faceVertexUvs[3].y) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[3], uvs); + vertex.applyMatrix4(this.matrix); + polygon.vertices.push(vertex); + } else + { + throw 'Invalid face type at index ' + i; + } + + polygon.calculateProperties(); + polygons.push(polygon); + } + + this.tree = new Node(polygons); + } + + //减 + subtract(other_tree) + { + let a = this.tree.clone(), + b = other_tree.tree.clone(); + + a.invert(); + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.allPolygons()); + a.invert(); + let bsp = new ThreeBSP(a); + bsp.matrix = this.matrix; + return bsp; + } + + //结合 + union(other_tree) + { + let a = this.tree.clone(), + b = other_tree.tree.clone(); + + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.allPolygons()); + let bsp = new ThreeBSP(a); + bsp.matrix = this.matrix; + return bsp; + } + + //相交 + intersect(other_tree) + { + let a = this.tree.clone(), + b = other_tree.tree.clone(); + + a.invert(); + b.clipTo(a); + b.invert(); + a.clipTo(b); + b.clipTo(a); + a.build(b.allPolygons()); + a.invert(); + let bsp = new ThreeBSP(a); + bsp.matrix = this.matrix; + return bsp; + } + + toGeometry() + { + let i, j, + matrix = new THREE.Matrix4().getInverse(this.matrix), + geometry = new THREE.Geometry(), + polygons = this.tree.allPolygons(), + polygon_count = polygons.length, + polygon, polygon_vertice_count, + vertice_dict = {}, + vertex_idx_a, vertex_idx_b, vertex_idx_c, + vertex, face, + verticeUvs; + + for (i = 0; i < polygon_count; i++) + { + polygon = polygons[i]; + polygon_vertice_count = polygon.vertices.length; + + for (j = 2; j < polygon_vertice_count; j++) + { + verticeUvs = []; + + vertex = polygon.vertices[0]; + verticeUvs.push(new THREE.Vector2(vertex.uv.x, vertex.uv.y)); + vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); + vertex.applyMatrix4(matrix); + + if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') + { + vertex_idx_a = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; + } else + { + geometry.vertices.push(vertex); + vertex_idx_a = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; + } + + vertex = polygon.vertices[j - 1]; + verticeUvs.push(new THREE.Vector2(vertex.uv.x, vertex.uv.y)); + vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); + vertex.applyMatrix4(matrix); + if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') + { + vertex_idx_b = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; + } else + { + geometry.vertices.push(vertex); + vertex_idx_b = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; + } + + vertex = polygon.vertices[j]; + verticeUvs.push(new THREE.Vector2(vertex.uv.x, vertex.uv.y)); + vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); + vertex.applyMatrix4(matrix); + if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') + { + vertex_idx_c = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; + } else + { + geometry.vertices.push(vertex); + vertex_idx_c = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; + } + + face = new THREE.Face3( + vertex_idx_a, + vertex_idx_b, + vertex_idx_c, + new THREE.Vector3(polygon.normal.x, polygon.normal.y, polygon.normal.z) + ); + + geometry.faces.push(face); + geometry.faceVertexUvs[0].push(verticeUvs); + } + + } + return geometry; + } + + toMesh(material) + { + let geometry = this.toGeometry(), + mesh = new THREE.Mesh(geometry, material); + + mesh.position.setFromMatrixPosition(this.matrix); + mesh.rotation.setFromRotationMatrix(this.matrix); + + return mesh; + } +} + +//多边形 +export class Polygon +{ + w: any; + normal: any; + vertices: any; + constructor(vertices?, normal?, w?) + { + if (!(vertices instanceof Array)) + { + vertices = []; + } + + this.vertices = vertices; + if (vertices.length > 0) + { + this.calculateProperties(); + } else + { + this.normal = this.w = undefined; + } + } + + calculateProperties() + { + let a = this.vertices[0], + b = this.vertices[1], + c = this.vertices[2]; + + this.normal = b.clone().subtract(a).cross( + c.clone().subtract(a) + ).normalize(); + + this.w = this.normal.clone().dot(a); + + return this; + } + + clone() + { + let i, vertice_count, + polygon = new Polygon(); + + for (i = 0, vertice_count = this.vertices.length; i < vertice_count; i++) + { + polygon.vertices.push(this.vertices[i].clone()); + } + polygon.calculateProperties(); + + return polygon; + } + + flip() + { + let i, vertices = []; + + this.normal.multiplyScalar(-1); + this.w *= -1; + + for (i = this.vertices.length - 1; i >= 0; i--) + { + vertices.push(this.vertices[i]); + } + this.vertices = vertices; + + return this; + } + + //划分? + classifyVertex(vertex) + { + let side_value = this.normal.dot(vertex) - this.w; + + if (side_value < -EPSILON) + { + return BACK; + } else if (side_value > EPSILON) + { + return FRONT; + } else + { + return COPLANAR; + } + } + + //划分边? + classifySide(polygon) + { + let i, vertex, classification, + num_positive = 0, + num_negative = 0, + vertice_count = polygon.vertices.length; + + for (i = 0; i < vertice_count; i++) + { + vertex = polygon.vertices[i]; + classification = this.classifyVertex(vertex); + if (classification === FRONT) + { + num_positive++; + } else if (classification === BACK) + { + num_negative++; + } + } + + if (num_positive > 0 && num_negative === 0) + { + return FRONT; + } else if (num_positive === 0 && num_negative > 0) + { + return BACK; + } else if (num_positive === 0 && num_negative === 0) + { + return COPLANAR; + } else + { + return SPANNING; + } + } + + //分解 分离 区域? + splitPolygon(polygon, coplanar_front, coplanar_back, front, back) + { + let classification = this.classifySide(polygon); + + if (classification === COPLANAR) + { + + (this.normal.dot(polygon.normal) > 0 ? coplanar_front : coplanar_back).push(polygon); + + } else if (classification === FRONT) + { + + front.push(polygon); + + } else if (classification === BACK) + { + + back.push(polygon); + + } else + { + + let vertice_count, + i, j, ti, tj, vi, vj, + t, v, + f = [], + b = []; + + for (i = 0, vertice_count = polygon.vertices.length; i < vertice_count; i++) + { + + j = (i + 1) % vertice_count; + vi = polygon.vertices[i]; + vj = polygon.vertices[j]; + ti = this.classifyVertex(vi); + tj = this.classifyVertex(vj); + + if (ti != BACK) f.push(vi); + if (ti != FRONT) b.push(vi); + if ((ti | tj) === SPANNING) + { + t = (this.w - this.normal.dot(vi)) / this.normal.dot(vj.clone().subtract(vi)); + v = vi.interpolate(vj, t); + f.push(v); + b.push(v); + } + } + + + if (f.length >= 3) front.push(new Polygon(f).calculateProperties()); + if (b.length >= 3) back.push(new Polygon(b).calculateProperties()); + } + } +} + +export class Vertex +{ + uv: any; + normal: any; + z: any; + y: any; + x: any; + constructor(x: number, y: number, z: number, normal: THREE.Vector3, uv: THREE.Vector2) + { + this.x = x; + this.y = y; + this.z = z; + this.normal = normal || new THREE.Vector3(); + this.uv = uv || new THREE.Vector2(); + } + + clone() + { + return new Vertex(this.x, this.y, this.z, this.normal.clone(), this.uv.clone()); + } + + add(vertex) + { + this.x += vertex.x; + this.y += vertex.y; + this.z += vertex.z; + return this; + } + + subtract(vertex) + { + this.x -= vertex.x; + this.y -= vertex.y; + this.z -= vertex.z; + return this; + } + + multiplyScalar(scalar) + { + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + return this; + } + + //×乘 + cross(vertex) + { + let x = this.x, + y = this.y, + z = this.z; + + this.x = y * vertex.z - z * vertex.y; + this.y = z * vertex.x - x * vertex.z; + this.z = x * vertex.y - y * vertex.x; + + return this; + } + + normalize() + { + let length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + + this.x /= length; + this.y /= length; + this.z /= length; + + return this; + } + + //点乘 + dot(vertex) + { + return this.x * vertex.x + this.y * vertex.y + this.z * vertex.z; + } + + //线性插值 + lerp(a, t) + { + this.add( + a.clone().subtract(this).multiplyScalar(t) + ); + + this.normal.add( + a.normal.clone().sub(this.normal).multiplyScalar(t) + ); + + this.uv.add( + a.uv.clone().sub(this.uv).multiplyScalar(t) + ); + + return this; + } + + //插值 + interpolate(other, t) + { + return this.clone().lerp(other, t); + } + + applyMatrix4(m) + { + + // input: THREE.Matrix4 affine matrix + + let x = this.x, y = this.y, z = this.z; + + let e = m.elements; + + this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; + this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; + this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; + + return this; + + } +} +export class Node +{ + divider: any; + back: any; + front: any; + polygons: any[]; + constructor(polygons?) + { + let i, polygon_count, + front = [], + back = []; + + this.polygons = []; + this.front = this.back = undefined; + + if (!(polygons instanceof Array) || polygons.length === 0) return; + + this.divider = polygons[0].clone(); + + for (i = 0, polygon_count = polygons.length; i < polygon_count; i++) + { + this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); + } + + if (front.length > 0) + { + this.front = new Node(front); + } + + if (back.length > 0) + { + this.back = new Node(back); + } + } + + //是凸的? 凸包? + isConvex(polygons) + { + let i, j; + for (i = 0; i < polygons.length; i++) + { + for (j = 0; j < polygons.length; j++) + { + if (i !== j && polygons[i].classifySide(polygons[j]) !== BACK) + { + return false; + } + } + } + return true; + } + + build(polygons) + { + let i, polygon_count, + front = [], + back = []; + + if (!this.divider) + { + this.divider = polygons[0].clone(); + } + + for (i = 0, polygon_count = polygons.length; i < polygon_count; i++) + { + this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); + } + + if (front.length > 0) + { + if (!this.front) this.front = new Node(); + this.front.build(front); + } + + if (back.length > 0) + { + if (!this.back) this.back = new Node(); + this.back.build(back); + } + } + + allPolygons() + { + let polygons = this.polygons.slice(); + if (this.front) polygons = polygons.concat(this.front.allPolygons()); + if (this.back) polygons = polygons.concat(this.back.allPolygons()); + return polygons; + } + + clone() + { + let node = new Node(); + + node.divider = this.divider.clone(); + node.polygons = this.polygons.map(function (polygon) + { + return polygon.clone(); + }); + node.front = this.front && this.front.clone(); + node.back = this.back && this.back.clone(); + + return node; + } + + //反转 + invert() + { + let i, polygon_count, temp; + + for (i = 0, polygon_count = this.polygons.length; i < polygon_count; i++) + { + this.polygons[i].flip(); + } + + this.divider.flip(); + if (this.front) this.front.invert(); + if (this.back) this.back.invert(); + + temp = this.front; + this.front = this.back; + this.back = temp; + + return this; + } + + // + clipPolygons(polygons) + { + let i, polygon_count, + front, back; + + if (!this.divider) return polygons.slice(); + + front = []; + back = []; + + for (i = 0, polygon_count = polygons.length; i < polygon_count; i++) + { + this.divider.splitPolygon(polygons[i], front, back, front, back); + } + + if (this.front) front = this.front.clipPolygons(front); + if (this.back) back = this.back.clipPolygons(back); + else back = []; + + return front.concat(back); + } + + clipTo(node) + { + this.polygons = node.clipPolygons(this.polygons); + if (this.front) this.front.clipTo(node); + if (this.back) this.back.clipTo(node); + } +} diff --git a/src/Utils.ts b/src/Utils.ts index ff1788c..bb0dd09 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -11,14 +11,12 @@ export function LoadBoard(view: Viewer, data: any[]) view.m_Scene.children.length = 0; //加板 - let brList = createTemplateBoard(data); - //线框 - let edgeList = brList.map(br => createEdge(br)); + let { meshs, edgesa } = createTemplateBoard(data); //加标注 - let dims = DrawDimension(brList); + let dims = DrawDimension(meshs); - view.m_Scene.add(...brList); - view.m_Scene.add(...edgeList); + view.m_Scene.add(...meshs); + view.m_Scene.add(...edgesa); view.m_Scene.add(...dims); view.ViewToSwiso(); diff --git a/src/createBoard.ts b/src/createBoard.ts index 0200291..6c4c79b 100644 --- a/src/createBoard.ts +++ b/src/createBoard.ts @@ -1,8 +1,9 @@ import * as THREE from 'three'; -import { LineSegments, Mesh } from 'three'; +import { Geometry, LineSegments } from 'three'; import { polar } from './GeUtils'; import { boardMaterial, edgeMaterial } from './Material'; import { RotateUVs } from './RotateUV'; +import { ThreeBSP } from './ThreeCSG'; //解析二维圆弧类. export class Arc2d { @@ -115,28 +116,51 @@ export function createBoard(boardData: object) amount: boardHeight }; - let ext = new THREE.ExtrudeGeometry(sp, extrudeSettings); + let ext = new THREE.ExtrudeGeometry(sp, extrudeSettings) as Geometry; ext.computeBoundingSphere(); ext.computeBoundingBox(); ext.translate(0, 0, -boardHeight) ext.applyMatrix(boardMat); - if (boardData["BoardName"] === "地脚线") + //外边. + let edges = [createEdge(ext)]; + //差集 + if (boardData["SubBoardLocal"].length > 0) { - RotateUVs(ext); + let subBoardList = boardData["SubBoardLocal"].map(d => createBoard(d)); + let thisCsg = new ThreeBSP(ext); + + for (let br of subBoardList) + { + edges.push(...br.edges); + let subCsg = new ThreeBSP(br.mesh); + thisCsg = thisCsg.subtract(subCsg); + } + ext = thisCsg.toGeometry(); } + if (boardData["BoardName"] === "地脚线") + RotateUVs(ext); + let mesh = new THREE.Mesh(ext, boardMaterial); - return mesh; + return { mesh, edges }; } export function createTemplateBoard(brDataList: any[]) { - return brDataList.map(brData => createBoard(brData)); + let meshs = []; + let edgesa = []; + for (let d of brDataList) + { + let { mesh, edges } = createBoard(d); + meshs.push(mesh); + edgesa.push(...edges); + } + return { meshs, edgesa }; } -export function createEdge(board: Mesh): LineSegments +export function createEdge(geo: Geometry): LineSegments { - let edge = new THREE.EdgesGeometry(board.geometry, 1); + let edge = new THREE.EdgesGeometry(geo, 1); return new LineSegments(edge, edgeMaterial); } diff --git a/tsconfig.json b/tsconfig.json index 6425527..c0cd936 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,7 @@ { + "compileOnSave": true, "compilerOptions": { + "sourceMap": true, "declaration": true, "outDir": "./dist", "target": "esnext",