/** * 铸爱流程中的光栅预览:与 lenticular-studio 一致的物理参数、离散档位与陀螺仪启动节奏 */ import { ref, nextTick } from 'vue' import { useLenticularPreview } from '@/composables/useLenticularPreview.js' import { useLenticularStudioTilt } from '@/composables/useLenticularStudioTilt.js' const FLOW_PHYSICS = { angleStability: 52, transitionSmoothness: 40, tiltSensitivity: 96, sensorDeadzoneStrength: 0, parallaxDepth: 18, lenticularAnchorFloor: 0.1, lenticularNonDominantResidualMin: 0.092, lenticularPrevLayerGhostMin: 0.098, lenticularBlendBaseScale: 1.1, } const DISCRETE_STEP_DEG = 13 const DISCRETE_STEP_HYST_DEG = 5 const TILT_MAG_TAU_MS = 150 const TILT_MAG_MAX_RISE_DPS = 82 const TILT_MAG_MAX_FALL_DPS = 168 const TILT_AWAY_FROM_LEVEL_DPS = 72 const TILT_RISE_TIGHTEN = 0.42 export const LENTICULAR_TILT_START_DELAY_MS = 560 /** * @param {import('vue').Ref|import('vue').Ref} layersRef */ export function useLenticularCraftTiltPreview(layersRef) { const gyroSourceLabel = ref('simulation') const { physics, layerTransforms, simulate, relax, snapSimulatedTilt } = useLenticularPreview(layersRef) Object.assign(physics, FLOW_PHYSICS) const discreteStableStep = ref(0) let tiltMagEma = 0 let lastTiltMagSampleAt = 0 let lastSignedDegHint = null function resetTiltMagFilter() { tiltMagEma = 0 lastTiltMagSampleAt = 0 lastSignedDegHint = null } function getLayersArray() { const v = layersRef.value !== undefined ? layersRef.value : layersRef return Array.isArray(v) ? v : [] } function simulateFromSignedDegrees(tiltMagDeg, signedDegHint) { const ls = getLayersArray() const n = Math.max(1, ls.length) const raw = Math.abs(Number(tiltMagDeg) || 0) const now = Date.now() let dt = 33 if (lastTiltMagSampleAt > 0) { dt = now - lastTiltMagSampleAt } lastTiltMagSampleAt = now dt = Math.max(16, Math.min(100, dt)) const alpha = 1 - Math.exp(-dt / TILT_MAG_TAU_MS) let nextEma = tiltMagEma + (raw - tiltMagEma) * alpha const sec = dt * 0.001 let maxRise = TILT_MAG_MAX_RISE_DPS * sec const maxFall = TILT_MAG_MAX_FALL_DPS * sec if (Number.isFinite(signedDegHint)) { if (lastSignedDegHint != null && sec > 1e-6) { const dAbsSignedDt = (Math.abs(signedDegHint) - Math.abs(lastSignedDegHint)) / sec if (dAbsSignedDt > TILT_AWAY_FROM_LEVEL_DPS) { maxRise *= TILT_RISE_TIGHTEN } } lastSignedDegHint = signedDegHint } else { lastSignedDegHint = null } let dMag = nextEma - tiltMagEma if (dMag > 0) { dMag = Math.min(dMag, maxRise) } else { dMag = Math.max(dMag, -maxFall) } tiltMagEma += dMag const absDeg = tiltMagEma const STEP = DISCRETE_STEP_DEG const MARGIN = DISCRETE_STEP_HYST_DEG const stepBefore = discreteStableStep.value let s = stepBefore while (absDeg >= (s + 1) * STEP + MARGIN) s++ while (s > 0 && absDeg <= s * STEP - MARGIN) s-- discreteStableStep.value = s const idx = s % n const sens = physics.tiltSensitivity / 100 const mul = 0.44 + sens * 0.52 const u = (idx + 0.5) / n const gPick = Math.max(-1, Math.min(1, 2 * u - 1)) const gamma = Math.max(-1, Math.min(1, gPick / mul)) simulate(gamma, 0) } function lockPreviewStill() { discreteStableStep.value = 0 resetTiltMagFilter() simulateFromSignedDegrees(0) snapSimulatedTilt({ resetLayerSmoothing: true }) } const { start: startTilt, stop: stopTilt } = useLenticularStudioTilt({ simulate, simulateFromSignedDegrees, gyroSourceLabel, useStudioAccelDirect: true, onTiltDriverFallback: () => { discreteStableStep.value = 0 resetTiltMagFilter() }, }) let entryTiltTimer = null function cancelScheduledTiltStart() { if (entryTiltTimer != null) { clearTimeout(entryTiltTimer) entryTiltTimer = null } } function scheduleTiltStart() { cancelScheduledTiltStart() nextTick(() => { relax(0.86) lockPreviewStill() entryTiltTimer = setTimeout(() => { entryTiltTimer = null startTilt() }, LENTICULAR_TILT_START_DELAY_MS) }) } function stopTiltPreview() { cancelScheduledTiltStart() stopTilt() } return { physics, layerTransforms, simulate, relax, gyroSourceLabel, scheduleTiltStart, stopTiltPreview, lockPreviewStill, } }