Scale BIM empty-space wheel zoom

This commit is contained in:
CODEX 2026-06-23 21:05:54 +03:00
parent b90142a6c8
commit 9dc5dd4aa0
1 changed files with 67 additions and 5 deletions

View File

@ -303,6 +303,8 @@ let modelVersionHistoryLoading = false;
let modelVersionUploading = false;
let versionHistoryModal = null;
let emptyWheelNavigationRestoreTimer = null;
let lastSurfaceDollyFactor = null;
let lastSurfaceDollyFactorAt = 0;
const GUEST_TOOLBAR_ACTIONS = new Set(["measure", "section", "projection"]);
const memoryStore = new Map(); // url -> ArrayBuffer
const blobUrls = [];
@ -387,6 +389,7 @@ const ZOOM_SPEED_PERCENT_MAX = 100;
const ZOOM_DOLLY_RATE_MIN = 1;
const ZOOM_DOLLY_RATE_MAX = 100;
const ZOOM_DOLLY_RESPONSE_EXPONENT = 1.45;
const LAST_SURFACE_DOLLY_FACTOR_TTL = 1500;
const normalizeZoomSpeed = (value) => {
const numeric = Number(value);
@ -401,6 +404,8 @@ const getZoomDollyRate = (value) => {
return ZOOM_DOLLY_RATE_MIN + (Math.pow(t, ZOOM_DOLLY_RESPONSE_EXPONENT) * (ZOOM_DOLLY_RATE_MAX - ZOOM_DOLLY_RATE_MIN));
};
const getBaseMouseWheelDollyRate = () => getZoomDollyRate(activeZoomSpeed);
const isHexColor = (value) => /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(String(value || "").trim());
const normalizeCustomDisplayColor = (value) => {
@ -466,7 +471,7 @@ const configureCameraControl = () => {
control.smartPivot = true;
control.zoomOnMouseWheel = true;
control.keyboardDollyRate = CAMERA_CONTROL_SETTINGS.keyboardDollyRate;
control.mouseWheelDollyRate = getZoomDollyRate(activeZoomSpeed);
control.mouseWheelDollyRate = getBaseMouseWheelDollyRate();
control.dollyInertia = CAMERA_CONTROL_SETTINGS.dollyInertia;
control.dollyMinSpeed = CAMERA_CONTROL_SETTINGS.dollyMinSpeed;
control.dollyProximityThreshold = CAMERA_CONTROL_SETTINGS.dollyProximityThreshold;
@ -1341,6 +1346,60 @@ const getCanvasPointFromEvent = (event) => {
};
};
const getDollyFactorForDistance = (distance) => {
const numeric = Number(distance);
if (!Number.isFinite(numeric) || numeric <= 0) return 1;
return Math.max(
CAMERA_CONTROL_SETTINGS.dollyMinSpeed,
numeric / CAMERA_CONTROL_SETTINGS.dollyProximityThreshold,
);
};
const getDollyFactorForWorldPos = (worldPos) => {
const eye = viewer?.camera?.eye || viewer?.scene?.camera?.eye;
if (!eye || !worldPos || worldPos.length < 3) return null;
const distance = Math.hypot(
Number(worldPos[0]) - Number(eye[0]),
Number(worldPos[1]) - Number(eye[1]),
Number(worldPos[2]) - Number(eye[2]),
);
return Number.isFinite(distance) ? getDollyFactorForDistance(distance) : null;
};
const getCameraFocusDollyFactor = () => {
const camera = viewer?.camera || viewer?.scene?.camera;
const directDistance = Number(camera?.eyeLookDist);
if (Number.isFinite(directDistance) && directDistance > 0) {
return getDollyFactorForDistance(directDistance);
}
const eye = camera?.eye;
const look = camera?.look;
if (!eye || !look) return 1;
const distance = Math.hypot(
Number(look[0]) - Number(eye[0]),
Number(look[1]) - Number(eye[1]),
Number(look[2]) - Number(eye[2]),
);
return Number.isFinite(distance) ? getDollyFactorForDistance(distance) : 1;
};
const rememberSurfaceDollyFactor = (hit) => {
const factor = getDollyFactorForWorldPos(hit?.worldPos);
if (factor === null) return;
lastSurfaceDollyFactor = factor;
lastSurfaceDollyFactorAt = window.performance?.now?.() || Date.now();
};
const getEmptyWheelDollyFactor = () => {
const focusFactor = getCameraFocusDollyFactor();
const now = window.performance?.now?.() || Date.now();
const hasFreshSurfaceFactor = (
lastSurfaceDollyFactor !== null &&
now - lastSurfaceDollyFactorAt <= LAST_SURFACE_DOLLY_FACTOR_TTL
);
return hasFreshSurfaceFactor ? Math.min(focusFactor, lastSurfaceDollyFactor) : focusFactor;
};
const restorePointerNavigation = () => {
if (emptyWheelNavigationRestoreTimer) {
window.clearTimeout(emptyWheelNavigationRestoreTimer);
@ -1350,6 +1409,7 @@ const restorePointerNavigation = () => {
if (!control) return;
control.followPointer = true;
control.smartPivot = true;
control.mouseWheelDollyRate = getBaseMouseWheelDollyRate();
};
const schedulePointerNavigationRestore = () => {
@ -1362,16 +1422,15 @@ const schedulePointerNavigationRestore = () => {
}, 80);
};
const hasWheelSurfaceHit = (event) => {
const getWheelSurfaceHit = (event) => {
if (!activeModels.length || !viewer?.scene?.pick) return true;
const point = getCanvasPointFromEvent(event);
if (!point) return true;
try {
const hit = viewer.scene.pick({
return viewer.scene.pick({
canvasPos: point.canvasPos,
pickSurface: true,
});
return Boolean(hit?.entity || hit?.worldPos);
} catch (_) {
return true;
}
@ -1380,11 +1439,14 @@ const hasWheelSurfaceHit = (event) => {
const guardEmptyWheelNavigation = (event) => {
const control = viewer?.cameraControl;
if (!control) return;
if (hasWheelSurfaceHit(event)) {
const hit = getWheelSurfaceHit(event);
if (hit === true || hit?.entity || hit?.worldPos) {
rememberSurfaceDollyFactor(hit === true ? null : hit);
restorePointerNavigation();
return;
}
control.followPointer = false;
control.mouseWheelDollyRate = getBaseMouseWheelDollyRate() * getEmptyWheelDollyFactor();
schedulePointerNavigationRestore();
};