Component({ behaviors: [require('../common/share-behavior').default], properties: { markerImg: { type: String }, }, data: { loaded: false, arReady: false, isStartPlay1: false, adlScale: '0 0 0', adlPos: '-0.1 -0.5 -0.05', sunReady: false, tigerReady: false }, lifetimes: { attached() { }, detached() { this.stopSunRotation(); this.stopTiger(); } }, methods: { handleReady({ detail }) { const xrScene = this.scene = detail.value; // console.log('xr-scene', xrScene); }, handleAssetsProgress: function ({ detail }) { // console.log('assets progress', detail.value); }, handleAssetsLoaded: function ({ detail }) { console.log('assets loaded', detail.value); this.setData({ loaded: true }); }, handleARReady: function ({ detail }) { console.log('arReady'); this.setData({ arReady: true }) }, handleItem1Loaded({ detail }) { const el = detail.value.target; const animator = el.getComponent("animator"); this.animator1 = animator console.log('animator1', animator) const gltf = el.getComponent("gltf"); this.bgGltf = gltf; this.sunMeshes = this.resolveSunMeshesFromBG(gltf); this.setData({ sunReady: !!(this.sunMeshes && this.sunMeshes.length) }); if (this.sunShouldRun) this.startSunRotation(); this.tigerMeshes = this.resolveTigerMeshesFromBG(gltf); this.setData({ tigerReady: !!(this.tigerMeshes && this.tigerMeshes.length) }); if (this.tigerShouldRun && this.tigerStartRequested) this.scheduleTigerStart(); }, handleItem2Loaded({ detail }) { const el = detail.value.target; const animator = el.getComponent("animator"); this.animator2 = animator; }, handleARTrackerState1({ detail }) { // 事件的值即为`ARTracker`实例 const tracker = detail.value; // 获取当前状态和错误信息 console.log('tracker', tracker) const { state, } = tracker; if (state == 2) { this.play() } else { this.pause() } }, play() { if (!this.data.loaded) return this.sunShouldRun = true; this.startSunRotation(); this.tigerShouldRun = true; this.tigerStartRequested = false; this.prepareTigerAudio(); this.prepareTigerFrames(); if (!this.data.isStartPlay1) { this.setData({ isStartPlay1: true }, () => { this.playitem1Action() }) } }, pause() { this.sunShouldRun = false; this.stopSunRotation(); this.tigerShouldRun = false; this.tigerStartRequested = false; this.stopTiger(); }, resolveSunMeshesFromBG(gltf) { if (!gltf || !this.scene) return []; const candidates = this.getSunNameCandidates(); const uniqueCandidates = []; for (const name of candidates) { if (name && !uniqueCandidates.includes(name)) uniqueCandidates.push(name); } for (const name of uniqueCandidates) { if (typeof gltf.getPrimitivesByNodeName === 'function') { const meshes = gltf.getPrimitivesByNodeName(name); if (meshes && meshes.length) return meshes; } if (typeof gltf.getPrimitivesByMeshName === 'function') { const meshes = gltf.getPrimitivesByMeshName(name); if (meshes && meshes.length) return meshes; } } if (typeof gltf.getMeshes === 'function') { const meshes = gltf.getMeshes() || []; const meshNames = meshes.map(m => m && (m.name || m.el && m.el.name)).filter(Boolean); console.warn('sun mesh not found, available mesh names:', meshNames); } else { console.warn('sun mesh not found'); } return []; }, getSunNameCandidates() { const defaultCandidates = ['Sun', 'sun', 'SUN', '太阳', 'Taiyang', 'taiyang']; if (!this.scene) return defaultCandidates; const gltfAsset = this.scene.assets && this.scene.assets.getAsset && this.scene.assets.getAsset('gltf', 'bg'); const jsonRaw = gltfAsset && gltfAsset.jsonRaw; if (!jsonRaw) return defaultCandidates; const names = []; const addIfMatch = (n) => { if (!n || typeof n !== 'string') return; if (/Sun/i.test(n) || n.includes('太阳')) names.push(n); }; if (Array.isArray(jsonRaw.nodes)) { for (const node of jsonRaw.nodes) addIfMatch(node && node.name); } if (Array.isArray(jsonRaw.meshes)) { for (const mesh of jsonRaw.meshes) addIfMatch(mesh && mesh.name); } return names.length ? names.concat(defaultCandidates) : defaultCandidates; }, startSunRotation() { if (!this.scene) return; if (!this.sunShouldRun) return; if (!this.sunMeshes || !this.sunMeshes.length) return; if (this.sunRotationTimer) return; this.sunFrameCount = 159; this.sunFrameIndex = this.sunFrameIndex || 1; this.sunTextureCacheLimit = 12; if (!this.sunTextureCache) this.sunTextureCache = new Map(); if (!this.sunTextureQueue) this.sunTextureQueue = []; this.updateSunFrame(); this.sunRotationTimer = setInterval(() => this.updateSunFrame(), 80); }, stopSunRotation() { if (this.sunRotationTimer) { clearInterval(this.sunRotationTimer); this.sunRotationTimer = null; } this.sunFrameLoading = false; if (this.scene && this.scene.assets && this.scene.assets.releaseAsset && this.sunTextureQueue) { for (const assetId of this.sunTextureQueue) { this.scene.assets.releaseAsset('texture', assetId); } } this.sunTextureCache = null; this.sunTextureQueue = null; }, async updateSunFrame() { if (this.sunFrameLoading) return; if (!this.scene || !this.sunMeshes || !this.sunMeshes.length) return; if (!this.sunShouldRun) return; this.sunFrameLoading = true; try { const frame = this.sunFrameIndex; this.sunFrameIndex = frame >= this.sunFrameCount ? 1 : frame + 1; const frameStr = String(frame).padStart(3, '0'); const assetId = `sun-${frameStr}`; const src = `https://ossxiaoan.4dage.com/hq-eduction-vr/sun/Sun_${frameStr}.png`; let texture = this.sunTextureCache && this.sunTextureCache.get(assetId); if (!texture) { const result = await this.scene.assets.loadAsset({ type: 'texture', assetId, src }); texture = result && result.value; if (texture) { if (this.sunTextureCache) this.sunTextureCache.set(assetId, texture); if (this.sunTextureQueue) this.sunTextureQueue.push(assetId); while (this.sunTextureQueue && this.sunTextureQueue.length > this.sunTextureCacheLimit) { const oldAssetId = this.sunTextureQueue.shift(); if (oldAssetId) { this.scene.assets.releaseAsset('texture', oldAssetId); if (this.sunTextureCache) this.sunTextureCache.delete(oldAssetId); } } } } if (texture) { for (const mesh of this.sunMeshes) { if (mesh && mesh.material && typeof mesh.material.setTexture === 'function') { mesh.material.setTexture('u_baseColorMap', texture); } } } } catch (e) { console.warn('updateSunFrame failed', e); } finally { this.sunFrameLoading = false; } }, resolveTigerMeshesFromBG(gltf) { if (!gltf || !this.scene) return []; const candidates = this.getTigerNameCandidates(); const uniqueCandidates = []; for (const name of candidates) { if (name && !uniqueCandidates.includes(name)) uniqueCandidates.push(name); } for (const name of uniqueCandidates) { if (typeof gltf.getPrimitivesByNodeName === 'function') { const meshes = gltf.getPrimitivesByNodeName(name); if (meshes && meshes.length) return meshes; } if (typeof gltf.getPrimitivesByMeshName === 'function') { const meshes = gltf.getPrimitivesByMeshName(name); if (meshes && meshes.length) return meshes; } } if (typeof gltf.getMeshes === 'function') { const meshes = gltf.getMeshes() || []; const meshNames = meshes.map(m => m && (m.name || m.el && m.el.name)).filter(Boolean); console.warn('tiger mesh not found, available mesh names:', meshNames); } else { console.warn('tiger mesh not found'); } return []; }, getTigerNameCandidates() { const defaultCandidates = ['Tiger', 'tiger', 'TIGER', '老虎', '虎', 'Laohu', 'laohu', 'hu']; if (!this.scene) return defaultCandidates; const gltfAsset = this.scene.assets && this.scene.assets.getAsset && this.scene.assets.getAsset('gltf', 'bg'); const jsonRaw = gltfAsset && gltfAsset.jsonRaw; if (!jsonRaw) return defaultCandidates; const names = []; const addIfMatch = (n) => { if (!n || typeof n !== 'string') return; if (/tiger/i.test(n) || /laohu/i.test(n) || n.includes('虎')) names.push(n); }; if (Array.isArray(jsonRaw.nodes)) { for (const node of jsonRaw.nodes) addIfMatch(node && node.name); } if (Array.isArray(jsonRaw.meshes)) { for (const mesh of jsonRaw.meshes) addIfMatch(mesh && mesh.name); } return names.length ? names.concat(defaultCandidates) : defaultCandidates; }, scheduleTigerStart() { if (!this.scene) return; if (!this.tigerShouldRun) return; if (!this.tigerStartRequested) return; if (this.tigerCompleted) return; if (this.tigerRotationTimer) return; if (!this.tigerMeshes || !this.tigerMeshes.length) return; if (this.tigerAppearTimer) return; this.tigerAppearTimer = setInterval(() => { if (!this.tigerShouldRun || this.tigerCompleted) { this.stopTigerAppearWatch(); return; } const visibleMeshes = this.getVisibleTigerMeshes(); if (!visibleMeshes.length) return; this.stopTigerAppearWatch(); this.tigerTargetMeshes = visibleMeshes; this.startTigerAudio(); this.startTigerAnimation(); }, 100); }, stopTigerAppearWatch() { if (!this.tigerAppearTimer) return; clearInterval(this.tigerAppearTimer); this.tigerAppearTimer = null; }, isTigerVisible() { return this.getVisibleTigerMeshes().length > 0; }, getVisibleTigerMeshes() { if (!this.tigerMeshes || !this.tigerMeshes.length) return []; const visible = []; for (const mesh of this.tigerMeshes) { const el = mesh && mesh.el; if (!el || typeof el.getComponent !== 'function') continue; try { const transform = el.getComponent('transform'); const scale = transform && transform.scale; if (!scale) continue; if (typeof scale === 'string') { const parts = scale.split(/\s+/).map(Number); const maxV = Math.max(...parts.filter(n => Number.isFinite(n))); if (Number.isFinite(maxV) && maxV > 0.001) visible.push(mesh); continue; } if (typeof scale === 'object') { const sx = typeof scale.x === 'number' ? scale.x : undefined; const sy = typeof scale.y === 'number' ? scale.y : undefined; const sz = typeof scale.z === 'number' ? scale.z : undefined; const maxV = Math.max(sx || 0, sy || 0, sz || 0); if (maxV > 0.001) visible.push(mesh); } } catch (e) { } } return visible; }, startTigerAnimation() { if (!this.scene) return; if (!this.tigerShouldRun) return; if (!this.tigerMeshes || !this.tigerMeshes.length) return; if (this.tigerCompleted) return; if (this.tigerRotationTimer) return; this.tigerFrameStart = 1; this.tigerFrameEnd = 460; this.tigerFrameCount = 460; const skipFrames = 29; this.tigerPlayedFrames = Math.min(skipFrames, this.tigerFrameCount); this.tigerTextureCacheLimit = 48; if (!this.tigerTextureCache) this.tigerTextureCache = new Map(); if (!this.tigerTextureQueue) this.tigerTextureQueue = []; this.prepareTigerFrames(); this.updateTigerFrame(); const intervalMs = this.getTigerFrameIntervalMs(); this.tigerRotationTimer = setInterval(() => this.updateTigerFrame(), intervalMs); }, getTigerFrameIntervalMs() { const durationMs = Number.isFinite(this.tigerAudioDurationMs) && this.tigerAudioDurationMs > 0 ? this.tigerAudioDurationMs : 46000; const frameCount = typeof this.tigerFrameCount === 'number' && this.tigerFrameCount > 0 ? this.tigerFrameCount : 460; const raw = Math.round(durationMs / frameCount); return Math.min(500, Math.max(16, raw)); }, stopTiger() { this.stopTigerAppearWatch(); if (this.tigerRotationTimer) { clearInterval(this.tigerRotationTimer); this.tigerRotationTimer = null; } this.tigerFrameLoading = false; this.tigerTargetMeshes = null; if (this.scene && this.scene.assets && this.scene.assets.releaseAsset && this.tigerTextureQueue) { for (const assetId of this.tigerTextureQueue) { if (assetId === this.tigerLastAssetId) continue; this.scene.assets.releaseAsset('texture', assetId); } } this.tigerTextureCache = null; this.tigerTextureQueue = null; this.stopTigerAudio(); this.stopTigerFramesPreload(); }, prepareTigerFrames() { if (this.tigerFramesPrepared || this.tigerFramesPreparing) return; this.tigerFramesPreparing = true; this.tigerFramesAbort = false; if (!this.tigerFrameTempPaths) this.tigerFrameTempPaths = Object.create(null); if (!this.tigerFrameDownloadTasks) this.tigerFrameDownloadTasks = Object.create(null); const startNo = 39; const endNo = 460; const maxConcurrent = 6; let nextNo = startNo; let inFlight = 0; const pump = () => { if (this.tigerFramesAbort) return; while (inFlight < maxConcurrent && nextNo <= endNo && !this.tigerFramesAbort) { const frameNo = nextNo++; const frameStr = String(frameNo).padStart(3, '0'); const assetId = `tigerx-${frameStr}`; if (this.tigerFrameTempPaths[assetId]) continue; inFlight += 1; const task = wx.downloadFile({ url: `https://ossxiaoan.4dage.com/hq-eduction-vr/tiger/Tigerx_${frameStr}.png`, success: (res) => { if (this.tigerFramesAbort) return; const tempPath = res && res.statusCode === 200 ? res.tempFilePath : ''; if (tempPath) this.tigerFrameTempPaths[assetId] = tempPath; }, complete: () => { inFlight -= 1; if (this.tigerFrameDownloadTasks) delete this.tigerFrameDownloadTasks[assetId]; if (nextNo > endNo && inFlight <= 0) { this.tigerFramesPrepared = true; this.tigerFramesPreparing = false; return; } pump(); } }); if (task && typeof task.abort === 'function') this.tigerFrameDownloadTasks[assetId] = task; } }; pump(); }, stopTigerFramesPreload() { this.tigerFramesAbort = true; this.tigerFramesPrepared = false; this.tigerFramesPreparing = false; if (this.tigerFrameDownloadTasks) { for (const k of Object.keys(this.tigerFrameDownloadTasks)) { const task = this.tigerFrameDownloadTasks[k]; if (task && typeof task.abort === 'function') { try { task.abort(); } catch (e) { } } } } this.tigerFrameDownloadTasks = null; this.tigerFrameTempPaths = null; }, async updateTigerFrame() { if (this.tigerFrameLoading) return; if (!this.scene || !this.tigerMeshes || !this.tigerMeshes.length) return; if (!this.tigerShouldRun) return; if (this.tigerCompleted) return; this.tigerFrameLoading = true; try { const frameCount = typeof this.tigerFrameCount === 'number' && this.tigerFrameCount > 0 ? this.tigerFrameCount : 460; const durationMs = Number.isFinite(this.tigerAudioDurationMs) && this.tigerAudioDurationMs > 0 ? this.tigerAudioDurationMs : 46000; const played = this.tigerPlayedFrames || 0; let desiredPlayed = played + 1; const audioCtx = this.tigerAudioCtx; if (audioCtx && this.tigerAudioStarted) { const currentTime = Number(audioCtx.currentTime); if (Number.isFinite(currentTime) && currentTime >= 0) { const p = Math.floor((currentTime * 1000 / durationMs) * frameCount) + 1; desiredPlayed = Math.max(desiredPlayed, p); } } if (desiredPlayed <= played) return; if (desiredPlayed >= frameCount) desiredPlayed = frameCount; if (played >= frameCount) { this.completeTigerAnimation(); return; } const start = typeof this.tigerFrameStart === 'number' ? this.tigerFrameStart : 1; const end = typeof this.tigerFrameEnd === 'number' ? this.tigerFrameEnd : frameCount; const rangeLen = Math.max(1, end - start + 1); const frameNo = start + ((desiredPlayed - 1) % rangeLen); this.tigerPlayedFrames = desiredPlayed; const frameStr = String(frameNo).padStart(3, '0'); const assetId = `tigerx-${frameStr}`; const localSrc = this.tigerFrameTempPaths && this.tigerFrameTempPaths[assetId]; const src = localSrc || `https://ossxiaoan.4dage.com/hq-eduction-vr/tiger/Tigerx_${frameStr}.png`; let texture = this.tigerTextureCache && this.tigerTextureCache.get(assetId); if (!texture) { const result = await this.scene.assets.loadAsset({ type: 'texture', assetId, src }); texture = result && result.value; if (texture) { if (this.tigerTextureCache) this.tigerTextureCache.set(assetId, texture); if (this.tigerTextureQueue) this.tigerTextureQueue.push(assetId); while (this.tigerTextureQueue && this.tigerTextureQueue.length > this.tigerTextureCacheLimit) { const oldAssetId = this.tigerTextureQueue.shift(); if (oldAssetId) { this.scene.assets.releaseAsset('texture', oldAssetId); if (this.tigerTextureCache) this.tigerTextureCache.delete(oldAssetId); } } } } if (texture) { this.tigerLastAssetId = assetId; const targets = (this.tigerTargetMeshes && this.tigerTargetMeshes.length) ? this.tigerTargetMeshes : this.tigerMeshes; for (const mesh of targets) { if (mesh && mesh.material && typeof mesh.material.setTexture === 'function') { mesh.material.setTexture('u_baseColorMap', texture); } } } if (this.tigerPlayedFrames >= this.tigerFrameCount) { this.completeTigerAnimation(); } } catch (e) { console.warn('updateTigerFrame failed', e); } finally { this.tigerFrameLoading = false; } }, completeTigerAnimation() { if (this.tigerRotationTimer) { clearInterval(this.tigerRotationTimer); this.tigerRotationTimer = null; } this.tigerCompleted = true; }, prepareTigerAudio() { if (this.tigerAudioCtx || this.tigerAudioPreparing) return; const url = 'https://ossxiaoan.4dage.com/hq-eduction-vr/tiger/tiger.mp3'; this.tigerAudioPrepared = false; this.tigerAudioStartPending = !!this.tigerAudioStartPending; this.tigerAudioStarted = false; this.tigerAudioPreparing = true; const createAudioCtx = (src) => { const audioCtx = wx.createInnerAudioContext(); audioCtx.src = src; audioCtx.loop = false; audioCtx.volume = 0; audioCtx.onCanplay(() => { if (this.tigerAudioPrepared) return; this.tigerAudioPrepared = true; if (this.tigerAudioStarted) return; const updateDuration = () => { if (!this.tigerAudioCtx) return; const d = Number(audioCtx.duration); if (Number.isFinite(d) && d > 0) this.tigerAudioDurationMs = Math.round(d * 1000); }; updateDuration(); setTimeout(updateDuration, 300); try { audioCtx.volume = 0; } catch (e) { } try { audioCtx.play(); } catch (e) { } setTimeout(() => { if (!this.tigerAudioCtx) return; if (this.tigerAudioStarted) return; try { audioCtx.pause(); } catch (e) { } try { if (Number(audioCtx.currentTime) > 0.1) audioCtx.seek(0); } catch (e) { } if (this.tigerAudioStartPending) this.startTigerAudio(); }, 200); }); audioCtx.onEnded(() => { this.stopTigerAudio(); this.completeTigerAnimation(); }); this.tigerAudioCtx = audioCtx; this.tigerAudioPreparing = false; try { audioCtx.play(); } catch (e) { } }; if (this.tigerAudioTempPath) { createAudioCtx(this.tigerAudioTempPath); return; } wx.downloadFile({ url, success: (res) => { const tempPath = res && res.statusCode === 200 ? res.tempFilePath : ''; if (tempPath) this.tigerAudioTempPath = tempPath; createAudioCtx(tempPath || url); }, fail: () => { createAudioCtx(url); } }); }, startTigerAudio() { if (this.tigerAudioStarted) return; if (!this.tigerAudioCtx) { this.tigerAudioStartPending = true; this.prepareTigerAudio(); } if (!this.tigerAudioCtx) return; if (this.tigerAudioStarted) return; const audioCtx = this.tigerAudioCtx; const requestedOffsetSec = typeof this.tigerAudioStartOffsetSec === 'number' ? this.tigerAudioStartOffsetSec : 2; const startOffsetSec = Math.max(0, Math.min(3, requestedOffsetSec)); const startNow = () => { if (!this.tigerAudioCtx) return; if (this.tigerAudioStarted) return; try { audioCtx.volume = 1; } catch (e) { } try { const t = Number(audioCtx.currentTime); if (startOffsetSec > 0) { if (!Number.isFinite(t) || Math.abs(t - startOffsetSec) > 0.2) audioCtx.seek(startOffsetSec); } else { if (Number.isFinite(t) && t > 0.1) audioCtx.seek(0); } } catch (e) { } try { audioCtx.play(); this.tigerAudioStarted = true; } catch (e) { this.tigerAudioStarted = false; } }; if (this.tigerAudioPrepared) { this.tigerAudioStartPending = false; startNow(); return; } this.tigerAudioStartPending = true; startNow(); }, stopTigerAudio() { if (!this.tigerAudioCtx) return; this.tigerAudioCtx.stop(); this.tigerAudioCtx.destroy(); this.tigerAudioCtx = null; this.tigerAudioPrepared = false; this.tigerAudioStartPending = false; this.tigerAudioStarted = false; this.tigerAudioPreparing = false; this.tigerAudioDurationMs = null; }, playitem1Action() { if (!this.animator1 || !this.animator2) return; console.log('start animator1'); this.animator1.play('All Animations', { loop: 0 }); setTimeout(() => { console.log('start animator2'); this.animator2.pause(); const duration = 1500; const startTime = Date.now(); const startScale = 0; const endScale = 0.38; const startPX = -0.1 const endPX = 0 const startPY = -0.5 const endPY = 0 const startPZ = -0.05 const endPZ = 0 const step = () => { const now = Date.now(); let t = (now - startTime) / duration; if (t > 1) t = 1; const s = startScale + (endScale - startScale) * t; const scaleStr = `${s} ${s} ${s}`; const p = `${startPX + (endPX - startPX) * t} ${startPY + (endPY - startPY) * t} ${startPZ + (endPZ - startPZ) * t}`; if (t >= 0.5 && !this.isStartPlay2) { this.isStartPlay2 = true; this.animator2.play('All Animations', { loop: 0 }); } this.setData({ adlScale: scaleStr, adlPos: p }); if (t < 1) { setTimeout(step, 16); // 60fps } else { const audioUrl = 'https://houseoss.4dkankan.com/project/hq-eduction-vr/public/%E5%AE%89%E5%BE%B7%E7%83%88.mp3'; if (!this.audioCtx) { const audioCtx = wx.createInnerAudioContext(); audioCtx.src = audioUrl; audioCtx.play(); audioCtx.onEnded(() => { const hideDuration = 1000; const hideStartTime = Date.now(); const startScaleHide = 0.38; const endScaleHide = 0; const hideStep = () => { const now = Date.now(); let t = (now - hideStartTime) / hideDuration; if (t > 1) t = 1; const s = startScaleHide + (endScaleHide - startScaleHide) * t; const scaleStr = `${s} ${s} ${s}`; this.setData({ adlScale: scaleStr }); if (t < 1) { setTimeout(hideStep, 16); } else { this.animator2 = null; if (this.audioCtx) { this.audioCtx.destroy(); this.audioCtx = null; } this.tigerStartRequested = true; this.scheduleTigerStart(); } }; hideStep(); }); } } }; this.setData({ adlScale: '0 0 0' }); step(); }, 1500); } } })