web-ifc-viewer
Version:
405 lines • 16.7 kB
JavaScript
/* eslint-disable */
import { Vector3, Triangle, Line3, MathUtils, Plane, BufferGeometry, BufferAttribute } from 'three';
import { ExtendedTriangle } from 'three-mesh-bvh';
const _upVector = new Vector3(0, 1, 0);
const EPSILON = 1e-16;
// Modified version of js EdgesGeometry logic to handle silhouette edges
export const generateEdges = (function () {
const _v0 = new Vector3();
const _v1 = new Vector3();
const _normal = new Vector3();
const _triangle = new Triangle();
return function generateEdges(geometry, projectionDir, thresholdAngle = 1) {
const edges = [];
const precisionPoints = 4;
const precision = Math.pow(10, precisionPoints);
const thresholdDot = Math.cos(MathUtils.DEG2RAD * thresholdAngle);
const indexAttr = geometry.getIndex();
const positionAttr = geometry.getAttribute('position');
const indexCount = indexAttr ? indexAttr.count : positionAttr.count;
const indexArr = [0, 0, 0];
const vertKeys = ['a', 'b', 'c'];
const hashes = new Array(3);
const edgeData = {};
for (let i = 0; i < indexCount; i += 3) {
if (indexAttr) {
indexArr[0] = indexAttr.getX(i);
indexArr[1] = indexAttr.getX(i + 1);
indexArr[2] = indexAttr.getX(i + 2);
}
else {
indexArr[0] = i;
indexArr[1] = i + 1;
indexArr[2] = i + 2;
}
const { a, b, c } = _triangle;
a.fromBufferAttribute(positionAttr, indexArr[0]);
b.fromBufferAttribute(positionAttr, indexArr[1]);
c.fromBufferAttribute(positionAttr, indexArr[2]);
_triangle.getNormal(_normal);
// create hashes for the edge from the vertices
hashes[0] = `${Math.round(a.x * precision)},${Math.round(a.y * precision)},${Math.round(a.z * precision)}`;
hashes[1] = `${Math.round(b.x * precision)},${Math.round(b.y * precision)},${Math.round(b.z * precision)}`;
hashes[2] = `${Math.round(c.x * precision)},${Math.round(c.y * precision)},${Math.round(c.z * precision)}`;
// skip degenerate triangles
if (hashes[0] === hashes[1] || hashes[1] === hashes[2] || hashes[2] === hashes[0]) {
continue;
}
// iterate over every edge
for (let j = 0; j < 3; j++) {
// get the first and next vertex making up the edge
const jNext = (j + 1) % 3;
const vecHash0 = hashes[j];
const vecHash1 = hashes[jNext];
const v0 = _triangle[vertKeys[j]];
const v1 = _triangle[vertKeys[jNext]];
const hash = `${vecHash0}_${vecHash1}`;
const reverseHash = `${vecHash1}_${vecHash0}`;
if (reverseHash in edgeData && edgeData[reverseHash]) {
// if we found a sibling edge add it into the vertex array if
// it meets the angle threshold and delete the edge from the map.
const otherNormal = edgeData[reverseHash].normal;
const meetsThreshold = _normal.dot(otherNormal) <= thresholdDot;
const projectionThreshold = Math.sign(projectionDir.dot(_normal)) !== Math.sign(projectionDir.dot(otherNormal));
if (meetsThreshold || projectionThreshold) {
const line = new Line3();
line.start.copy(v0);
line.end.copy(v1);
edges.push(line);
}
edgeData[reverseHash] = null;
}
else if (!(hash in edgeData)) {
// if we've already got an edge here then skip adding a new one
edgeData[hash] = {
index0: indexArr[j],
index1: indexArr[jNext],
normal: _normal.clone()
};
}
}
}
// iterate over all remaining, unmatched edges and add them to the vertex array
for (const key in edgeData) {
if (edgeData[key]) {
const { index0, index1 } = edgeData[key];
_v0.fromBufferAttribute(positionAttr, index0);
_v1.fromBufferAttribute(positionAttr, index1);
const line = new Line3();
line.start.copy(_v0);
line.end.copy(_v1);
edges.push(line);
}
}
return edges;
};
})();
// outputs the overlapping segment of a coplanar line and triangle
export const getOverlappingLine = (function () {
const _dir0 = new Vector3();
const _dir1 = new Vector3();
const _tempDir = new Vector3();
const _orthoPlane = new Plane();
const _line0 = new Line3();
const _line1 = new Line3();
const _tempLine = new Line3();
return function getOverlappingLine(line, triangle, lineTarget = new Line3()) {
if (triangle.needsUpdate) {
triangle.needsUpdate();
}
// if the triangle is degenerate then return no overlap
if (triangle.getArea() <= EPSILON) {
return null;
}
const { points, plane } = triangle;
_line0.copy(line);
_line0.delta(_dir0);
// if the line and triangle are not coplanar then return no overlap
const areCoplanar = plane.normal.dot(_dir0) === 0.0;
if (!areCoplanar) {
return null;
}
// a plane that's orthogonal to the triangle that the line lies on
_dir0.cross(plane.normal).normalize();
_orthoPlane.setFromNormalAndCoplanarPoint(_dir0, _line0.start);
// find the line of intersection of the triangle along the plane if it exists
let intersectCount = 0;
for (let i = 0; i < 3; i++) {
const p1 = points[i];
const p2 = points[(i + 1) % 3];
_tempLine.start.copy(p1);
_tempLine.end.copy(p2);
if (_orthoPlane.distanceToPoint(_tempLine.end) === 0 &&
_orthoPlane.distanceToPoint(_tempLine.start) === 0) {
// if the edge lies on the plane then take the line
_line1.copy(_tempLine);
intersectCount = 2;
break;
}
else if (_orthoPlane.intersectLine(_tempLine, intersectCount === 0 ? _line1.start : _line1.end)) {
let p;
if (intersectCount === 0) {
p = _line1.start;
}
else {
p = _line1.end;
}
if (p.distanceTo(p2) === 0.0) {
continue;
}
intersectCount++;
if (intersectCount === 2) {
break;
}
}
}
if (intersectCount === 2) {
// find the intersect line if any
_line0.delta(_dir0).normalize();
_line1.delta(_dir1).normalize();
// swap edges so they're facing in the same direction
if (_dir0.dot(_dir1) < 0) {
const tmp = _line1.start;
_line1.start = _line1.end;
_line1.end = tmp;
}
// check if the edges are overlapping
const s1 = _line0.start.dot(_dir0);
const e1 = _line0.end.dot(_dir0);
const s2 = _line1.start.dot(_dir0);
const e2 = _line1.end.dot(_dir0);
const separated1 = e1 < s2;
const separated2 = s1 < e2;
if (s1 !== e2 && s2 !== e1 && separated1 === separated2) {
return null;
}
// assign the target output
_tempDir.subVectors(_line0.start, _line1.start);
if (_tempDir.dot(_dir0) > 0) {
lineTarget.start.copy(_line0.start);
}
else {
lineTarget.start.copy(_line1.start);
}
_tempDir.subVectors(_line0.end, _line1.end);
if (_tempDir.dot(_dir0) < 0) {
lineTarget.end.copy(_line0.end);
}
else {
lineTarget.end.copy(_line1.end);
}
return lineTarget;
}
return null;
};
})();
// returns the the y value on the plane at the given point x, z
export const getPlaneYAtPoint = (function () {
const testLine = new Line3();
return function getPlaneYAtPoint(plane, point, target = null) {
testLine.start.copy(point);
testLine.end.copy(point);
testLine.start.y += 1e5;
testLine.end.y -= 1e5;
plane.intersectLine(testLine, target);
};
})();
// returns whether the given line is above the given triangle plane
export const isLineAbovePlane = (function () {
const _v0 = new Vector3();
const _v1 = new Vector3();
return function isLineAbovePlane(plane, line) {
_v0.lerpVectors(line.start, line.end, 0.5);
getPlaneYAtPoint(plane, _v0, _v1);
return _v1.y < _v0.y;
};
})();
export const isYProjectedLineDegenerate = (function () {
const _tempDir = new Vector3();
const _upVector = new Vector3(0, 1, 0);
return function isYProjectedLineDegenerate(line) {
line.delta(_tempDir).normalize();
return Math.abs(_tempDir.dot(_upVector)) >= 1.0 - EPSILON;
};
})();
// checks whether the y-projected triangle will be degerate
export function isYProjectedTriangleDegenerate(tri) {
if (tri.needsUpdate) {
tri.update();
}
return Math.abs(tri.plane.normal.dot(_upVector)) <= EPSILON;
}
// Is the provided line exactly an edge on the triangle
export function isLineTriangleEdge(tri, line) {
// if this is the same line as on the triangle
const triPoints = tri.points;
let matches = 0;
for (let i = 0; i < 3; i++) {
const { start, end } = line;
const tp = triPoints[i];
if (start.distanceToSquared(tp) <= EPSILON) {
matches++;
}
if (end.distanceToSquared(tp) <= EPSILON) {
matches++;
}
}
return matches >= 2;
}
// Extracts the normalized [0, 1] distances along the given line that overlaps with the provided triangle when
// projected along the y axis
export const getProjectedOverlaps = (function () {
const _target = new Line3();
const _tempDir = new Vector3();
const _tempVec0 = new Vector3();
const _tempVec1 = new Vector3();
const _line = new Line3();
const _tri = new ExtendedTriangle();
return function getProjectedOverlaps(tri, line, overlapsTarget) {
_line.copy(line);
_tri.copy(tri);
// flatten them to a common plane
_line.start.y = 0;
_line.end.y = 0;
_tri.a.y = 0;
_tri.b.y = 0;
_tri.c.y = 0;
_tri.needsUpdate = true;
_tri.update();
// if the line is meaningfully long and the we have an overlapping line then extract the
// distances along the original line to return
if (getOverlappingLine(_line, _tri, _target)) {
_line.delta(_tempDir);
_tempVec0.subVectors(_target.start, _line.start);
_tempVec1.subVectors(_target.end, _line.start);
let d0 = _tempVec0.length() / _tempDir.length();
let d1 = _tempVec1.length() / _tempDir.length();
d0 = Math.min(Math.max(d0, 0), 1);
d1 = Math.min(Math.max(d1, 0), 1);
if (!(Math.abs(d0 - d1) <= EPSILON)) {
overlapsTarget.push(new Float32Array([d0, d1]));
}
return true;
}
return false;
};
})();
// Trim the provided line to just the section below the given triangle plane
export const trimToBeneathTriPlane = (function () {
const _lineDirection = new Vector3();
const _planeHit = new Vector3();
const _centerPoint = new Vector3();
const _planePoint = new Vector3();
return function trimToBeneathTriPlane(tri, line, lineTarget) {
if (tri.needsUpdate) {
tri.update();
}
lineTarget.copy(line);
// handle vertical triangles
const { plane } = tri;
if (isYProjectedTriangleDegenerate(tri)) {
return false;
}
// if the line and plane are coplanar then return that we can't trim
line.delta(_lineDirection);
const areCoplanar = plane.normal.dot(_lineDirection) === 0.0;
if (areCoplanar) {
return false;
}
// if the line does intersect the plane then trim
const doesLineIntersect = plane.intersectLine(line, _planeHit);
if (doesLineIntersect) {
const { start, end } = lineTarget;
// test the line side with the largest segment extending beyond the plane
let testPoint;
let flipped = false;
if (start.distanceTo(_planeHit) > end.distanceTo(_planeHit)) {
testPoint = start;
}
else {
testPoint = end;
flipped = true;
}
// get the center point of the line segment and the plane hit
_centerPoint.lerpVectors(testPoint, _planeHit, 0.5);
getPlaneYAtPoint(tri.plane, _centerPoint, _planePoint);
// adjust the appropriate line point align with the plane hit point
if (_planePoint.y < _centerPoint.y) {
if (flipped)
end.copy(_planeHit);
else
start.copy(_planeHit);
}
else if (flipped)
start.copy(_planeHit);
else
end.copy(_planeHit);
return true;
}
return false;
};
})();
// Converts the given array of overlaps into line segments
export const overlapsToLines = (function () {
const newLine = new Line3();
return function overlapsToLines(line, overlaps, target = []) {
compressEdgeOverlaps(overlaps);
const invOverlaps = [[0, 1]];
for (let i = 0, l = overlaps.length; i < l; i++) {
const invOverlap = invOverlaps[i];
const overlap = overlaps[i];
invOverlap[1] = overlap[0];
invOverlaps.push(new Float32Array([overlap[1], 1]));
}
for (let i = 0, l = invOverlaps.length; i < l; i++) {
const { start, end } = line;
newLine.start.lerpVectors(start, end, invOverlaps[i][0]);
newLine.end.lerpVectors(start, end, invOverlaps[i][1]);
target.push(
// @ts-ignore
new Float32Array([
newLine.start.x,
newLine.start.y,
newLine.start.z,
newLine.end.x,
newLine.end.y,
newLine.end.z
]));
}
return target;
};
})();
// converts the given list of edges to a line segments geometry
export function edgesToGeometry(edges, y = null) {
const edgeArray = new Float32Array(edges.length * 6);
let c = 0;
for (let i = 0, l = edges.length; i < l; i++) {
const line = edges[i];
edgeArray[c++] = line[0];
edgeArray[c++] = y === null ? line[1] : y;
edgeArray[c++] = line[2];
edgeArray[c++] = line[3];
edgeArray[c++] = y === null ? line[4] : y;
edgeArray[c++] = line[5];
}
const edgeGeom = new BufferGeometry();
const edgeBuffer = new BufferAttribute(edgeArray, 3, true);
edgeGeom.setAttribute('position', edgeBuffer);
return edgeGeom;
}
// compresses the given edge overlaps into a minimal set of representative objects
export function compressEdgeOverlaps(overlaps) {
overlaps.sort((a, b) => {
return a[0] - b[0];
});
for (let i = 1; i < overlaps.length; i++) {
const overlap = overlaps[i];
const prevOverlap = overlaps[i - 1];
if (overlap[0] <= prevOverlap[1]) {
prevOverlap[1] = Math.max(prevOverlap[1], overlap[1]);
overlaps.splice(i, 1);
i--;
}
}
}
//# sourceMappingURL=edgeUtils.js.map