Files
supart-ComfyUI-TY360-Photo-…/web/pano_interaction_controller.js
T

181 lines
6.3 KiB
JavaScript

import { clamp, wrapYaw } from "./pano_math.js";
const DEG2RAD = Math.PI / 180;
export const PANO_DRAG_SENSITIVITY = 0.12;
export const PANO_WHEEL_STEP = 3;
export const PANO_FOV_MIN = 35;
export const PANO_FOV_MAX = 140;
export const PANO_INITIAL_FOV = 100;
export const PANO_INERTIA_BLEND_OLD = 0.4;
export const PANO_INERTIA_BLEND_INST = 0.6;
export const PANO_INERTIA_DAMPING = 5.5;
export const PANO_INERTIA_START_SPEED = 20;
export const PANO_INERTIA_STOP_SPEED = 0.8;
function readWheelDelta(ev) {
if (Number.isFinite(Number(ev?.deltaY))) return Number(ev.deltaY);
if (Number.isFinite(Number(ev?.wheelDelta))) return -Number(ev.wheelDelta);
if (Number.isFinite(Number(ev?.detail))) return Number(ev.detail) * 40;
return 0;
}
export function createPanoInteractionController(options = {}) {
const getView = typeof options.getView === "function" ? options.getView : (() => ({ yaw: 0, pitch: 0, fov: PANO_INITIAL_FOV }));
const setView = typeof options.setView === "function" ? options.setView : (() => {});
const getInvert = typeof options.getInvert === "function" ? options.getInvert : (() => ({ x: 1, y: 1 }));
const getUnwrapRect = typeof options.getUnwrapRect === "function" ? options.getUnwrapRect : (() => ({ w: 1, h: 1 }));
const onInteraction = typeof options.onInteraction === "function" ? options.onInteraction : (() => {});
const onDebug = typeof options.onDebug === "function" ? options.onDebug : null;
const state = {
drag: { active: false, lastX: 0, lastY: 0, lastTs: 0, pointerId: null },
inertia: { vx: 0, vy: 0, active: false, lastTs: 0 },
};
function log(tag, payload = null) {
if (!onDebug) return;
onDebug(tag, payload);
}
function startDrag(x, y, pointerId = null, ts = performance.now()) {
state.drag.active = true;
state.drag.lastX = Number(x || 0);
state.drag.lastY = Number(y || 0);
state.drag.lastTs = Number(ts || performance.now());
state.drag.pointerId = pointerId;
state.inertia.active = false;
state.inertia.vx = 0;
state.inertia.vy = 0;
state.inertia.lastTs = state.drag.lastTs;
log("drag", { phase: "start", x: state.drag.lastX, y: state.drag.lastY, pointerId });
return true;
}
function moveDrag(x, y, mode = "pano", ts = performance.now()) {
if (!state.drag.active) return false;
const now = Number(ts || performance.now());
const px = Number(x);
const py = Number(y);
const dt = Math.max(0.001, (now - (state.drag.lastTs || now)) / 1000);
const dx = px - state.drag.lastX;
const dy = py - state.drag.lastY;
state.drag.lastX = px;
state.drag.lastY = py;
state.drag.lastTs = now;
const inv = getInvert() || { x: 1, y: 1 };
const invX = Number(inv.x || 1);
const invY = Number(inv.y || 1);
const view = { ...getView() };
let dYaw = 0;
let dPitch = 0;
if (mode === "unwrap") {
const r = getUnwrapRect() || { w: 1, h: 1 };
const rw = Math.max(1, Number(r.w || 1));
const rh = Math.max(1, Number(r.h || 1));
const nx = dx / rw;
const ny = dy / rh;
dYaw = -nx * 360 * invX;
dPitch = ny * 180 * invY;
} else {
dYaw = -dx * PANO_DRAG_SENSITIVITY * invX;
dPitch = dy * PANO_DRAG_SENSITIVITY * invY;
}
view.yaw = wrapYaw(Number(view.yaw || 0) + dYaw);
view.pitch = clamp(Number(view.pitch || 0) + dPitch, -89.9, 89.9);
setView(view);
state.inertia.vx = state.inertia.vx * PANO_INERTIA_BLEND_OLD + (dYaw / dt) * PANO_INERTIA_BLEND_INST;
state.inertia.vy = state.inertia.vy * PANO_INERTIA_BLEND_OLD + (dPitch / dt) * PANO_INERTIA_BLEND_INST;
state.inertia.active = false;
state.inertia.lastTs = now;
onInteraction();
log("drag", { phase: "move", dx, dy, dYaw, dPitch });
return true;
}
function endDrag(ts = performance.now()) {
if (!state.drag.active) return false;
state.drag.active = false;
state.drag.lastTs = Number(ts || performance.now());
const speed = Math.hypot(state.inertia.vx || 0, state.inertia.vy || 0);
state.inertia.active = speed > PANO_INERTIA_START_SPEED;
state.inertia.lastTs = state.drag.lastTs;
log("drag", { phase: "end", speed, inertiaActive: state.inertia.active });
return true;
}
function stepInertia(ts = performance.now()) {
if (!state.inertia.active) return false;
const now = Number(ts || performance.now());
const dt = Math.max(0.001, (now - (state.inertia.lastTs || now)) / 1000);
state.inertia.lastTs = now;
const view = { ...getView() };
view.yaw = wrapYaw(Number(view.yaw || 0) + state.inertia.vx * dt);
view.pitch = clamp(Number(view.pitch || 0) + state.inertia.vy * dt, -89.9, 89.9);
const damping = Math.exp(-PANO_INERTIA_DAMPING * dt);
state.inertia.vx *= damping;
state.inertia.vy *= damping;
if (Math.abs(state.inertia.vx) < PANO_INERTIA_STOP_SPEED && Math.abs(state.inertia.vy) < PANO_INERTIA_STOP_SPEED) {
state.inertia.vx = 0;
state.inertia.vy = 0;
state.inertia.active = false;
}
setView(view);
onInteraction();
return state.inertia.active;
}
function applyWheel(deltaSign) {
const sign = Math.sign(Number(deltaSign || 0));
if (!sign) return false;
const view = { ...getView() };
const before = Number(view.fov || PANO_INITIAL_FOV);
view.fov = clamp(before + sign * PANO_WHEEL_STEP, PANO_FOV_MIN, PANO_FOV_MAX);
setView(view);
onInteraction();
log("wheel", { deltaSign: sign, fovBefore: before, fovAfter: view.fov });
return true;
}
function applyWheelEvent(ev) {
return applyWheel(Math.sign(readWheelDelta(ev)));
}
function resetView() {
setView({ yaw: 0, pitch: 0, fov: PANO_INITIAL_FOV });
state.inertia.active = false;
state.inertia.vx = 0;
state.inertia.vy = 0;
onInteraction();
}
return {
state,
startDrag,
moveDrag,
endDrag,
stepInertia,
applyWheel,
applyWheelEvent,
resetView,
};
}
export const PANO_MODAL_PARITY_CONSTANTS = {
dragSensitivity: PANO_DRAG_SENSITIVITY,
wheelStep: PANO_WHEEL_STEP,
fovMin: PANO_FOV_MIN,
fovMax: PANO_FOV_MAX,
initialFov: PANO_INITIAL_FOV,
inertiaBlendOld: PANO_INERTIA_BLEND_OLD,
inertiaBlendInst: PANO_INERTIA_BLEND_INST,
inertiaDamping: PANO_INERTIA_DAMPING,
inertiaStartSpeed: PANO_INERTIA_START_SPEED,
inertiaStopSpeed: PANO_INERTIA_STOP_SPEED,
rad2deg: 1 / DEG2RAD,
};