topfans/frontend/composables/useLenticularCraftTiltPreview.js

204 lines
6.0 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-result / asset-detail 等):物理参数、离散档位与陀螺仪启动节奏
*/
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 = 8
const DISCRETE_STEP_HYST_DEG = 4
/** 更跟手:降低时间常数并提升斜率上限 */
const TILT_MAG_TAU_MS = 90
const TILT_MAG_MAX_RISE_DPS = 160
const TILT_MAG_MAX_FALL_DPS = 240
const TILT_AWAY_FROM_LEVEL_DPS = 72
const TILT_RISE_TIGHTEN = 0.72
/** 进入页面后的传感器启动延迟,过大体感会像「不灵敏」 */
export const LENTICULAR_TILT_START_DELAY_MS = 260
/** 高频预览日志开关:默认关闭,避免传感器驱动时刷屏 */
const DEBUG_TILT_PREVIEW_LOG = false
const dlog = (...args) => {
if (DEBUG_TILT_PREVIEW_LOG) console.log(...args)
}
/**
* @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) {
dlog('[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)) {
dlog('[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
dlog('[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
dlog('[DEBUG] 方向改变,回到中性, signedDegHint:', signedDegHint.toFixed(2))
}
} else if (absDeg <= EXIT_THRESHOLD) {
// 没有方向信息时,使用角度阈值
s = 0
dlog('[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() {
dlog('[DEBUG useLenticularCraftTiltPreview] scheduleTiltStart called')
cancelScheduledTiltStart()
nextTick(() => {
relax(0.86)
lockPreviewStill()
dlog('[DEBUG useLenticularCraftTiltPreview] entryTiltTimer set, delay:', LENTICULAR_TILT_START_DELAY_MS)
entryTiltTimer = setTimeout(() => {
entryTiltTimer = null
dlog('[DEBUG useLenticularCraftTiltPreview] startTilt about to be called')
startTilt()
dlog('[DEBUG useLenticularCraftTiltPreview] startTilt called')
}, LENTICULAR_TILT_START_DELAY_MS)
})
}
function stopTiltPreview() {
cancelScheduledTiltStart()
stopTilt()
}
return {
physics,
layerTransforms,
simulate,
relax,
gyroSourceLabel,
scheduleTiltStart,
stopTiltPreview,
lockPreviewStill,
}
}