|
|
@ -1,155 +1,21 @@
|
|
|
|
import { BufferGeometry, Vector3, Geometry, Line3, Face3, Float32BufferAttribute } from "three";
|
|
|
|
import { BufferGeometry, Face3, Float32BufferAttribute, Geometry, Line3, Triangle, Vector3 } from "three";
|
|
|
|
|
|
|
|
import { arraySortByNumber } from "../Common/ArrayExt";
|
|
|
|
//ref: https://github.com/mrdoob/js/issues/10517
|
|
|
|
//ref: https://github.com/mrdoob/js/issues/10517
|
|
|
|
|
|
|
|
|
|
|
|
export class OutlinesGeometry extends BufferGeometry
|
|
|
|
const keys = ['a', 'b', 'c'];
|
|
|
|
{
|
|
|
|
|
|
|
|
constructor(geometry, thresholdAngle: number)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
super();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thresholdAngle = (thresholdAngle !== undefined) ? thresholdAngle : 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let thresholdDot = Math.cos(Math.PI / 180 * thresholdAngle);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let edge = [0, 0], hash = {}, i, j, l, face, key;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function sortFunction(a, b)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return a - b;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let keys = ['a', 'b', 'c'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let geometry2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (geometry.isBufferGeometry)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
geometry2 = new Geometry();
|
|
|
|
|
|
|
|
geometry2.fromBufferGeometry(geometry);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
geometry2 = geometry.clone();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
geometry2.mergeVertices();
|
|
|
|
|
|
|
|
geometry2.computeFaceNormals();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let vertices = geometry2.vertices;
|
|
|
|
|
|
|
|
let faces = geometry2.faces;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < faces.length; i++)
|
|
|
|
export class EdgesGeometry extends BufferGeometry
|
|
|
|
{
|
|
|
|
|
|
|
|
face = faces[i];
|
|
|
|
|
|
|
|
for (j = 0; j < 3; j++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
edge[0] = face[keys[j]];
|
|
|
|
|
|
|
|
edge[1] = face[keys[(j + 1) % 3]];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let line = new Line3(vertices[edge[0]], vertices[edge[1]]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// for each vertex checks if it lies in the edge
|
|
|
|
|
|
|
|
for (let e = vertices.length - 1; e >= 0; e--)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (e === edge[0] || e === edge[1]) continue;
|
|
|
|
|
|
|
|
let v = vertices[e];
|
|
|
|
|
|
|
|
let closestPoint = line.closestPointToPoint(v, true, new Vector3());
|
|
|
|
|
|
|
|
if ((new Line3(closestPoint, v)).distance() < 1e-5)
|
|
|
|
|
|
|
|
{ //1e-5
|
|
|
|
|
|
|
|
// mark the current face as splitted so that his cords won't be considered
|
|
|
|
|
|
|
|
face.splitted = true;
|
|
|
|
|
|
|
|
// Add two new faces, created splitting the face in two
|
|
|
|
|
|
|
|
faces.push(new Face3(
|
|
|
|
|
|
|
|
e, face[keys[(j + 2) % 3]], face[keys[(j) % 3]],
|
|
|
|
|
|
|
|
face.normal, face.color, face.materialIndex
|
|
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
faces.push(new Face3(
|
|
|
|
|
|
|
|
e, face[keys[(j + 2) % 3]], face[keys[(j + 1) % 3]],
|
|
|
|
|
|
|
|
face.normal, face.color, face.materialIndex
|
|
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (face.splitted) break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (i = faces.length - 1; i >= 0; i--)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
face = faces[i];
|
|
|
|
|
|
|
|
if (face.splitted) continue;
|
|
|
|
|
|
|
|
for (j = 0; j < 3; j++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
edge[0] = face[keys[j]];
|
|
|
|
|
|
|
|
edge[1] = face[keys[(j + 1) % 3]];
|
|
|
|
|
|
|
|
edge.sort(sortFunction);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
key = edge.toString();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (hash[key] === undefined)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
hash[key] = { vert1: edge[0], vert2: edge[1], face1: i, face2: undefined };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
hash[key].face2 = i;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let coords = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (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 !== undefined && faces[h.face1].normal.dot(faces[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));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class EdgesGeometry2 extends BufferGeometry
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
constructor(geometry, thresholdAngle: number)
|
|
|
|
constructor(geometry, thresholdAngle: number = 1)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
super();
|
|
|
|
super();
|
|
|
|
|
|
|
|
|
|
|
|
thresholdAngle = (thresholdAngle !== undefined) ? thresholdAngle : 1;
|
|
|
|
let geometry2: Geometry;
|
|
|
|
|
|
|
|
|
|
|
|
let thresholdDot = Math.cos(Math.PI / 180 * thresholdAngle);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let edge = [0, 0], hash = {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function sortFunction(a, b)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return a - b;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let keys = ['a', 'b', 'c'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let geometry2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (geometry.isBufferGeometry)
|
|
|
|
if (geometry.isBufferGeometry)
|
|
|
|
{
|
|
|
|
geometry2 = new Geometry().fromBufferGeometry(geometry);
|
|
|
|
geometry2 = new Geometry();
|
|
|
|
|
|
|
|
geometry2.fromBufferGeometry(geometry);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
|
|
|
geometry2 = geometry.clone();
|
|
|
|
geometry2 = geometry.clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
geometry2.mergeVertices();
|
|
|
|
geometry2.mergeVertices();
|
|
|
|
geometry2.computeFaceNormals();
|
|
|
|
geometry2.computeFaceNormals();
|
|
|
@ -157,75 +23,88 @@ export class EdgesGeometry2 extends BufferGeometry
|
|
|
|
let vertices = geometry2.vertices;
|
|
|
|
let vertices = geometry2.vertices;
|
|
|
|
let faces = geometry2.faces;
|
|
|
|
let faces = geometry2.faces;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let faceHash = new Set<string>();
|
|
|
|
for (let i = 0; i < faces.length; i++)
|
|
|
|
for (let i = 0; i < faces.length; i++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
if (i > 1000)//出错
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
let face = faces[i];
|
|
|
|
let face = faces[i];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//fix CSG produces duplicate faces
|
|
|
|
|
|
|
|
let faceStr = `${face.a},${face.b},${face.c}`;
|
|
|
|
|
|
|
|
if (faceHash.has(faceStr))
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
faceHash.add(faceStr);
|
|
|
|
|
|
|
|
|
|
|
|
for (let j = 0; j < 3; j++)
|
|
|
|
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]];
|
|
|
|
|
|
|
|
|
|
|
|
edge[0] = face[keys[j]];
|
|
|
|
let line = new Line3(vertices[e1], vertices[e2]);
|
|
|
|
edge[1] = face[keys[(j + 1) % 3]];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let line = new Line3(vertices[edge[0]], vertices[edge[1]]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//split triangle
|
|
|
|
for (let e = 0, l = vertices.length; e < l; e++)
|
|
|
|
for (let e = 0, l = vertices.length; e < l; e++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (e === edge[0] || e === edge[1]) continue;
|
|
|
|
if (e === e1 || e === e2 || e === e3)
|
|
|
|
let v = vertices[e];
|
|
|
|
continue;
|
|
|
|
let closestPoint = line.closestPointToPoint(v, true, new Vector3());
|
|
|
|
|
|
|
|
if (closestPoint.equals(vertices[edge[0]]) || closestPoint.equals(vertices[edge[0]])) continue;
|
|
|
|
let p = vertices[e];
|
|
|
|
if ((new Line3(closestPoint, v)).distance() < 1e-5)
|
|
|
|
let closestPoint = line.closestPointToPoint(p, true, new Vector3());
|
|
|
|
{ //1e-5
|
|
|
|
if (closestPoint.equals(vertices[e1]) || closestPoint.equals(vertices[e2]))
|
|
|
|
// mark the current face as splitted so that his cords won't be considered
|
|
|
|
continue;
|
|
|
|
face.splitted = true;
|
|
|
|
if (closestPoint.distanceTo(p) < 1e-5)
|
|
|
|
// split the face in two using the new point
|
|
|
|
{
|
|
|
|
faces.push(new Face3(
|
|
|
|
face["splitted"] = true;
|
|
|
|
e, face[keys[(j + 2) % 3]], face[keys[(j) % 3]],
|
|
|
|
let f1 = new Face3(e, e3, e1, face.normal);
|
|
|
|
face.normal, face.color, face.materialIndex
|
|
|
|
let f2 = new Face3(e, e3, e2, face.normal);
|
|
|
|
));
|
|
|
|
|
|
|
|
faces.push(new Face3(
|
|
|
|
if (FaceArea(f1, vertices) > 1
|
|
|
|
e, face[keys[(j + 2) % 3]], face[keys[(j + 1) % 3]],
|
|
|
|
&& FaceArea(f2, vertices) > 1)
|
|
|
|
face.normal, face.color, face.materialIndex
|
|
|
|
faces.push(f1, f2);
|
|
|
|
));
|
|
|
|
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (face.splitted) 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++)
|
|
|
|
for (let i = 0, l = faces.length; i < l; i++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
let face = faces[i];
|
|
|
|
let face = faces[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (face.splitted) continue;
|
|
|
|
if (face["splitted"]) continue;
|
|
|
|
|
|
|
|
|
|
|
|
for (let j = 0; j < 3; j++)
|
|
|
|
for (let j = 0; j < 3; j++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
edge[0] = face[keys[j]];
|
|
|
|
edge[0] = face[keys[j]];
|
|
|
|
edge[1] = face[keys[(j + 1) % 3]];
|
|
|
|
edge[1] = face[keys[(j + 1) % 3]];
|
|
|
|
edge.sort(sortFunction);
|
|
|
|
arraySortByNumber(edge);
|
|
|
|
|
|
|
|
|
|
|
|
let key = edge.toString();
|
|
|
|
let key = edge.toString();
|
|
|
|
|
|
|
|
|
|
|
|
if (hash[key] === undefined)
|
|
|
|
if (hash[key] === undefined)
|
|
|
|
{
|
|
|
|
hash[key] = { vert1: edge[0], vert2: edge[1], face1: face };
|
|
|
|
hash[key] = { vert1: edge[0], vert2: edge[1], face1: i, face2: undefined };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
else
|
|
|
|
{
|
|
|
|
hash[key].face2 = face;
|
|
|
|
hash[key].face2 = i;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let coords = [];
|
|
|
|
|
|
|
|
|
|
|
|
let coords: number[] = [];
|
|
|
|
|
|
|
|
let thresholdDot = Math.cos(Math.PI / 180 * thresholdAngle);
|
|
|
|
|
|
|
|
|
|
|
|
for (let key in hash)
|
|
|
|
for (let key in hash)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
let h = hash[key];
|
|
|
|
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.
|
|
|
|
// 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 !== undefined && faces[h.face1].normal.dot(faces[h.face2].normal) <= thresholdDot)
|
|
|
|
if (h.face2 && h.face1.normal.dot(h.face2.normal) <= thresholdDot)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
let vertex = vertices[h.vert1];
|
|
|
|
let vertex = vertices[h.vert1];
|
|
|
|
coords.push(vertex.x);
|
|
|
|
coords.push(vertex.x);
|
|
|
@ -241,3 +120,13 @@ export class EdgesGeometry2 extends BufferGeometry
|
|
|
|
this.addAttribute('position', new Float32BufferAttribute(coords, 3));
|
|
|
|
this.addAttribute('position', new Float32BufferAttribute(coords, 3));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
}
|
|
|
|