/** * 铸爱流程中的光栅预览:与 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: 1, parallaxDepth: 18, lenticularAnchorFloor: 0.1, lenticularNonDominantResidualMin: 0.092, lenticularPrevLayerGhostMin: 0.098, lenticularBlendBaseScale: 1.1, } const DISCRETE_STEP_DEG = 13 const DISCRETE_STEP_HYST_DEG = 9 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) { console.log('[DEBUG simulateFromSignedDegrees] called, tiltMagDeg:', tiltMagDeg, 'signedDegHint:', 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)) { console.log('[DEBUG simulateFromSignedDegrees] signedDegHint is finite:', 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 // 二值模式:仅允许 0(中性) 或 1(倾斜),防止同方向重复递增档位 const ENTER_THRESHOLD = STEP + MARGIN const EXIT_THRESHOLD = MARGIN if (s === 0) { // 中性状态:仅当倾斜超过阈值才进入档位1 if (absDeg >= ENTER_THRESHOLD) { s = 1 console.log('[DEBUG] 进入档位1, absDeg:', absDeg.toFixed(2), 'signedDegHint:', signedDegHint) } } else if (s === 1) { // 档位1状态:检测方向变化才允许退回 // 如果 signedDegHint 符号改变(反方向倾斜),立即回到中性 if (lastSignedDegHint !== null && Number.isFinite(signedDegHint)) { if (signedDegHint * lastSignedDegHint < 0) { // 方向改变,回到中性 s = 0 lastSignedDegHint = signedDegHint console.log('[DEBUG] 方向改变,回到中性, signedDegHint:', signedDegHint.toFixed(2)) } } else if (absDeg <= EXIT_THRESHOLD) { // 没有方向信息时,使用角度阈值 s = 0 console.log('[DEBUG] 角度不足,回到中性, absDeg:', absDeg.toFixed(2)) } } 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() { console.log('[DEBUG useLenticularCraftTiltPreview] scheduleTiltStart called') cancelScheduledTiltStart() nextTick(() => { relax(0.86) lockPreviewStill() console.log('[DEBUG useLenticularCraftTiltPreview] entryTiltTimer set, delay:', LENTICULAR_TILT_START_DELAY_MS) entryTiltTimer = setTimeout(() => { entryTiltTimer = null console.log('[DEBUG useLenticularCraftTiltPreview] startTilt about to be called') startTilt() console.log('[DEBUG useLenticularCraftTiltPreview] startTilt called') }, LENTICULAR_TILT_START_DELAY_MS) }) } function stopTiltPreview() { cancelScheduledTiltStart() stopTilt() } return { physics, layerTransforms, simulate, relax, gyroSourceLabel, scheduleTiltStart, stopTiltPreview, lockPreviewStill, } }