/** * 光栅卡预览:触摸模拟倾斜 + LenticularEngine(不启动硬件传感器) * @param {import('vue').Ref} 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, } }