mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-20 06:27:04 +01:00
start working with distance measurements
This commit is contained in:
54
src/misc/distances.ts
Normal file
54
src/misc/distances.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import {Vector3} from 'three';
|
||||||
|
import type {MObject3D} from "../tools/Selection.vue";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given two THREE.Object3D objects, returns their closest and farthest vertices, and the geometric centers.
|
||||||
|
* All of them are approximated and should not be used for precise calculations.
|
||||||
|
*/
|
||||||
|
export function distances(
|
||||||
|
a: InstanceType<typeof MObject3D>, b: InstanceType<typeof MObject3D>): {
|
||||||
|
min: Array<Vector3>,
|
||||||
|
center: Array<Vector3>,
|
||||||
|
max: Array<Vector3>
|
||||||
|
} {
|
||||||
|
// Simplify this problem (approximate) by using the distance between each of their vertices.
|
||||||
|
// TODO: Compute actual min and max distances between the two objects.
|
||||||
|
a.updateMatrixWorld();
|
||||||
|
b.updateMatrixWorld();
|
||||||
|
// FIXME: Working for points and lines, but not triangles...
|
||||||
|
const aVertices = a.geometry.getAttribute('position').array;
|
||||||
|
const aCenter = new Vector3();
|
||||||
|
const bVertices = b.geometry.getAttribute('position').array;
|
||||||
|
const bCenter = new Vector3();
|
||||||
|
let minDistance = Infinity;
|
||||||
|
let minDistanceVertices = [new Vector3(), new Vector3()];
|
||||||
|
let maxDistance = -Infinity;
|
||||||
|
let maxDistanceVertices = [new Vector3(), new Vector3()];
|
||||||
|
for (let i = 0; i < aVertices.length; i += 3) {
|
||||||
|
const v = new Vector3(aVertices[i], aVertices[i + 1], aVertices[i + 2]);
|
||||||
|
//a.localToWorld(v);
|
||||||
|
aCenter.add(v);
|
||||||
|
for (let j = 0; j < bVertices.length; j += 3) {
|
||||||
|
const w = new Vector3(bVertices[j], bVertices[j + 1], bVertices[j + 2]);
|
||||||
|
//b.localToWorld(w);
|
||||||
|
bCenter.add(w);
|
||||||
|
const d = v.distanceTo(w);
|
||||||
|
if (d < minDistance) {
|
||||||
|
minDistance = d;
|
||||||
|
minDistanceVertices = [v, w];
|
||||||
|
}
|
||||||
|
if (d > maxDistance) {
|
||||||
|
maxDistance = d;
|
||||||
|
maxDistanceVertices = [v, w];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aCenter.divideScalar(aVertices.length / 3);
|
||||||
|
bCenter.divideScalar(bVertices.length / 3);
|
||||||
|
return {
|
||||||
|
min: minDistanceVertices,
|
||||||
|
center: [aCenter, bCenter],
|
||||||
|
max: maxDistanceVertices
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import {extrasNameKey} from "../misc/gltf";
|
|||||||
import {SceneMgr} from "../misc/scene";
|
import {SceneMgr} from "../misc/scene";
|
||||||
import {Document} from "@gltf-transform/core";
|
import {Document} from "@gltf-transform/core";
|
||||||
import {AxesColors} from "../misc/helpers";
|
import {AxesColors} from "../misc/helpers";
|
||||||
|
import {distances} from "../misc/distances";
|
||||||
|
|
||||||
export type MObject3D = Object3D & {
|
export type MObject3D = Object3D & {
|
||||||
userData: { noHit?: boolean },
|
userData: { noHit?: boolean },
|
||||||
@@ -23,13 +24,12 @@ let emit = defineEmits<{ findModel: [string] }>();
|
|||||||
let selectionEnabled = ref(false);
|
let selectionEnabled = ref(false);
|
||||||
let selected = defineModel<Array<Intersection<MObject3D>>>({default: []});
|
let selected = defineModel<Array<Intersection<MObject3D>>>({default: []});
|
||||||
let highlightNextSelection = ref([false, false]); // Second is whether selection was enabled before
|
let highlightNextSelection = ref([false, false]); // Second is whether selection was enabled before
|
||||||
let showBoundingBox = ref<Boolean>(false);
|
let showBoundingBox = ref<Boolean>(false); // Enabled automatically on start
|
||||||
|
let showDistances = ref<Boolean>(true);
|
||||||
|
|
||||||
let mouseDownAt: [number, number] | null = null;
|
let mouseDownAt: [number, number] | null = null;
|
||||||
let selectFilter = ref('Any');
|
let selectFilter = ref('Any');
|
||||||
const raycaster = new Raycaster();
|
const raycaster = new Raycaster();
|
||||||
raycaster.params.Line.threshold = 0.2;
|
|
||||||
raycaster.params.Points.threshold = 0.8;
|
|
||||||
|
|
||||||
|
|
||||||
let selectionMoveListener = (event: MouseEvent) => {
|
let selectionMoveListener = (event: MouseEvent) => {
|
||||||
@@ -52,6 +52,21 @@ let selectionListener = (event: MouseEvent) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set raycaster parameters
|
||||||
|
if (selectFilter.value === 'Any') {
|
||||||
|
raycaster.params.Line.threshold = 0.2;
|
||||||
|
raycaster.params.Points.threshold = 0.8;
|
||||||
|
} else if(selectFilter.value === 'Edges') {
|
||||||
|
raycaster.params.Line.threshold = 0.8;
|
||||||
|
raycaster.params.Points.threshold = 0.0;
|
||||||
|
} else if (selectFilter.value === 'Vertices') {
|
||||||
|
raycaster.params.Line.threshold = 0.0;
|
||||||
|
raycaster.params.Points.threshold = 0.8;
|
||||||
|
} else if (selectFilter.value === 'Faces') {
|
||||||
|
raycaster.params.Line.threshold = 0.0;
|
||||||
|
raycaster.params.Points.threshold = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
// Define the 3D ray from the camera to the mouse
|
// Define the 3D ray from the camera to the mouse
|
||||||
// NOTE: Need to access internal as the API has issues with small faces surrounded by edges
|
// NOTE: Need to access internal as the API has issues with small faces surrounded by edges
|
||||||
let scene: ModelScene = props.viewer?.scene;
|
let scene: ModelScene = props.viewer?.scene;
|
||||||
@@ -95,6 +110,7 @@ let selectionListener = (event: MouseEvent) => {
|
|||||||
deselectAll();
|
deselectAll();
|
||||||
}
|
}
|
||||||
updateBoundingBox();
|
updateBoundingBox();
|
||||||
|
updateDistances();
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, highlight the model that owns the hit
|
// Otherwise, highlight the model that owns the hit
|
||||||
emit('findModel', hit.object.userData[extrasNameKey])
|
emit('findModel', hit.object.userData[extrasNameKey])
|
||||||
@@ -282,7 +298,6 @@ function updateBoundingBox() {
|
|||||||
let color = [AxesColors.x, AxesColors.y, AxesColors.z][edgeI][1]; // Secondary colors
|
let color = [AxesColors.x, AxesColors.y, AxesColors.z][edgeI][1]; // Secondary colors
|
||||||
let lineCacheKey = JSON.stringify([from, to]);
|
let lineCacheKey = JSON.stringify([from, to]);
|
||||||
let matchingLine = boundingBoxLines[lineCacheKey];
|
let matchingLine = boundingBoxLines[lineCacheKey];
|
||||||
console.log('Edge', edge, 'Matching line', matchingLine, 'key')
|
|
||||||
if (matchingLine) {
|
if (matchingLine) {
|
||||||
boundingBoxLinesToRemove = boundingBoxLinesToRemove.filter((l) => l !== lineCacheKey);
|
boundingBoxLinesToRemove = boundingBoxLinesToRemove.filter((l) => l !== lineCacheKey);
|
||||||
} else {
|
} else {
|
||||||
@@ -299,6 +314,56 @@ function updateBoundingBox() {
|
|||||||
delete boundingBoxLines[lineLocator];
|
delete boundingBoxLines[lineLocator];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleShowDistances() {
|
||||||
|
showDistances.value = !showDistances.value;
|
||||||
|
updateDistances();
|
||||||
|
}
|
||||||
|
|
||||||
|
let distanceLines: { [points: string]: number } = {}
|
||||||
|
|
||||||
|
function updateDistances() {
|
||||||
|
if (!showDistances.value || selected.value.length != 2) {
|
||||||
|
for (let lineId of Object.values(distanceLines)) {
|
||||||
|
props.viewer?.removeLine3D(lineId);
|
||||||
|
}
|
||||||
|
distanceLines = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the line cache (for delta updates)
|
||||||
|
let distanceLinesToRemove = Object.keys(distanceLines);
|
||||||
|
function ensureLine(from: Vector3, to: Vector3, text: string, color: string) {
|
||||||
|
console.log('ensureLine', from, to, text, color)
|
||||||
|
let lineCacheKey = JSON.stringify([from, to]);
|
||||||
|
let matchingLine = distanceLines[lineCacheKey];
|
||||||
|
if (matchingLine) {
|
||||||
|
distanceLinesToRemove = distanceLinesToRemove.filter((l) => l !== lineCacheKey);
|
||||||
|
} else {
|
||||||
|
distanceLines[lineCacheKey] = props.viewer?.addLine3D(from, to, text, {
|
||||||
|
"stroke": color,
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-dasharray": "5"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add lines (if not already added)
|
||||||
|
let objA = selected.value[0].object;
|
||||||
|
let objB = selected.value[1].object;
|
||||||
|
let {min, center, max} = distances(objA, objB);
|
||||||
|
ensureLine(max[0], max[1], max[1].distanceTo(max[0]).toFixed(1) + "mm", "orange");
|
||||||
|
ensureLine(center[0], center[1], center[1].distanceTo(center[0]).toFixed(1) + "mm", "green");
|
||||||
|
ensureLine(min[0], min[1], min[1].distanceTo(min[0]).toFixed(1) + "mm", "cyan");
|
||||||
|
|
||||||
|
// Remove the lines that are no longer needed
|
||||||
|
for (let lineLocator of distanceLinesToRemove) {
|
||||||
|
props.viewer?.removeLine3D(distanceLines[lineLocator]);
|
||||||
|
delete distanceLines[lineLocator];
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -321,15 +386,14 @@ function updateBoundingBox() {
|
|||||||
<v-tooltip activator="parent">Highlight the next clicked element in the models list</v-tooltip>
|
<v-tooltip activator="parent">Highlight the next clicked element in the models list</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiFeatureSearch"/>
|
<svg-icon type="mdi" :path="mdiFeatureSearch"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<!-- TODO: Show BB -->
|
|
||||||
<v-btn icon @click="toggleShowBoundingBox" :color="showBoundingBox ? 'surface-light' : ''">
|
<v-btn icon @click="toggleShowBoundingBox" :color="showBoundingBox ? 'surface-light' : ''">
|
||||||
<v-tooltip activator="parent">{{ showBoundingBox ? 'Hide selection bounds' : 'Show selection bounds' }}
|
<v-tooltip activator="parent">{{ showBoundingBox ? 'Hide selection bounds' : 'Show selection bounds' }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiCubeOutline"/>
|
<svg-icon type="mdi" :path="mdiCubeOutline"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<!-- TODO: Show distances of selections (min/center/max distance) -->
|
<v-btn icon @click="toggleShowDistances" :color="showDistances ? 'surface-light' : ''">
|
||||||
<v-btn icon disabled @click="toggleShowBoundingBox" :color="showBoundingBox ? 'surface-light' : ''">
|
<v-tooltip activator="parent">
|
||||||
<v-tooltip activator="parent">{{ showBoundingBox ? 'Hide selection dimensions' : 'Show selection dimensions' }}
|
{{ showDistances ? 'Hide selection distances' : 'Show distances (when a pair of features is selected)' }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<svg-icon type="mdi" :path="mdiRuler"/>
|
<svg-icon type="mdi" :path="mdiRuler"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ function removeLine3D(id: number) {
|
|||||||
scene.value.removeHotspot(new Hotspot({name: 'line' + id + '_end'}));
|
scene.value.removeHotspot(new Hotspot({name: 'line' + id + '_end'}));
|
||||||
lines.value[id].endHotspot.parentElement.remove()
|
lines.value[id].endHotspot.parentElement.remove()
|
||||||
delete lines.value[id];
|
delete lines.value[id];
|
||||||
|
lines.value = {...lines.value}; // TODO: Trigger reactivity not working...
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCameraChange() {
|
function onCameraChange() {
|
||||||
|
|||||||
Reference in New Issue
Block a user