Corrigiu snap e label do modelo
X-Lovable-Edit-ID: edt-b48b9eb0-fdf4-48af-bbd5-4fbbc7d06e5d Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import { Loader2 } from 'lucide-react';
|
||||
import * as THREE from 'three';
|
||||
import { useModelStore, type SceneModel } from '@/stores/useModelStore';
|
||||
import { findNearestVertex, detectHoleAtFace, findNearestEdgeSegment, detectCircularEdgeAtPoint } from './SmartMeasure';
|
||||
import { registerModelLocalGroup, unregisterModelLocalGroup } from '@/lib/modelTransforms';
|
||||
import { registerModelLocalGroup, unregisterModelLocalGroup, getModelWorldScaleFactor } from '@/lib/modelTransforms';
|
||||
import { parseIFCtoThree } from '@/lib/convertIFC';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||
import { mainCameraRef, mainControlsRef, viewAnim, calibration, pushModelFaceNormal, computeCalibrationQuaternion, subscribeCalibration } from './viewCubeBus';
|
||||
@@ -415,9 +415,24 @@ function SceneModels() {
|
||||
);
|
||||
}
|
||||
|
||||
/** Sphere marker at a 3D point — scaled per-frame to keep constant screen size.
|
||||
* Compensates for any cumulative parent world scale so it stays the same screen
|
||||
* size whether mounted in world space or inside a transformed model group. */
|
||||
/** World-units-per-pixel for the active camera (persp or ortho). */
|
||||
function computeWorldPerPixel(
|
||||
camera: THREE.Camera,
|
||||
size: { height: number },
|
||||
worldPos: THREE.Vector3,
|
||||
): number {
|
||||
const ortho = camera as THREE.OrthographicCamera;
|
||||
if ((ortho as any).isOrthographicCamera) {
|
||||
const visibleHeight = (ortho.top - ortho.bottom) / (ortho.zoom || 1);
|
||||
return visibleHeight / Math.max(1, size.height);
|
||||
}
|
||||
const persp = camera as THREE.PerspectiveCamera;
|
||||
const dist = camera.position.distanceTo(worldPos);
|
||||
const fov = (persp.fov ?? 50) * (Math.PI / 180);
|
||||
return (2 * dist * Math.tan(fov / 2)) / Math.max(1, size.height);
|
||||
}
|
||||
|
||||
/** Sphere marker at a 3D point — scaled per-frame to keep constant screen size. */
|
||||
function PointMarker({ position, color = '#e8a838' }: { position: [number, number, number]; color?: string }) {
|
||||
const ref = useRef<THREE.Mesh>(null);
|
||||
const { camera, size } = useThree();
|
||||
@@ -426,13 +441,8 @@ function PointMarker({ position, color = '#e8a838' }: { position: [number, numbe
|
||||
useFrame(() => {
|
||||
if (!ref.current) return;
|
||||
ref.current.getWorldPosition(worldPos);
|
||||
const dist = camera.position.distanceTo(worldPos);
|
||||
const perspCam = camera as THREE.PerspectiveCamera;
|
||||
const fov = (perspCam.fov ?? 50) * (Math.PI / 180);
|
||||
const worldPerPixel = (2 * dist * Math.tan(fov / 2)) / size.height;
|
||||
// Target: ~6 px radius (radius = 1 in base geometry → scale = 6*wpp).
|
||||
const targetWorld = Math.max(0.0005, 6 * worldPerPixel);
|
||||
// Divide by parent world scale so the final world size matches `targetWorld`.
|
||||
const wpp = computeWorldPerPixel(camera, size, worldPos);
|
||||
const targetWorld = Math.max(0.0005, 6 * wpp);
|
||||
const parentScale = ref.current.parent ? ref.current.parent.getWorldScale(tmpVec).x || 1 : 1;
|
||||
ref.current.scale.setScalar(targetWorld / parentScale);
|
||||
});
|
||||
@@ -444,19 +454,26 @@ function PointMarker({ position, color = '#e8a838' }: { position: [number, numbe
|
||||
);
|
||||
}
|
||||
|
||||
/** Snap ring indicator — scaled per-frame */
|
||||
/** Snap ring indicator — fixed ~10 mm diameter in world units (follows piece scale).
|
||||
* World units in this app are meters (1 unit = 1000 mm), so 5 mm radius = 0.005
|
||||
* world units, divided by the model's cumulative world scale so it remains
|
||||
* ~10 mm physically when the piece is scaled. */
|
||||
function SnapRing({ position, color = '#eab308' }: { position: [number, number, number]; color?: string }) {
|
||||
const ref = useRef<THREE.Mesh>(null);
|
||||
const { camera, size } = useThree();
|
||||
const worldPos = useMemo(() => new THREE.Vector3(), []);
|
||||
useFrame(() => {
|
||||
if (!ref.current) return;
|
||||
ref.current.lookAt(camera.position);
|
||||
const dist = camera.position.distanceTo(ref.current.position);
|
||||
const perspCam = camera as THREE.PerspectiveCamera;
|
||||
const fov = (perspCam.fov ?? 50) * (Math.PI / 180);
|
||||
const worldPerPixel = (2 * dist * Math.tan(fov / 2)) / size.height;
|
||||
const s = Math.max(0.0008, 10 * worldPerPixel);
|
||||
ref.current.scale.setScalar(s);
|
||||
const activeId = useModelStore.getState().activeModelId;
|
||||
const sf = getModelWorldScaleFactor(activeId);
|
||||
let radiusWorld = 0.005 / Math.max(1e-6, sf); // 5 mm in world units
|
||||
if (!Number.isFinite(radiusWorld) || radiusWorld <= 0) {
|
||||
ref.current.getWorldPosition(worldPos);
|
||||
const wpp = computeWorldPerPixel(camera, size, worldPos);
|
||||
radiusWorld = Math.max(0.0008, 8 * wpp);
|
||||
}
|
||||
ref.current.scale.setScalar(radiusWorld);
|
||||
});
|
||||
return (
|
||||
<mesh ref={ref} position={position}>
|
||||
@@ -466,26 +483,13 @@ function SnapRing({ position, color = '#eab308' }: { position: [number, number,
|
||||
);
|
||||
}
|
||||
|
||||
/** Computes a distanceFactor that keeps an Html label roughly constant on screen */
|
||||
function useLabelDistanceFactor(position: [number, number, number]): number {
|
||||
const { camera, size } = useThree();
|
||||
const v = useMemo(() => new THREE.Vector3(), []);
|
||||
const ref = useRef(1);
|
||||
useFrame(() => {
|
||||
v.set(position[0], position[1], position[2]);
|
||||
const dist = camera.position.distanceTo(v);
|
||||
const perspCam = camera as THREE.PerspectiveCamera;
|
||||
const fov = (perspCam.fov ?? 50) * (Math.PI / 180);
|
||||
const worldPerPixel = (2 * dist * Math.tan(fov / 2)) / size.height;
|
||||
// distanceFactor scales the html so it stays ~140 px tall at this distance
|
||||
ref.current = Math.max(0.05, worldPerPixel * 140);
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
function MeasurementLabel({ position, text, variant, fixed = false }: { position: [number, number, number]; text: string; variant: 'success' | 'primary' | 'accent'; fixed?: boolean }) {
|
||||
const dfDynamic = useLabelDistanceFactor(position);
|
||||
const df = fixed ? dfDynamic : 1.5;
|
||||
function MeasurementLabel({ position, text, variant }: { position: [number, number, number]; text: string; variant: 'success' | 'primary' | 'accent'; fixed?: boolean }) {
|
||||
const { camera } = useThree();
|
||||
const isOrtho = (camera as THREE.OrthographicCamera).isOrthographicCamera === true;
|
||||
// In orthographic mode, drei's distanceFactor uses perspective math and can
|
||||
// explode → the HTML label scales massively and visually wipes the model.
|
||||
// Use a plain screen-space Html label in ortho; perspective keeps a stable factor.
|
||||
const df = isOrtho ? undefined : 1.5;
|
||||
const borderClass = variant === 'success'
|
||||
? 'border-success/60'
|
||||
: variant === 'accent'
|
||||
|
||||
Reference in New Issue
Block a user