index.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  1. Component({
  2. behaviors: [require('../common/share-behavior').default],
  3. properties: {
  4. markerImg: {
  5. type: String
  6. },
  7. },
  8. data: {
  9. loaded: false,
  10. arReady: false,
  11. isStartPlay1: false,
  12. adlScale: '0 0 0',
  13. adlPos: '-0.1 -0.5 -0.05',
  14. sunReady: false,
  15. tigerReady: false,
  16. item1Loaded: false,
  17. item2Loaded: false,
  18. assetsProgress: 0
  19. },
  20. lifetimes: {
  21. attached() {
  22. },
  23. detached() {
  24. this.stopSunRotation();
  25. this.stopTiger();
  26. }
  27. },
  28. methods: {
  29. emitLoadState(extra) {
  30. const payload = Object.assign({
  31. arReady: !!this.data.arReady,
  32. assetsLoaded: !!this.data.loaded,
  33. item1Loaded: !!this.data.item1Loaded,
  34. item2Loaded: !!this.data.item2Loaded,
  35. assetsProgress: typeof this.data.assetsProgress === 'number' ? this.data.assetsProgress : 0,
  36. }, extra || {});
  37. this.triggerEvent('loadState', payload);
  38. },
  39. pauseSunRotationSoft() {
  40. if (this.sunRotationTimer) {
  41. clearInterval(this.sunRotationTimer);
  42. this.sunRotationTimer = null;
  43. }
  44. this.sunFrameLoading = false;
  45. },
  46. pauseTigerSoft() {
  47. this.stopTigerAppearWatch();
  48. if (this.tigerRotationTimer) {
  49. clearInterval(this.tigerRotationTimer);
  50. this.tigerRotationTimer = null;
  51. }
  52. this.tigerFrameLoading = false;
  53. this.pauseTigerAudio();
  54. },
  55. pauseTigerAudio() {
  56. if (!this.tigerAudioCtx) return;
  57. try {
  58. this.tigerAudioCtx.pause();
  59. } catch (e) {
  60. }
  61. },
  62. resumeTigerSoft() {
  63. if (!this.tigerShouldRun) return;
  64. if (this.tigerCompleted) return;
  65. if (!this.tigerMeshes || !this.tigerMeshes.length) return;
  66. const visibleMeshes = this.getVisibleTigerMeshes();
  67. if (visibleMeshes && visibleMeshes.length) this.tigerTargetMeshes = visibleMeshes;
  68. if (this.tigerAudioCtx) {
  69. try {
  70. this.tigerAudioCtx.volume = 1;
  71. } catch (e) {
  72. }
  73. try {
  74. this.tigerAudioCtx.play();
  75. } catch (e) {
  76. }
  77. }
  78. if (this.tigerRotationTimer) return;
  79. const intervalMs = this.getTigerFrameIntervalMs();
  80. this.tigerRotationTimer = setInterval(() => this.updateTigerFrame(), intervalMs);
  81. },
  82. tryStartEntryAnimation() {
  83. if (!this._isTracking) return;
  84. if (!this.data.loaded) return;
  85. if (this.data.isStartPlay1) return;
  86. if (!this.animator1 || !this.animator2) return;
  87. this.setData({ isStartPlay1: true }, () => {
  88. this.playitem1Action();
  89. });
  90. },
  91. handleReady({
  92. detail
  93. }) {
  94. const xrScene = this.scene = detail.value;
  95. // console.log('xr-scene', xrScene);
  96. this.emitLoadState({ sceneReady: true });
  97. },
  98. handleAssetsProgress: function ({
  99. detail
  100. }) {
  101. const v = detail && detail.value;
  102. let progress = 0;
  103. if (v && typeof v.progress === 'number') {
  104. progress = Math.round(Math.max(0, Math.min(1, v.progress)) * 100);
  105. } else if (v && typeof v.loaded === 'number' && typeof v.total === 'number' && v.total > 0) {
  106. progress = Math.round(Math.max(0, Math.min(1, v.loaded / v.total)) * 100);
  107. }
  108. const now = Date.now();
  109. if (!this._lastProgressEmitAt) this._lastProgressEmitAt = 0;
  110. if (!this._lastProgressValue && this._lastProgressValue !== 0) this._lastProgressValue = -1;
  111. const shouldEmit = (progress === 100) || (progress !== this._lastProgressValue && now - this._lastProgressEmitAt >= 120);
  112. if (!shouldEmit) return;
  113. this._lastProgressEmitAt = now;
  114. this._lastProgressValue = progress;
  115. this.setData({ assetsProgress: progress }, () => this.emitLoadState());
  116. },
  117. handleAssetsLoaded: function ({
  118. detail
  119. }) {
  120. console.log('assets loaded', detail.value);
  121. this.setData({
  122. loaded: true,
  123. assetsProgress: 100
  124. }, () => {
  125. this.emitLoadState();
  126. if (this._pendingPlay || this._isTracking) this.play();
  127. });
  128. },
  129. handleARReady: function ({
  130. detail
  131. }) {
  132. console.log('arReady');
  133. this.setData({
  134. arReady: true
  135. }, () => {
  136. this.emitLoadState();
  137. })
  138. },
  139. handleItem1Loaded({ detail }) {
  140. const el = detail.value.target;
  141. const animator = el.getComponent("animator");
  142. this.animator1 = animator
  143. console.log('animator1', animator)
  144. const gltf = el.getComponent("gltf");
  145. this.bgGltf = gltf;
  146. this.sunMeshes = this.resolveSunMeshesFromBG(gltf);
  147. this.setData({ sunReady: !!(this.sunMeshes && this.sunMeshes.length) });
  148. if (this.sunShouldRun) this.startSunRotation();
  149. this.tigerMeshes = this.resolveTigerMeshesFromBG(gltf);
  150. this.setData({ tigerReady: !!(this.tigerMeshes && this.tigerMeshes.length) });
  151. if (this.tigerShouldRun && this.tigerStartRequested) this.scheduleTigerStart();
  152. if (!this.data.item1Loaded) {
  153. this.setData({ item1Loaded: true }, () => this.emitLoadState());
  154. }
  155. this.tryStartEntryAnimation();
  156. },
  157. handleItem2Loaded({ detail }) {
  158. const el = detail.value.target;
  159. const animator = el.getComponent("animator");
  160. this.animator2 = animator;
  161. if (!this.data.item2Loaded) {
  162. this.setData({ item2Loaded: true }, () => this.emitLoadState());
  163. }
  164. this.tryStartEntryAnimation();
  165. },
  166. handleARTrackerState1({
  167. detail
  168. }) {
  169. // 事件的值即为`ARTracker`实例
  170. const tracker = detail.value;
  171. // 获取当前状态和错误信息
  172. console.log('tracker', tracker)
  173. const {
  174. state,
  175. } = tracker;
  176. this._lastTrackerState = state;
  177. this._isTracking = state == 2;
  178. if (state == 2) {
  179. this.play()
  180. } else {
  181. this.pause()
  182. }
  183. },
  184. play() {
  185. if (!this.data.loaded) {
  186. this._pendingPlay = true;
  187. return;
  188. }
  189. this._pendingPlay = false;
  190. this.sunShouldRun = true;
  191. this.startSunRotation();
  192. this.tigerShouldRun = true;
  193. if (!this.data.isStartPlay1) this.tigerStartRequested = false;
  194. this.prepareTigerAudio();
  195. this.prepareTigerFrames();
  196. this.tryStartEntryAnimation();
  197. if (this.tigerHasStarted) {
  198. this.resumeTigerSoft();
  199. } else if (this.tigerStartRequested) {
  200. this.scheduleTigerStart();
  201. }
  202. },
  203. pause() {
  204. this.sunShouldRun = false;
  205. this.pauseSunRotationSoft();
  206. this.tigerShouldRun = false;
  207. this._pendingPlay = false;
  208. this.pauseTigerSoft();
  209. },
  210. resolveSunMeshesFromBG(gltf) {
  211. if (!gltf || !this.scene) return [];
  212. const candidates = this.getSunNameCandidates();
  213. const uniqueCandidates = [];
  214. for (const name of candidates) {
  215. if (name && !uniqueCandidates.includes(name)) uniqueCandidates.push(name);
  216. }
  217. for (const name of uniqueCandidates) {
  218. if (typeof gltf.getPrimitivesByNodeName === 'function') {
  219. const meshes = gltf.getPrimitivesByNodeName(name);
  220. if (meshes && meshes.length) return meshes;
  221. }
  222. if (typeof gltf.getPrimitivesByMeshName === 'function') {
  223. const meshes = gltf.getPrimitivesByMeshName(name);
  224. if (meshes && meshes.length) return meshes;
  225. }
  226. }
  227. if (typeof gltf.getMeshes === 'function') {
  228. const meshes = gltf.getMeshes() || [];
  229. const meshNames = meshes.map(m => m && (m.name || m.el && m.el.name)).filter(Boolean);
  230. console.warn('sun mesh not found, available mesh names:', meshNames);
  231. } else {
  232. console.warn('sun mesh not found');
  233. }
  234. return [];
  235. },
  236. getSunNameCandidates() {
  237. const defaultCandidates = ['Sun', 'sun', 'SUN', '太阳', 'Taiyang', 'taiyang'];
  238. if (!this.scene) return defaultCandidates;
  239. const gltfAsset = this.scene.assets && this.scene.assets.getAsset && this.scene.assets.getAsset('gltf', 'bg');
  240. const jsonRaw = gltfAsset && gltfAsset.jsonRaw;
  241. if (!jsonRaw) return defaultCandidates;
  242. const names = [];
  243. const addIfMatch = (n) => {
  244. if (!n || typeof n !== 'string') return;
  245. if (/Sun/i.test(n) || n.includes('太阳')) names.push(n);
  246. };
  247. if (Array.isArray(jsonRaw.nodes)) {
  248. for (const node of jsonRaw.nodes) addIfMatch(node && node.name);
  249. }
  250. if (Array.isArray(jsonRaw.meshes)) {
  251. for (const mesh of jsonRaw.meshes) addIfMatch(mesh && mesh.name);
  252. }
  253. return names.length ? names.concat(defaultCandidates) : defaultCandidates;
  254. },
  255. startSunRotation() {
  256. if (!this.scene) return;
  257. if (!this.sunShouldRun) return;
  258. if (!this.sunMeshes || !this.sunMeshes.length) return;
  259. if (this.sunRotationTimer) return;
  260. this.sunFrameCount = 159;
  261. this.sunFrameIndex = this.sunFrameIndex || 1;
  262. this.sunTextureCacheLimit = 12;
  263. if (!this.sunTextureCache) this.sunTextureCache = new Map();
  264. if (!this.sunTextureQueue) this.sunTextureQueue = [];
  265. this.updateSunFrame();
  266. this.sunRotationTimer = setInterval(() => this.updateSunFrame(), 80);
  267. },
  268. stopSunRotation() {
  269. if (this.sunRotationTimer) {
  270. clearInterval(this.sunRotationTimer);
  271. this.sunRotationTimer = null;
  272. }
  273. this.sunFrameLoading = false;
  274. if (this.scene && this.scene.assets && this.scene.assets.releaseAsset && this.sunTextureQueue) {
  275. for (const assetId of this.sunTextureQueue) {
  276. this.scene.assets.releaseAsset('texture', assetId);
  277. }
  278. }
  279. this.sunTextureCache = null;
  280. this.sunTextureQueue = null;
  281. },
  282. async updateSunFrame() {
  283. if (this.sunFrameLoading) return;
  284. if (!this.scene || !this.sunMeshes || !this.sunMeshes.length) return;
  285. if (!this.sunShouldRun) return;
  286. this.sunFrameLoading = true;
  287. try {
  288. const frame = this.sunFrameIndex;
  289. this.sunFrameIndex = frame >= this.sunFrameCount ? 1 : frame + 1;
  290. const frameStr = String(frame).padStart(3, '0');
  291. const assetId = `sun-${frameStr}`;
  292. const src = `https://ossxiaoan.4dage.com/hq-eduction-vr/sun/Sun_${frameStr}.png`;
  293. let texture = this.sunTextureCache && this.sunTextureCache.get(assetId);
  294. if (!texture) {
  295. const result = await this.scene.assets.loadAsset({ type: 'texture', assetId, src });
  296. texture = result && result.value;
  297. if (texture) {
  298. if (this.sunTextureCache) this.sunTextureCache.set(assetId, texture);
  299. if (this.sunTextureQueue) this.sunTextureQueue.push(assetId);
  300. while (this.sunTextureQueue && this.sunTextureQueue.length > this.sunTextureCacheLimit) {
  301. const oldAssetId = this.sunTextureQueue.shift();
  302. if (oldAssetId) {
  303. this.scene.assets.releaseAsset('texture', oldAssetId);
  304. if (this.sunTextureCache) this.sunTextureCache.delete(oldAssetId);
  305. }
  306. }
  307. }
  308. }
  309. if (texture) {
  310. for (const mesh of this.sunMeshes) {
  311. if (mesh && mesh.material && typeof mesh.material.setTexture === 'function') {
  312. mesh.material.setTexture('u_baseColorMap', texture);
  313. }
  314. }
  315. }
  316. } catch (e) {
  317. console.warn('updateSunFrame failed', e);
  318. } finally {
  319. this.sunFrameLoading = false;
  320. }
  321. },
  322. resolveTigerMeshesFromBG(gltf) {
  323. if (!gltf || !this.scene) return [];
  324. const candidates = this.getTigerNameCandidates();
  325. const uniqueCandidates = [];
  326. for (const name of candidates) {
  327. if (name && !uniqueCandidates.includes(name)) uniqueCandidates.push(name);
  328. }
  329. for (const name of uniqueCandidates) {
  330. if (typeof gltf.getPrimitivesByNodeName === 'function') {
  331. const meshes = gltf.getPrimitivesByNodeName(name);
  332. if (meshes && meshes.length) return meshes;
  333. }
  334. if (typeof gltf.getPrimitivesByMeshName === 'function') {
  335. const meshes = gltf.getPrimitivesByMeshName(name);
  336. if (meshes && meshes.length) return meshes;
  337. }
  338. }
  339. if (typeof gltf.getMeshes === 'function') {
  340. const meshes = gltf.getMeshes() || [];
  341. const meshNames = meshes.map(m => m && (m.name || m.el && m.el.name)).filter(Boolean);
  342. console.warn('tiger mesh not found, available mesh names:', meshNames);
  343. } else {
  344. console.warn('tiger mesh not found');
  345. }
  346. return [];
  347. },
  348. getTigerNameCandidates() {
  349. const defaultCandidates = ['Tiger', 'tiger', 'TIGER', '老虎', '虎', 'Laohu', 'laohu', 'hu'];
  350. if (!this.scene) return defaultCandidates;
  351. const gltfAsset = this.scene.assets && this.scene.assets.getAsset && this.scene.assets.getAsset('gltf', 'bg');
  352. const jsonRaw = gltfAsset && gltfAsset.jsonRaw;
  353. if (!jsonRaw) return defaultCandidates;
  354. const names = [];
  355. const addIfMatch = (n) => {
  356. if (!n || typeof n !== 'string') return;
  357. if (/tiger/i.test(n) || /laohu/i.test(n) || n.includes('虎')) names.push(n);
  358. };
  359. if (Array.isArray(jsonRaw.nodes)) {
  360. for (const node of jsonRaw.nodes) addIfMatch(node && node.name);
  361. }
  362. if (Array.isArray(jsonRaw.meshes)) {
  363. for (const mesh of jsonRaw.meshes) addIfMatch(mesh && mesh.name);
  364. }
  365. return names.length ? names.concat(defaultCandidates) : defaultCandidates;
  366. },
  367. scheduleTigerStart() {
  368. if (!this.scene) return;
  369. if (!this.tigerShouldRun) return;
  370. if (!this.tigerStartRequested) return;
  371. if (this.tigerCompleted) return;
  372. if (this.tigerRotationTimer) return;
  373. if (!this.tigerMeshes || !this.tigerMeshes.length) return;
  374. if (this.tigerAppearTimer) return;
  375. this.tigerAppearTimer = setInterval(() => {
  376. if (!this.tigerShouldRun || this.tigerCompleted) {
  377. this.stopTigerAppearWatch();
  378. return;
  379. }
  380. const visibleMeshes = this.getVisibleTigerMeshes();
  381. if (!visibleMeshes.length) return;
  382. this.stopTigerAppearWatch();
  383. this.tigerTargetMeshes = visibleMeshes;
  384. this.startTigerAudio();
  385. this.startTigerAnimation();
  386. }, 100);
  387. },
  388. stopTigerAppearWatch() {
  389. if (!this.tigerAppearTimer) return;
  390. clearInterval(this.tigerAppearTimer);
  391. this.tigerAppearTimer = null;
  392. },
  393. isTigerVisible() {
  394. return this.getVisibleTigerMeshes().length > 0;
  395. },
  396. getVisibleTigerMeshes() {
  397. if (!this.tigerMeshes || !this.tigerMeshes.length) return [];
  398. const visible = [];
  399. for (const mesh of this.tigerMeshes) {
  400. const el = mesh && mesh.el;
  401. if (!el || typeof el.getComponent !== 'function') continue;
  402. try {
  403. const transform = el.getComponent('transform');
  404. const scale = transform && transform.scale;
  405. if (!scale) continue;
  406. if (typeof scale === 'string') {
  407. const parts = scale.split(/\s+/).map(Number);
  408. const maxV = Math.max(...parts.filter(n => Number.isFinite(n)));
  409. if (Number.isFinite(maxV) && maxV > 0.001) visible.push(mesh);
  410. continue;
  411. }
  412. if (typeof scale === 'object') {
  413. const sx = typeof scale.x === 'number' ? scale.x : undefined;
  414. const sy = typeof scale.y === 'number' ? scale.y : undefined;
  415. const sz = typeof scale.z === 'number' ? scale.z : undefined;
  416. const maxV = Math.max(sx || 0, sy || 0, sz || 0);
  417. if (maxV > 0.001) visible.push(mesh);
  418. }
  419. } catch (e) {
  420. }
  421. }
  422. return visible;
  423. },
  424. startTigerAnimation() {
  425. if (!this.scene) return;
  426. if (!this.tigerShouldRun) return;
  427. if (!this.tigerMeshes || !this.tigerMeshes.length) return;
  428. if (this.tigerCompleted) return;
  429. if (this.tigerRotationTimer) return;
  430. this.tigerHasStarted = true;
  431. this.tigerFrameStart = 1;
  432. this.tigerFrameEnd = 460;
  433. this.tigerFrameCount = 460;
  434. const skipFrames = 29;
  435. this.tigerPlayedFrames = Math.min(skipFrames, this.tigerFrameCount);
  436. this.tigerTextureCacheLimit = 48;
  437. if (!this.tigerTextureCache) this.tigerTextureCache = new Map();
  438. if (!this.tigerTextureQueue) this.tigerTextureQueue = [];
  439. this.prepareTigerFrames();
  440. this.updateTigerFrame();
  441. const intervalMs = this.getTigerFrameIntervalMs();
  442. this.tigerRotationTimer = setInterval(() => this.updateTigerFrame(), intervalMs);
  443. },
  444. getTigerFrameIntervalMs() {
  445. const durationMs = Number.isFinite(this.tigerAudioDurationMs) && this.tigerAudioDurationMs > 0
  446. ? this.tigerAudioDurationMs
  447. : 46000;
  448. const frameCount = typeof this.tigerFrameCount === 'number' && this.tigerFrameCount > 0 ? this.tigerFrameCount : 460;
  449. const raw = Math.round(durationMs / frameCount);
  450. return Math.min(500, Math.max(16, raw));
  451. },
  452. stopTiger() {
  453. this.stopTigerAppearWatch();
  454. if (this.tigerRotationTimer) {
  455. clearInterval(this.tigerRotationTimer);
  456. this.tigerRotationTimer = null;
  457. }
  458. this.tigerFrameLoading = false;
  459. this.tigerTargetMeshes = null;
  460. this.tigerHasStarted = false;
  461. if (this.scene && this.scene.assets && this.scene.assets.releaseAsset && this.tigerTextureQueue) {
  462. for (const assetId of this.tigerTextureQueue) {
  463. if (assetId === this.tigerLastAssetId) continue;
  464. this.scene.assets.releaseAsset('texture', assetId);
  465. }
  466. }
  467. this.tigerTextureCache = null;
  468. this.tigerTextureQueue = null;
  469. this.stopTigerAudio();
  470. this.stopTigerFramesPreload();
  471. },
  472. prepareTigerFrames() {
  473. if (this.tigerFramesPrepared || this.tigerFramesPreparing) return;
  474. this.tigerFramesPreparing = true;
  475. this.tigerFramesAbort = false;
  476. if (!this.tigerFrameTempPaths) this.tigerFrameTempPaths = Object.create(null);
  477. if (!this.tigerFrameDownloadTasks) this.tigerFrameDownloadTasks = Object.create(null);
  478. const startNo = 39;
  479. const endNo = 460;
  480. const maxConcurrent = 6;
  481. let nextNo = startNo;
  482. let inFlight = 0;
  483. const pump = () => {
  484. if (this.tigerFramesAbort) return;
  485. while (inFlight < maxConcurrent && nextNo <= endNo && !this.tigerFramesAbort) {
  486. const frameNo = nextNo++;
  487. const frameStr = String(frameNo).padStart(3, '0');
  488. const assetId = `tigerx-${frameStr}`;
  489. if (this.tigerFrameTempPaths[assetId]) continue;
  490. inFlight += 1;
  491. const task = wx.downloadFile({
  492. url: `https://ossxiaoan.4dage.com/hq-eduction-vr/tiger/Tigerx_${frameStr}.png`,
  493. success: (res) => {
  494. if (this.tigerFramesAbort) return;
  495. const tempPath = res && res.statusCode === 200 ? res.tempFilePath : '';
  496. if (tempPath) this.tigerFrameTempPaths[assetId] = tempPath;
  497. },
  498. complete: () => {
  499. inFlight -= 1;
  500. if (this.tigerFrameDownloadTasks) delete this.tigerFrameDownloadTasks[assetId];
  501. if (nextNo > endNo && inFlight <= 0) {
  502. this.tigerFramesPrepared = true;
  503. this.tigerFramesPreparing = false;
  504. return;
  505. }
  506. pump();
  507. }
  508. });
  509. if (task && typeof task.abort === 'function') this.tigerFrameDownloadTasks[assetId] = task;
  510. }
  511. };
  512. pump();
  513. },
  514. stopTigerFramesPreload() {
  515. this.tigerFramesAbort = true;
  516. this.tigerFramesPrepared = false;
  517. this.tigerFramesPreparing = false;
  518. if (this.tigerFrameDownloadTasks) {
  519. for (const k of Object.keys(this.tigerFrameDownloadTasks)) {
  520. const task = this.tigerFrameDownloadTasks[k];
  521. if (task && typeof task.abort === 'function') {
  522. try {
  523. task.abort();
  524. } catch (e) {
  525. }
  526. }
  527. }
  528. }
  529. this.tigerFrameDownloadTasks = null;
  530. this.tigerFrameTempPaths = null;
  531. },
  532. async updateTigerFrame() {
  533. if (this.tigerFrameLoading) return;
  534. if (!this.scene || !this.tigerMeshes || !this.tigerMeshes.length) return;
  535. if (!this.tigerShouldRun) return;
  536. if (this.tigerCompleted) return;
  537. this.tigerFrameLoading = true;
  538. try {
  539. const frameCount = typeof this.tigerFrameCount === 'number' && this.tigerFrameCount > 0 ? this.tigerFrameCount : 460;
  540. const durationMs = Number.isFinite(this.tigerAudioDurationMs) && this.tigerAudioDurationMs > 0
  541. ? this.tigerAudioDurationMs
  542. : 46000;
  543. const played = this.tigerPlayedFrames || 0;
  544. let desiredPlayed = played + 1;
  545. const audioCtx = this.tigerAudioCtx;
  546. if (audioCtx && this.tigerAudioStarted) {
  547. const currentTime = Number(audioCtx.currentTime);
  548. if (Number.isFinite(currentTime) && currentTime >= 0) {
  549. const p = Math.floor((currentTime * 1000 / durationMs) * frameCount) + 1;
  550. desiredPlayed = Math.max(desiredPlayed, p);
  551. }
  552. }
  553. if (desiredPlayed <= played) return;
  554. if (desiredPlayed >= frameCount) desiredPlayed = frameCount;
  555. if (played >= frameCount) {
  556. this.completeTigerAnimation();
  557. return;
  558. }
  559. const start = typeof this.tigerFrameStart === 'number' ? this.tigerFrameStart : 1;
  560. const end = typeof this.tigerFrameEnd === 'number' ? this.tigerFrameEnd : frameCount;
  561. const rangeLen = Math.max(1, end - start + 1);
  562. const frameNo = start + ((desiredPlayed - 1) % rangeLen);
  563. this.tigerPlayedFrames = desiredPlayed;
  564. const frameStr = String(frameNo).padStart(3, '0');
  565. const assetId = `tigerx-${frameStr}`;
  566. const localSrc = this.tigerFrameTempPaths && this.tigerFrameTempPaths[assetId];
  567. const src = localSrc || `https://ossxiaoan.4dage.com/hq-eduction-vr/tiger/Tigerx_${frameStr}.png`;
  568. let texture = this.tigerTextureCache && this.tigerTextureCache.get(assetId);
  569. if (!texture) {
  570. const result = await this.scene.assets.loadAsset({ type: 'texture', assetId, src });
  571. texture = result && result.value;
  572. if (texture) {
  573. if (this.tigerTextureCache) this.tigerTextureCache.set(assetId, texture);
  574. if (this.tigerTextureQueue) this.tigerTextureQueue.push(assetId);
  575. while (this.tigerTextureQueue && this.tigerTextureQueue.length > this.tigerTextureCacheLimit) {
  576. const oldAssetId = this.tigerTextureQueue.shift();
  577. if (oldAssetId) {
  578. this.scene.assets.releaseAsset('texture', oldAssetId);
  579. if (this.tigerTextureCache) this.tigerTextureCache.delete(oldAssetId);
  580. }
  581. }
  582. }
  583. }
  584. if (texture) {
  585. this.tigerLastAssetId = assetId;
  586. const targets = (this.tigerTargetMeshes && this.tigerTargetMeshes.length) ? this.tigerTargetMeshes : this.tigerMeshes;
  587. for (const mesh of targets) {
  588. if (mesh && mesh.material && typeof mesh.material.setTexture === 'function') {
  589. mesh.material.setTexture('u_baseColorMap', texture);
  590. }
  591. }
  592. }
  593. if (this.tigerPlayedFrames >= this.tigerFrameCount) {
  594. this.completeTigerAnimation();
  595. }
  596. } catch (e) {
  597. console.warn('updateTigerFrame failed', e);
  598. } finally {
  599. this.tigerFrameLoading = false;
  600. }
  601. },
  602. completeTigerAnimation() {
  603. if (this.tigerRotationTimer) {
  604. clearInterval(this.tigerRotationTimer);
  605. this.tigerRotationTimer = null;
  606. }
  607. this.tigerCompleted = true;
  608. },
  609. prepareTigerAudio() {
  610. if (this.tigerAudioCtx || this.tigerAudioPreparing) return;
  611. const url = 'https://ossxiaoan.4dage.com/hq-eduction-vr/tiger/tiger.mp3';
  612. this.tigerAudioPrepared = false;
  613. this.tigerAudioStartPending = !!this.tigerAudioStartPending;
  614. this.tigerAudioStarted = false;
  615. this.tigerAudioPreparing = true;
  616. const createAudioCtx = (src) => {
  617. const audioCtx = wx.createInnerAudioContext();
  618. audioCtx.src = src;
  619. audioCtx.loop = false;
  620. audioCtx.volume = 0;
  621. audioCtx.onCanplay(() => {
  622. if (this.tigerAudioPrepared) return;
  623. this.tigerAudioPrepared = true;
  624. if (this.tigerAudioStarted) return;
  625. const updateDuration = () => {
  626. if (!this.tigerAudioCtx) return;
  627. const d = Number(audioCtx.duration);
  628. if (Number.isFinite(d) && d > 0) this.tigerAudioDurationMs = Math.round(d * 1000);
  629. };
  630. updateDuration();
  631. setTimeout(updateDuration, 300);
  632. try {
  633. audioCtx.volume = 0;
  634. } catch (e) {
  635. }
  636. try {
  637. audioCtx.play();
  638. } catch (e) {
  639. }
  640. setTimeout(() => {
  641. if (!this.tigerAudioCtx) return;
  642. if (this.tigerAudioStarted) return;
  643. try {
  644. audioCtx.pause();
  645. } catch (e) {
  646. }
  647. try {
  648. if (Number(audioCtx.currentTime) > 0.1) audioCtx.seek(0);
  649. } catch (e) {
  650. }
  651. if (this.tigerAudioStartPending) this.startTigerAudio();
  652. }, 200);
  653. });
  654. audioCtx.onEnded(() => {
  655. this.stopTigerAudio();
  656. this.completeTigerAnimation();
  657. });
  658. this.tigerAudioCtx = audioCtx;
  659. this.tigerAudioPreparing = false;
  660. try {
  661. audioCtx.play();
  662. } catch (e) {
  663. }
  664. };
  665. if (this.tigerAudioTempPath) {
  666. createAudioCtx(this.tigerAudioTempPath);
  667. return;
  668. }
  669. wx.downloadFile({
  670. url,
  671. success: (res) => {
  672. const tempPath = res && res.statusCode === 200 ? res.tempFilePath : '';
  673. if (tempPath) this.tigerAudioTempPath = tempPath;
  674. createAudioCtx(tempPath || url);
  675. },
  676. fail: () => {
  677. createAudioCtx(url);
  678. }
  679. });
  680. },
  681. startTigerAudio() {
  682. if (this.tigerAudioStarted) return;
  683. if (!this.tigerAudioCtx) {
  684. this.tigerAudioStartPending = true;
  685. this.prepareTigerAudio();
  686. }
  687. if (!this.tigerAudioCtx) return;
  688. if (this.tigerAudioStarted) return;
  689. const audioCtx = this.tigerAudioCtx;
  690. const requestedOffsetSec = typeof this.tigerAudioStartOffsetSec === 'number' ? this.tigerAudioStartOffsetSec : 2;
  691. const startOffsetSec = Math.max(0, Math.min(3, requestedOffsetSec));
  692. const startNow = () => {
  693. if (!this.tigerAudioCtx) return;
  694. if (this.tigerAudioStarted) return;
  695. try {
  696. audioCtx.volume = 1;
  697. } catch (e) {
  698. }
  699. try {
  700. const t = Number(audioCtx.currentTime);
  701. if (startOffsetSec > 0) {
  702. if (!Number.isFinite(t) || Math.abs(t - startOffsetSec) > 0.2) audioCtx.seek(startOffsetSec);
  703. } else {
  704. if (Number.isFinite(t) && t > 0.1) audioCtx.seek(0);
  705. }
  706. } catch (e) {
  707. }
  708. try {
  709. audioCtx.play();
  710. this.tigerAudioStarted = true;
  711. } catch (e) {
  712. this.tigerAudioStarted = false;
  713. }
  714. };
  715. if (this.tigerAudioPrepared) {
  716. this.tigerAudioStartPending = false;
  717. startNow();
  718. return;
  719. }
  720. this.tigerAudioStartPending = true;
  721. startNow();
  722. },
  723. stopTigerAudio() {
  724. if (!this.tigerAudioCtx) return;
  725. this.tigerAudioCtx.stop();
  726. this.tigerAudioCtx.destroy();
  727. this.tigerAudioCtx = null;
  728. this.tigerAudioPrepared = false;
  729. this.tigerAudioStartPending = false;
  730. this.tigerAudioStarted = false;
  731. this.tigerAudioPreparing = false;
  732. this.tigerAudioDurationMs = null;
  733. },
  734. playitem1Action() {
  735. if (!this.animator1 || !this.animator2) return;
  736. console.log('start animator1');
  737. this.animator1.play('All Animations', {
  738. loop: 0
  739. });
  740. setTimeout(() => {
  741. console.log('start animator2');
  742. this.animator2.pause();
  743. const duration = 1500;
  744. const startTime = Date.now();
  745. const startScale = 0;
  746. const endScale = 0.38;
  747. const startPX = -0.1
  748. const endPX = 0
  749. const startPY = -0.5
  750. const endPY = 0
  751. const startPZ = -0.05
  752. const endPZ = 0
  753. const step = () => {
  754. const now = Date.now();
  755. let t = (now - startTime) / duration;
  756. if (t > 1) t = 1;
  757. const s = startScale + (endScale - startScale) * t;
  758. const scaleStr = `${s} ${s} ${s}`;
  759. const p = `${startPX + (endPX - startPX) * t} ${startPY + (endPY - startPY) * t} ${startPZ + (endPZ - startPZ) * t}`;
  760. if (t >= 0.5 && !this.isStartPlay2) {
  761. this.isStartPlay2 = true;
  762. this.animator2.play('All Animations', {
  763. loop: 0
  764. });
  765. }
  766. this.setData({
  767. adlScale: scaleStr,
  768. adlPos: p
  769. });
  770. if (t < 1) {
  771. setTimeout(step, 16); // 60fps
  772. } else {
  773. const audioUrl = 'https://houseoss.4dkankan.com/project/hq-eduction-vr/public/%E5%AE%89%E5%BE%B7%E7%83%88.mp3';
  774. if (!this.audioCtx) {
  775. const audioCtx = wx.createInnerAudioContext();
  776. audioCtx.src = audioUrl;
  777. audioCtx.play();
  778. audioCtx.onEnded(() => {
  779. const hideDuration = 1000;
  780. const hideStartTime = Date.now();
  781. const startScaleHide = 0.38;
  782. const endScaleHide = 0;
  783. const hideStep = () => {
  784. const now = Date.now();
  785. let t = (now - hideStartTime) / hideDuration;
  786. if (t > 1) t = 1;
  787. const s = startScaleHide + (endScaleHide - startScaleHide) * t;
  788. const scaleStr = `${s} ${s} ${s}`;
  789. this.setData({
  790. adlScale: scaleStr
  791. });
  792. if (t < 1) {
  793. setTimeout(hideStep, 16);
  794. } else {
  795. this.animator2 = null;
  796. if (this.audioCtx) {
  797. this.audioCtx.destroy();
  798. this.audioCtx = null;
  799. }
  800. this.tigerStartRequested = true;
  801. this.scheduleTigerStart();
  802. }
  803. };
  804. hideStep();
  805. });
  806. }
  807. }
  808. };
  809. this.setData({
  810. adlScale: '0 0 0'
  811. });
  812. step();
  813. }, 1500);
  814. }
  815. }
  816. })