164 lines
4.2 KiB
JavaScript
164 lines
4.2 KiB
JavaScript
/**
|
|
* 铸爱流程中的光栅预览:与 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<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) {
|
|
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,
|
|
}
|
|
}
|