import { Box3, BufferGeometry, Face3, Float32BufferAttribute, Geometry, Line3, Triangle, Vector3 } from "three"; import { arraySortByNumber } from "../Common/ArrayExt"; import { FixIndex, ToFixed } from "../Common/Utils"; import { CSG } from "../csg/core/CSG"; import { FuzzyCSGFactory } from "../csg/core/FuzzyFactory3d"; import { Polygon } from "../csg/core/math/Polygon3"; import { Vector3D } from "../csg/core/math/Vector3"; import { equalv3 } from "./GeUtils"; //ref: https://github.com/mrdoob/js/issues/10517 const keys = ['a', 'b', 'c']; export class EdgesGeometry extends BufferGeometry { /** * 在使用Extrude实体的时候,有可能导致面无限分裂,并且有可能造成丢线问题,使用FromCSG方法可解. */ FromGeometry(geometry, thresholdAngle: number = 1) { let geometry2: Geometry; if (geometry.isBufferGeometry) geometry2 = new Geometry().fromBufferGeometry(geometry); else geometry2 = geometry.clone(); geometry2.mergeVertices(); geometry2.computeFaceNormals(); let vertices = geometry2.vertices; let faces = geometry2.faces; let count = faces.length; for (let i = 0; i < faces.length; i++) { if (faces.length > count * 2) { console.warn("EdgeGeometry的分裂已经到达2倍!"); break; } let face = faces[i]; if (FaceArea(face, vertices) < 1e-5) continue; for (let j = 0; j < 3; j++) { let e1 = face[keys[j]]; let e2 = face[keys[(j + 1) % 3]]; let e3 = face[keys[(j + 2) % 3]]; let line = new Line3(vertices[e1], vertices[e2]); //split triangle for (let e = 0, l = vertices.length; e < l; e++) { if (e === e1 || e === e2 || e === e3) continue; let p = vertices[e]; let closestPoint = line.closestPointToPoint(p, true, new Vector3()); if (equalv3(closestPoint, vertices[e1], 1e-5) || equalv3(closestPoint, vertices[e2])) continue; if (equalv3(closestPoint, p, 1e-5)) { face["splitted"] = true; let f1 = new Face3(e, e3, e1, face.normal); let f2 = new Face3(e, e3, e2, face.normal); faces.push(f1, f2); break; } } if (face["splitted"]) break; } } let edge = [0, 0]; let hash: { [key: string]: { vert1: number, vert2: number, face1: Face3, face2?: Face3 } } = {}; for (let i = 0, l = faces.length; i < l; i++) { let face = faces[i]; if (face["splitted"]) continue; for (let j = 0; j < 3; j++) { edge[0] = face[keys[j]]; edge[1] = face[keys[(j + 1) % 3]]; arraySortByNumber(edge); let key = edge.toString(); if (hash[key] === undefined) hash[key] = { vert1: edge[0], vert2: edge[1], face1: face }; else hash[key].face2 = face; } } let coords: number[] = []; let thresholdDot = Math.cos(Math.PI / 180 * thresholdAngle); for (let key in hash) { let h = hash[key]; // An edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree. if (h.face2 && h.face1.normal.dot(h.face2.normal) <= thresholdDot) { let vertex = vertices[h.vert1]; coords.push(vertex.x); coords.push(vertex.y); coords.push(vertex.z); vertex = vertices[h.vert2]; coords.push(vertex.x); coords.push(vertex.y); coords.push(vertex.z); } } this.addAttribute('position', new Float32BufferAttribute(coords, 3)); return this; } /** * 解决原来算法性能低下并且结果不理想的问题 */ FromCSG(csg: CSG) { let fuzzyfactory = new FuzzyCSGFactory(); let polygonsPerPlane: { [key: number]: Polygon[] } = {}; for (let polygon of csg.polygons) { let plane = polygon.plane; plane = fuzzyfactory.getPlane(plane); let tag = plane.getTag(); if (!(tag in polygonsPerPlane)) polygonsPerPlane[tag] = [polygon]; else polygonsPerPlane[tag].push(polygon); } let coords: number[] = []; for (let key in polygonsPerPlane) { this.PolygonsOutline(polygonsPerPlane[key], coords); } this.addAttribute('position', new Float32BufferAttribute(coords, 3)); return this; } PolygonsOutline(polygons: Polygon[], coords: number[]) { let pts: Vector3[] = []; let fp = new FuzzPoint(); let record: { [key: string]: { p1: Vector3, p2: Vector3, count: number } } = {}; for (let polygon of polygons) { for (let i = 0, count = polygon.vertices.length; i < count; i++) { let p = polygon.vertices[i]; let np = fp.GetVector(p.pos); p.pos = np; if (!("_added_" in np)) { np["_added_"] = pts.length; pts.push(np); } } } for (let polygon of polygons) { for (let i = 0, count = polygon.vertices.length; i < count; i++) { let p1 = polygon.vertices[i].pos as Vector3; let p2 = polygon.vertices[FixIndex(i + 1, count)].pos; let delta = p2.clone().sub(p1); let lengthSq = delta.dot(delta); let splitPts: { param: number, pt: Vector3 }[] = []; let box = new Box3().setFromPoints([p1, p2]).expandByVector(new Vector3(1, 1, 1)); for (let p of pts) { if (p === p1 || p === p2) continue; if (!box.containsPoint(p)) continue; let delta2 = p.clone().sub(p1); let len2 = delta2.dot(delta); let t = len2 / lengthSq; if (t > 0 && t < 1) { let closestPoint = delta.clone().multiplyScalar(t).add(p1); if (equalv3(closestPoint, p, 1e-3)) splitPts.push({ param: t, pt: p }); } } splitPts.sort((p1, p2) => p1.param - p2.param); splitPts.push({ param: 1, pt: p2 }); let lastP = p1; for (let p of splitPts) { let tag1 = lastP["_added_"] as number; let tag2 = p.pt["_added_"] as number; let key: string; if (tag1 < tag2) key = `${tag1},${tag2}`; else key = `${tag2},${tag1}`; if (key in record) record[key].count++; else record[key] = { p1: lastP, p2: p.pt, count: 1 }; lastP = p.pt; } } } for (let key in record) { let d = record[key]; if (d.count === 1) coords.push(d.p1.x, d.p1.y, d.p1.z, d.p2.x, d.p2.y, d.p2.z); } } } class FuzzPoint { private map: { [key: string]: Vector3D } = {}; GetVector(v: Vector3D) { let key = `${ToFixed(v.x, 2)},${ToFixed(v.y, 2)},${ToFixed(v.z, 2)}`; if (key in this.map) return this.map[key]; else this.map[key] = v; return v; } } let triangle = new Triangle(); function FaceArea(f: Face3, pts: Vector3[]) { triangle.a = pts[f.a]; triangle.b = pts[f.b]; triangle.c = pts[f.c]; return triangle.getArea() }