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.
WebCAD/src/Geometry/EdgeGeometry.ts

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";
6 years ago
//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);
6 years ago
else
geometry2 = geometry.clone();
geometry2.mergeVertices();
geometry2.computeFaceNormals();
6 years ago
let vertices = geometry2.vertices;
let faces = geometry2.faces;
let count = faces.length;
6 years ago
for (let i = 0; i < faces.length; i++)
{
if (faces.length > count * 2)
{
console.warn("EdgeGeometry的分裂已经到达2倍!");
break;
}
6 years ago
let face = faces[i];
if (FaceArea(face, vertices) < 1e-5)
continue;
6 years ago
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
6 years ago
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 } } = {};
6 years ago
for (let i = 0, l = faces.length; i < l; i++)
{
6 years ago
let face = faces[i];
if (face["splitted"]) continue;
6 years ago
for (let j = 0; j < 3; j++)
{
edge[0] = face[keys[j]];
edge[1] = face[keys[(j + 1) % 3]];
arraySortByNumber(edge);
6 years ago
let key = edge.toString();
if (hash[key] === undefined)
hash[key] = { vert1: edge[0], vert2: edge[1], face1: face };
6 years ago
else
hash[key].face2 = face;
}
}
let coords: number[] = [];
let thresholdDot = Math.cos(Math.PI / 180 * thresholdAngle);
6 years ago
for (let key in hash)
{
6 years ago
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)
{
6 years ago
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);
}
}
6 years ago
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()
}