/** * 全息镭射卡预览组合式函数 * * 集成 WebGL HolographicEngine + 陀螺仪/触摸交互 * 与现有 useLenticularPreview / useLenticularStudioTilt 架构保持一致 */ import { ref, reactive, onMounted, onUnmounted } from 'vue' function clamp(v, min, max) { return Math.max(min, Math.min(max, v)) } function lerp(a, b, t) { return a + (b - a) * t } export function useHolographicPreview() { const physics = reactive({ tiltSensitivity: 72, transitionSmoothness: 66, angleStability: 88, sensorDeadzoneStrength: 1, gyroSimEnabled: true, }) const viewAngle = reactive({ x: 0, y: 0, z: 0 }) const gyroSource = ref('simulation') const isWebGLReady = ref(false) const hasWebGLError = ref(false) const fps = ref(0) let accelHandler = null let accelSmoothed = 0 let accelBaselineReady = false let accelBaseX = 0 let accelBaseY = 0 function simulate(x, y) { viewAngle.x = clamp(x, -1, 1) viewAngle.y = clamp(y != null ? y : 0, -1, 1) viewAngle.z = Math.sqrt(viewAngle.x * viewAngle.x + viewAngle.y * viewAngle.y) * 0.5 } function relax(factor = 0.85) { viewAngle.x *= factor viewAngle.y *= factor viewAngle.z *= factor } function startGyro() { gyroSource.value = 'simulation' if (typeof DeviceOrientationEvent === 'undefined') return if (typeof DeviceOrientationEvent.requestPermission === 'function') { gyroSource.value = 'deviceorientation-requesting' DeviceOrientationEvent.requestPermission() .then(state => { if (state === 'granted') startDeviceOrientation() }) .catch(() => { gyroSource.value = 'simulation' }) } else { startDeviceOrientation() } } function startDeviceOrientation() { gyroSource.value = 'deviceorientation' const handler = (e) => { if (e.gamma == null || e.beta == null) return const gamma = e.gamma || 0 const beta = e.beta || 0 if (!accelBaselineReady) { accelBaseX = lerp(accelBaseX, gamma, 0.08) accelBaseY = lerp(accelBaseY, beta, 0.08) if (Math.abs(accelBaseX - gamma) < 0.3 && Math.abs(accelBaseY - beta) < 0.3) { accelBaselineReady = true } return } const stab = clamp(physics.angleStability / 100, 0, 1) const sens = physics.tiltSensitivity / 100 const k = 0.02 + (1 - stab) * 0.08 const dx = (gamma - accelBaseX) / 45 * sens const dy = (beta - accelBaseY) / 45 * sens accelSmoothed = lerp(accelSmoothed, dx, k) const dead = physics.sensorDeadzoneStrength || 1 const db = 0.016 * dead simulate( Math.abs(accelSmoothed) < db ? 0 : clamp(accelSmoothed, -1, 1), clamp(dy, -1, 1) ) } window.addEventListener('deviceorientation', handler, true) accelHandler = handler } function stopGyro() { if (accelHandler) { window.removeEventListener('deviceorientation', accelHandler, true) accelHandler = null } gyroSource.value = 'simulation' accelBaselineReady = false } function onWebGLReady() { isWebGLReady.value = true; hasWebGLError.value = false } function onWebGLError() { hasWebGLError.value = true; isWebGLReady.value = false } function onFPSUpdate(v) { fps.value = v } onMounted(() => { if (physics.gyroSimEnabled) setTimeout(startGyro, 600) }) onUnmounted(() => { stopGyro() }) return { physics, viewAngle, gyroSource, isWebGLReady, hasWebGLError, fps, simulate, relax, startGyro, stopGyro, onWebGLReady, onWebGLError, onFPSUpdate, } } export function detectPerformanceTier() { const mem = navigator.deviceMemory || 4 const cores = navigator.hardwareConcurrency || 4 if (mem <= 2 || cores <= 2) return 'low' if (mem <= 4 || cores <= 4) return 'mid' return 'high' } export const HOLO_PERFORMANCE_PRESETS = { high: { effectIntensity: 0.85, dispersionStrength: 1.0, diffractionScale: 0.7, highlightSpeed: 0.8, highlightWidth: 1.0, fresnelPower: 3.5, noiseScale: 1.0, noiseOctaves: 6, safeZoneRadius: 0.35, safeZoneSoftness: 0.15, }, mid: { effectIntensity: 0.75, dispersionStrength: 0.8, diffractionScale: 0.55, highlightSpeed: 0.7, highlightWidth: 1.1, fresnelPower: 3.0, noiseScale: 0.8, noiseOctaves: 4, safeZoneRadius: 0.35, safeZoneSoftness: 0.15, }, low: { effectIntensity: 0.6, dispersionStrength: 0.5, diffractionScale: 0.35, highlightSpeed: 0.5, highlightWidth: 1.3, fresnelPower: 2.5, noiseScale: 0.55, noiseOctaves: 3, safeZoneRadius: 0.38, safeZoneSoftness: 0.18, }, }