EmboDi / src /client /tools /volume-selection.mjs
xlt99's picture
Upload 180 files
970fc3e verified
Raw History Blame Contribute Delete
4.26 kB
import { UI_THEME as T } from "../../shared/ui-theme.mjs";
import * as THREE from "three";
import { idsInVolume } from "../../shared/selection.mjs";
function createVolumeSelection({ world, getScene, getMeshes, getHit, onCandidates }) {
let box = null, helper = null, startMarker = null, depth = 4, ids = [];
const localRay = (ray) => {
world.updateMatrixWorld(true);
return ray.clone().applyMatrix4(world.matrixWorld.clone().invert());
};
function clear() {
if (helper) {
world.remove(helper);
helper.traverse((o) => {
o.geometry?.dispose();
o.material?.dispose();
});
helper = null;
}
if (startMarker) {
world.remove(startMarker);
startMarker.geometry.dispose();
startMarker.material.dispose();
startMarker = null;
}
box = null;
}
function paint() {
const { origin, u, v, n, a, b } = box;
helper.position.copy(origin).addScaledVector(u, (a[0] + b[0]) / 2).addScaledVector(v, (a[1] + b[1]) / 2);
helper.quaternion.setFromRotationMatrix(new THREE.Matrix4().makeBasis(u, v, n));
const visibleMinimum = 0.025 / world.scale.x;
helper.scale.set(Math.max(visibleMinimum, Math.abs(b[0] - a[0])), Math.max(visibleMinimum, Math.abs(b[1] - a[1])), depth);
ids = idsInVolume(getScene(), { origin: origin.toArray(), u: u.toArray(), v: v.toArray(), n: n.toArray(), a, b, depth });
onCandidates(ids);
}
function begin(raycaster) {
clear();
const ray = localRay(raycaster.ray), hit = getHit ? getHit(raycaster) : raycaster.intersectObjects(getMeshes().filter((m) => m.userData.definition.id !== "ground"), false)[0];
const objects = getScene().objects, hitDefinition = hit?.object?.userData.definition || objects.find((o) => o.id === hit?.id);
const distances = objects.filter((o) => o.editable === true || o.id !== "ground" && o.category !== "structure").map((o) => new THREE.Vector3().fromArray(o.position).sub(ray.origin).dot(ray.direction)).filter((d) => d > 0).sort((a, b) => a - b);
const distance = hitDefinition ? new THREE.Vector3().fromArray(hitDefinition.position).sub(ray.origin).dot(ray.direction) : distances[Math.floor(distances.length / 2)] ?? 3 / world.scale.x;
const origin = ray.at(Math.max(0.1, distance), new THREE.Vector3());
const n = ray.direction.clone().negate(), up = Math.abs(n.y) > 0.95 ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(0, 1, 0);
const u = new THREE.Vector3().crossVectors(up, n).normalize(), v = new THREE.Vector3().crossVectors(n, u).normalize();
box = { origin, u, v, n, a: [0, 0], b: [0, 0], plane: new THREE.Plane().setFromNormalAndCoplanarPoint(n, origin) };
helper = new THREE.Group();
const geometry = new THREE.BoxGeometry(1, 1, 1);
const fill = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ toneMapped: false, color: T.selected, transparent: true, opacity: 0.08, depthWrite: false, side: THREE.DoubleSide }));
const edges = new THREE.LineSegments(new THREE.EdgesGeometry(geometry), new THREE.LineBasicMaterial({ toneMapped: false, color: T.accent, depthTest: false, transparent: true, opacity: 0.9 }));
edges.renderOrder = 12;
helper.add(fill, edges);
world.add(helper);
startMarker = new THREE.Mesh(new THREE.SphereGeometry(0.015 / world.scale.x, 10, 6), new THREE.MeshBasicMaterial({ toneMapped: false, color: T.accent, depthTest: false }));
startMarker.position.copy(origin);
startMarker.renderOrder = 13;
world.add(startMarker);
paint();
}
function update(raycaster) {
if (!box) return;
const point = localRay(raycaster.ray).intersectPlane(box.plane, new THREE.Vector3());
if (!point) return;
const delta = point.sub(box.origin);
box.b = [delta.dot(box.u), delta.dot(box.v)];
paint();
}
function finish() {
if (!box) return null;
const valid = Math.abs(box.b[0] - box.a[0]) > 0.08 && Math.abs(box.b[1] - box.a[1]) > 0.08;
const result = valid ? [...ids] : null;
clear();
return result;
}
return { begin, update, finish, cancel: clear, isActive: () => !!box, setDepth(value) {
depth = THREE.MathUtils.clamp(value, 0.5, 30);
if (box) paint();
return depth;
}, getDepth: () => depth };
}
export {
createVolumeSelection
};