topfans/frontend/composables/useLenticularPreview.js

156 lines
3.5 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.

/**
* 光栅卡预览:触摸模拟倾斜 + LenticularEngine不启动硬件传感器
* @param {import('vue').Ref<Array>} layersRef 图层配置reactive/ref 均可被 .value 读取时用 ref
*/
import { reactive, ref, watch, onMounted, onUnmounted } from 'vue'
import { LenticularEngine, DEFAULT_PHYSICS } from '@/utils/lenticular-engine.js'
function clamp(v, min, max) {
return Math.max(min, Math.min(max, v))
}
export function useLenticularPreview(layersRef) {
const physics = reactive({ ...DEFAULT_PHYSICS })
physics.gyroSimEnabled = false
const sensorData = ref({ gamma: 0, beta: 0, timestamp: Date.now() })
const source = ref('simulation')
function simulate(x, _y) {
sensorData.value = {
gamma: clamp(x, -1, 1),
beta: 0,
timestamp: Date.now(),
}
}
function relax(factor = 0.85) {
sensorData.value = {
gamma: sensorData.value.gamma * factor,
beta: 0,
timestamp: Date.now(),
}
}
const engine = new LenticularEngine(physics)
const initial = layersRef.value || layersRef
engine.setLayers(Array.isArray(initial) ? initial : [])
const layerTransforms = ref({})
function rebuildTransformsFromLayers(ls) {
const prev = layerTransforms.value
const next = {}
for (const l of ls) {
const p = prev[l.id]
next[l.id] =
p != null ? p : { x: 0, y: 0, opacity: l.opacity }
}
layerTransforms.value = next
}
rebuildTransformsFromLayers(engine.layers.length ? engine.layers : layersRef.value || [])
const stripeRender = ref({
phaseShift: 0,
shares: [1 / 3, 1 / 3, 1 / 3],
pitchPx: DEFAULT_PHYSICS.lenticularPitchPx,
prevLayerGhost: null,
})
let rafId = null
const nextFrame =
typeof requestAnimationFrame === 'function'
? (cb) => requestAnimationFrame(cb)
: (cb) => setTimeout(cb, 16)
const cancelFrame =
typeof cancelAnimationFrame === 'function'
? (id) => cancelAnimationFrame(id)
: (id) => clearTimeout(id)
function getLayersArray() {
const v = layersRef.value !== undefined ? layersRef.value : layersRef
return Array.isArray(v) ? v : []
}
function tick() {
try {
const ls = getLayersArray()
const renderState = engine.feedSimulatedTilt(sensorData.value.gamma, sensorData.value.beta)
const next = {}
for (const layer of ls) {
const offset = renderState.layerOffsets.get(layer.id)
const opacity = renderState.layerOpacities.get(layer.id)
next[layer.id] = {
x: offset != null ? offset.x : 0,
y: offset != null ? offset.y : 0,
opacity: opacity != null ? opacity : layer.opacity,
}
}
layerTransforms.value = next
stripeRender.value = {
phaseShift: renderState.stripePhaseShift,
shares: [...renderState.stripShares],
pitchPx: renderState.lenticularPitchPx,
prevLayerGhost: renderState.prevLayerGhost,
}
} catch (e) {
console.error('[useLenticularPreview] tick failed', e)
}
rafId = nextFrame(tick)
}
function startRenderLoop() {
if (rafId != null) return
rafId = nextFrame(tick)
}
function stopRenderLoop() {
if (rafId != null) {
cancelFrame(rafId)
rafId = null
}
}
watch(
layersRef,
(ls) => {
const arr = Array.isArray(ls) ? ls : []
engine.setLayers(arr)
rebuildTransformsFromLayers(arr)
},
{ deep: true, immediate: true }
)
onMounted(() => {
startRenderLoop()
})
onUnmounted(() => {
stopRenderLoop()
})
const gyro = {
sensorData,
source,
simulate,
relax,
start: () => {},
stop: () => {
source.value = 'simulation'
},
}
return {
physics,
layerTransforms,
stripeRender,
gyro,
simulate,
relax,
engine,
startRenderLoop,
stopRenderLoop,
}
}