mirror of https://gitee.com/cf-fz/WebCAD.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
8.2 KiB
264 lines
8.2 KiB
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()
|
|
}
|