web-ifc-viewer
Version:
178 lines • 8.52 kB
JavaScript
import { CONTAINED, INTERSECTED, NOT_INTERSECTED } from 'three-mesh-bvh';
import { Line3, Vector3 } from 'three';
import { SelectionBoxMath } from './selection-window-math';
export class ShapeCaster {
constructor(toScreenSpaceMatrix, lassoSegments) {
this.toScreenSpaceMatrix = toScreenSpaceMatrix;
this.lassoSegments = lassoSegments;
this.boxPoints = new Array(8).fill(0).map(() => new Vector3());
this.boxLines = new Array(12).fill(0).map(() => new Line3());
this.perBoundsSegments = [];
this.math = new SelectionBoxMath();
this.selectModel = false;
this.useBoundsTree = true;
this.selectionMode = 'intersection';
}
shapeCast(mesh, indices) {
if (!mesh.geometry.boundsTree) {
throw new Error('Geometry must have BVH applied!');
}
mesh.geometry.boundsTree.shapecast({
intersectsBounds: (box, _isLeaf, _score, depth) => {
// Get the bounding box points
const { min, max } = box;
let index = 0;
let minY = Infinity;
let maxY = -Infinity;
let minX = Infinity;
for (let x = 0; x <= 1; x++) {
for (let y = 0; y <= 1; y++) {
for (let z = 0; z <= 1; z++) {
const v = this.boxPoints[index];
v.x = x === 0 ? min.x : max.x;
v.y = y === 0 ? min.y : max.y;
v.z = z === 0 ? min.z : max.z;
// @ts-ignore
v.w = 1;
v.applyMatrix4(this.toScreenSpaceMatrix);
index++;
if (v.y < minY)
minY = v.y;
if (v.y > maxY)
maxY = v.y;
if (v.x < minX)
minX = v.x;
}
}
}
// Find all the relevant segments here and cache them in the above array for
// subsequent child checks to use.
const parentSegments = this.perBoundsSegments[depth - 1] || this.lassoSegments;
const segmentsToCheck = this.perBoundsSegments[depth] || [];
segmentsToCheck.length = 0;
this.perBoundsSegments[depth] = segmentsToCheck;
for (let i = 0, l = parentSegments.length; i < l; i++) {
const line = parentSegments[i];
const sx = line.start.x;
const sy = line.start.y;
const ex = line.end.x;
const ey = line.end.y;
if (sx < minX && ex < minX)
continue;
const startAbove = sy > maxY;
const endAbove = ey > maxY;
if (startAbove && endAbove)
continue;
const startBelow = sy < minY;
const endBelow = ey < minY;
if (startBelow && endBelow)
continue;
segmentsToCheck.push(line);
}
if (segmentsToCheck.length === 0) {
return NOT_INTERSECTED;
}
// Get the screen space hull lines
const hull = this.math.getConvexHull(this.boxPoints);
if (!hull)
return NOT_INTERSECTED;
const lines = hull.map((p, i) => {
const nextP = hull[(i + 1) % hull.length];
const line = this.boxLines[i];
line.start.copy(p);
line.end.copy(nextP);
return line;
});
// If a lasso point is inside the hull then it's intersected and cannot be contained
if (this.math.pointRayCrossesSegments(segmentsToCheck[0].start, lines) % 2 === 1) {
return INTERSECTED;
}
// check if the screen space hull is in the lasso
let crossings = 0;
for (let i = 0, l = hull.length; i < l; i++) {
const v = hull[i];
const pCrossings = this.math.pointRayCrossesSegments(v, segmentsToCheck);
if (i === 0) {
crossings = pCrossings;
}
// if two points on the hull have different amounts of crossings then
// it can only be intersected
if (crossings !== pCrossings) {
return INTERSECTED;
}
}
// check if there are any intersections
for (let i = 0, l = lines.length; i < l; i++) {
const boxLine = lines[i];
for (let s = 0, ls = segmentsToCheck.length; s < ls; s++) {
if (this.math.lineCrossesLine(boxLine, segmentsToCheck[s])) {
return INTERSECTED;
}
}
}
return crossings % 2 === 0 ? NOT_INTERSECTED : CONTAINED;
},
intersectsTriangle: (tri, index, contained, depth) => {
const i3 = index * 3;
const a = i3;
const b = i3 + 1;
const c = i3 + 2;
// if the parent bounds were marked as contained
if (contained) {
indices.push(a, b, c);
return this.selectModel;
}
// check all the segments if using no bounds tree
const segmentsToCheck = this.useBoundsTree
? this.perBoundsSegments[depth]
: this.lassoSegments;
if (this.selectionMode === 'centroid') {
// get the center of the triangle
const centroid = tri.a
.add(tri.b)
.add(tri.c)
.multiplyScalar(1 / 3);
centroid.applyMatrix4(this.toScreenSpaceMatrix);
// counting the crossings
const crossings = this.math.pointRayCrossesSegments(centroid, segmentsToCheck);
if (crossings % 2 === 1) {
indices.push(a, b, c);
return this.selectModel;
}
}
else if (this.selectionMode === 'intersection') {
// get the projected vertices
const vertices = [tri.a, tri.b, tri.c];
for (let j = 0; j < 3; j++) {
const v = vertices[j];
v.applyMatrix4(this.toScreenSpaceMatrix);
const crossings = this.math.pointRayCrossesSegments(v, segmentsToCheck);
if (crossings % 2 === 1) {
indices.push(a, b, c);
return this.selectModel;
}
}
// get the lines for the triangle
const lines = [this.boxLines[0], this.boxLines[1], this.boxLines[2]];
lines[0].start.copy(tri.a);
lines[0].end.copy(tri.b);
lines[1].start.copy(tri.b);
lines[1].end.copy(tri.c);
lines[2].start.copy(tri.c);
lines[2].end.copy(tri.a);
for (let i = 0; i < 3; i++) {
const l = lines[i];
for (let s = 0, sl = segmentsToCheck.length; s < sl; s++) {
if (this.math.lineCrossesLine(l, segmentsToCheck[s])) {
indices.push(a, b, c);
return this.selectModel;
}
}
}
}
return false;
}
});
}
}
//# sourceMappingURL=shape-caster.js.map