topfans/frontend/composables/useLenticularCraftTiltPreview.js

196 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 铸爱流程中的光栅预览:与 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<Array>|import('vue').Ref<unknown>} 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,
}
}