您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

aframe-with-encantar.js 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. /**
  2. * A-Frame plugin for encantar.js
  3. * @author Alexandre Martins <alemartf(at)gmail.com> (https://github.com/alemart/encantar-js)
  4. * @license LGPL-3.0-or-later
  5. */
  6. (function() {
  7. /* Usage of the indicated versions is encouraged */
  8. __THIS_PLUGIN_HAS_BEEN_TESTED_WITH__({
  9. 'encantar.js': { version: '0.3.0' },
  10. 'A-Frame': { version: '1.4.2' }
  11. });
  12. /**
  13. * AR Utilities
  14. */
  15. const ARUtils = () => ({
  16. findTrackedImage(frame, name = '')
  17. {
  18. if(frame === null)
  19. return null;
  20. for(const result of frame.results) {
  21. if(result.tracker.type == 'image-tracker') {
  22. for(const trackable of result.trackables) {
  23. if(name === '' || name === trackable.referenceImage.name) {
  24. return {
  25. projectionMatrix: result.viewer.view.projectionMatrix,
  26. viewMatrixInverse: result.viewer.pose.transform.matrix,
  27. modelMatrix: trackable.pose.transform.matrix,
  28. };
  29. }
  30. }
  31. }
  32. }
  33. return null;
  34. },
  35. });
  36. /* ========================================================================= */
  37. /**
  38. * AR System
  39. */
  40. AFRAME.registerSystem('ar', {
  41. // el;
  42. // data;
  43. // schema;
  44. session: null,
  45. frame: null,
  46. utils: ARUtils(),
  47. _started: false,
  48. _components: [],
  49. _roots: [],
  50. init()
  51. {
  52. const scene = this.el;
  53. // validate
  54. if(!scene.getAttribute('ar-session')) {
  55. scene.setAttribute('ar-session', {}); // use a default ar-session
  56. //throw new Error('Missing ar-session in a-scene'); // other errors will appear
  57. }
  58. // initial setup
  59. scene.setAttribute('vr-mode-ui', { enabled: false });
  60. scene.setAttribute('embedded', true);
  61. scene.setAttribute('renderer', { alpha: true });
  62. // pause the scene until we're ready
  63. scene.addEventListener('ar-started', () => {
  64. scene.play();
  65. });
  66. scene.addEventListener('loaded', () => {
  67. //scene.pause();
  68. Promise.resolve().then(() => scene.pause());
  69. });
  70. /*
  71. // we take control of the rendering
  72. scene.addEventListener('loaded', () => {
  73. scene.renderer.setAnimationLoop(null);
  74. });
  75. */
  76. },
  77. tick()
  78. {
  79. const scene = this.el;
  80. // we take control of the rendering
  81. scene.renderer.setAnimationLoop(null);
  82. // manually update the roots
  83. for(let i = 0; i < this._roots.length; i++)
  84. this._roots[i].teek();
  85. },
  86. startSession()
  87. {
  88. if(this._started)
  89. throw new Error('Can\'t start an AR session twice');
  90. this._started = true;
  91. for(const component of this._components)
  92. component.validate();
  93. return Speedy.Promise.all([
  94. this._loadSources(),
  95. this._loadTrackers(),
  96. this._loadViewport(),
  97. this._loadPreferences(),
  98. ])
  99. .then(([
  100. sources,
  101. trackers,
  102. viewport,
  103. preferences,
  104. ]) => AR.startSession(
  105. Object.assign({}, preferences, {
  106. sources,
  107. trackers,
  108. viewport
  109. })
  110. ))
  111. .then(session => {
  112. // setup
  113. this.session = session;
  114. this._configureSession();
  115. this._startAnimationLoop();
  116. // we're done!
  117. const scene = this.el;
  118. scene.emit('ar-started', { ar: this });
  119. return session;
  120. })
  121. .catch(error => {
  122. console.error(error);
  123. throw error;
  124. });
  125. },
  126. register(component)
  127. {
  128. this._register(this._components, component);
  129. },
  130. unregister(component)
  131. {
  132. this._unregister(this._components, component);
  133. },
  134. registerRoot(component)
  135. {
  136. this._register(this._roots, component);
  137. },
  138. unregisterRoot(component)
  139. {
  140. this._unregister(this._roots, component);
  141. },
  142. _register(arr, component)
  143. {
  144. const j = arr.indexOf(component);
  145. if(j < 0)
  146. arr.push(component);
  147. },
  148. _unregister(arr, component)
  149. {
  150. const j = arr.indexOf(component);
  151. if(j >= 0)
  152. arr.splice(j, 1);
  153. },
  154. _configureSession()
  155. {
  156. const scene = this.el;
  157. const session = this.session;
  158. if(session.viewport.canvas !== scene.canvas) {
  159. session.end();
  160. throw new Error('Invalid A-Frame canvas');
  161. }
  162. session.addEventListener('end', () => {
  163. this.frame = null;
  164. });
  165. session.viewport.addEventListener('resize', () => {
  166. // timeout : internals of aframe (a-scene.js)
  167. setTimeout(() => this._resize(), 200);
  168. this._resize();
  169. });
  170. this._resize(); // initial setup
  171. },
  172. _startAnimationLoop()
  173. {
  174. const scene = this.el;
  175. const session = this.session;
  176. scene.object3D.background = null;
  177. const animate = (time, frame) => {
  178. this.frame = frame;
  179. scene.render();
  180. session.requestAnimationFrame(animate);
  181. };
  182. session.requestAnimationFrame(animate);
  183. },
  184. _resize()
  185. {
  186. const scene = this.el;
  187. const size = this.session.viewport.virtualSize;
  188. scene.renderer.setPixelRatio(1.0);
  189. scene.renderer.setSize(size.width, size.height, false);
  190. },
  191. _loadTrackers()
  192. {
  193. const scene = this.el;
  194. const groups = Array.from(
  195. scene.querySelectorAll('[ar-trackers]'),
  196. el => el.components['ar-trackers']
  197. );
  198. if(groups.length > 1)
  199. throw new Error('Can\'t define multiple groups of ar-trackers');
  200. else if(groups.length == 0)
  201. throw new Error('Missing ar-trackers');
  202. return groups[0].trackers();
  203. },
  204. _loadSources()
  205. {
  206. const scene = this.el;
  207. const groups = Array.from(
  208. scene.querySelectorAll('[ar-sources]'),
  209. el => el.components['ar-sources']
  210. );
  211. if(groups.length > 1)
  212. throw new Error('Can\'t define multiple groups of ar-sources');
  213. else if(groups.length == 0)
  214. throw new Error('Missing ar-sources');
  215. return groups[0].sources();
  216. },
  217. _loadViewport()
  218. {
  219. const scene = this.el;
  220. const viewports = Array.from(
  221. scene.querySelectorAll('[ar-viewport]'),
  222. el => el.components['ar-viewport']
  223. );
  224. if(viewports.length > 1)
  225. throw new Error('Can\'t define multiple ar-viewport\'s');
  226. else if(viewports.length == 0)
  227. throw new Error('Missing ar-viewport');
  228. return viewports[0].viewport();
  229. },
  230. _loadPreferences()
  231. {
  232. const scene = this.el;
  233. const sessionComponent = scene.components['ar-session'];
  234. if(sessionComponent === undefined)
  235. throw new Error('Missing ar-session in a-scene');
  236. return sessionComponent.preferences();
  237. },
  238. });
  239. /**
  240. * AR Component
  241. */
  242. const ARComponent = obj => Object.assign({}, obj, {
  243. // el;
  244. // data;
  245. // id;
  246. init()
  247. {
  248. Object.defineProperty(this, 'ar', {
  249. get: function() { return this.el.sceneEl.systems.ar; }
  250. });
  251. this.ar.register(this);
  252. if(obj.init !== undefined)
  253. obj.init.call(this);
  254. if(this.el.sceneEl.hasLoaded)
  255. this.validate();
  256. },
  257. remove()
  258. {
  259. if(obj.remove !== undefined)
  260. obj.remove.call(this);
  261. this.ar.unregister(this);
  262. },
  263. validate()
  264. {
  265. if(obj.validate !== undefined)
  266. obj.validate.call(this);
  267. }
  268. });
  269. /**
  270. * AR Session
  271. */
  272. AFRAME.registerComponent('ar-session', ARComponent({
  273. schema: {
  274. /** session mode: "immersive" | "inline" */
  275. 'mode': { type: 'string', default: 'immersive' },
  276. /** show stats panel? */
  277. 'stats': { type: 'boolean', default: false },
  278. /** show gizmos? */
  279. 'gizmos': { type: 'boolean', default: false },
  280. /** start the session automatically? */
  281. 'autoplay': { type: 'boolean', default: true },
  282. },
  283. sceneOnly: true,
  284. _started: false,
  285. init()
  286. {
  287. this._started = false;
  288. },
  289. play()
  290. {
  291. // start the session (run once)
  292. if(!this._started) {
  293. this._started = true;
  294. if(this.data.autoplay)
  295. this.startSession();
  296. }
  297. },
  298. remove()
  299. {
  300. // end the session
  301. if(this.ar.session !== null)
  302. this.ar.session.end();
  303. },
  304. preferences()
  305. {
  306. return {
  307. mode: this.data.mode,
  308. stats: this.data.stats,
  309. gizmos: this.data.gizmos
  310. };
  311. },
  312. startSession()
  313. {
  314. return this.ar.startSession();
  315. },
  316. }));
  317. /* ========================================================================= */
  318. /**
  319. * AR Camera
  320. */
  321. AFRAME.registerComponent('ar-camera', ARComponent({
  322. dependencies: ['camera'],
  323. init()
  324. {
  325. const el = this.el;
  326. el.setAttribute('camera', { active: true });
  327. el.setAttribute('wasd-controls', { enabled: false });
  328. el.setAttribute('look-controls', { enabled: false });
  329. el.setAttribute('position', { x: 0, y: 0, z: 0 }); // A-Frame sets y = 1.6m for VR
  330. const camera = el.getObject3D('camera');
  331. camera.matrixAutoUpdate = false;
  332. },
  333. tick()
  334. {
  335. const ar = this.ar;
  336. const el = this.el;
  337. const tracked = ar.utils.findTrackedImage(ar.frame);
  338. if(tracked === null)
  339. return;
  340. const camera = el.getObject3D('camera');
  341. camera.projectionMatrix.fromArray(tracked.projectionMatrix.read());
  342. camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
  343. camera.matrix.fromArray(tracked.viewMatrixInverse.read());
  344. camera.updateMatrixWorld(true);
  345. //console.log('projectionMatrix', tracked.projectionMatrix.read());
  346. //console.log('viewMatrixInverse', tracked.viewMatrixInverse.read());
  347. },
  348. validate()
  349. {
  350. if(!this.el.getAttribute('camera'))
  351. throw new Error('Incorrect ar-camera');
  352. if(this.el.parentNode !== this.el.sceneEl)
  353. throw new Error('ar-camera must be a direct child of a-scene');
  354. },
  355. }));
  356. AFRAME.registerPrimitive('ar-camera', {
  357. defaultComponents: {
  358. 'ar-camera': {},
  359. 'camera': {}
  360. }
  361. });
  362. /**
  363. * AR Root node
  364. */
  365. AFRAME.registerComponent('ar-root', ARComponent({
  366. schema: {
  367. /** the name of a reference image (target) or the empty string (to match any target) */
  368. 'referenceImage': { type: 'string', default: '' },
  369. },
  370. _origin: null,
  371. _firstRun: true,
  372. init()
  373. {
  374. const origin = new THREE.Group();
  375. origin.matrixAutoUpdate = false;
  376. const root = this.el.object3D;
  377. root.parent.add(origin);
  378. origin.add(root);
  379. this._origin = origin;
  380. this._firstRun = true;
  381. this.ar.registerRoot(this);
  382. },
  383. remove()
  384. {
  385. const origin = this._origin;
  386. const root = this.el.object3D;
  387. origin.parent.add(root);
  388. origin.removeFromParent();
  389. this._origin = null;
  390. this.ar.unregisterRoot(this);
  391. },
  392. play()
  393. {
  394. const origin = this._origin;
  395. origin.visible = true;
  396. if(this._firstRun) {
  397. this._firstRun = false;
  398. origin.visible = false;
  399. this.el.pause();
  400. }
  401. },
  402. pause()
  403. {
  404. const origin = this._origin;
  405. origin.visible = false;
  406. },
  407. teek()
  408. {
  409. const ar = this.ar;
  410. const targetName = this.data.referenceImage;
  411. const tracked = ar.utils.findTrackedImage(ar.frame, targetName);
  412. if(tracked === null) {
  413. this.el.pause();
  414. return;
  415. }
  416. const origin = this._origin;
  417. origin.matrix.fromArray(tracked.modelMatrix.read());
  418. origin.updateMatrixWorld(true);
  419. this.el.play();
  420. //console.log('modelMatrix', tracked.modelMatrix.toString());
  421. },
  422. validate()
  423. {
  424. if(this.el.parentNode !== this.el.sceneEl)
  425. throw new Error('ar-root must be a direct child of a-scene');
  426. },
  427. }));
  428. AFRAME.registerPrimitive('ar-root', {
  429. defaultComponents: {
  430. 'ar-root': {}
  431. },
  432. mappings: {
  433. 'reference-image': 'ar-root.referenceImage'
  434. }
  435. });
  436. /* ========================================================================= */
  437. /**
  438. * AR Sources
  439. */
  440. AFRAME.registerComponent('ar-sources', ARComponent({
  441. validate()
  442. {
  443. if(this.el.parentNode !== this.el.sceneEl)
  444. throw new Error('ar-sources must be a direct child of a-scene');
  445. },
  446. sources()
  447. {
  448. const sources = [];
  449. for(const child of this.el.children) {
  450. if(child.components !== undefined) {
  451. for(const name in child.components) {
  452. const component = child.components[name];
  453. if(component.ar === this.ar && typeof component.source == 'function')
  454. sources.push(component.source());
  455. }
  456. }
  457. }
  458. return sources;
  459. },
  460. }));
  461. AFRAME.registerPrimitive('ar-sources', {
  462. defaultComponents: {
  463. 'ar-sources': {}
  464. }
  465. });
  466. /**
  467. * AR Camera Source
  468. */
  469. AFRAME.registerComponent('ar-camera-source', ARComponent({
  470. schema: {
  471. /** video resolution: "xs" | "xs+" | "sm" | "sm+" | "md" | "md+" | "lg" | "lg+" */
  472. 'resolution': { type: 'string', default: 'md' },
  473. /** facing mode: "environment" | "user" */
  474. 'facingMode': { type: 'string', default: 'environment' },
  475. },
  476. validate()
  477. {
  478. if(!this.el.parentNode.getAttribute('ar-sources'))
  479. throw new Error('ar-camera-source must be a direct child of ar-sources');
  480. },
  481. source()
  482. {
  483. return AR.Source.Camera({
  484. resolution: this.data.resolution,
  485. constraints: {
  486. facingMode: this.data.facingMode
  487. }
  488. });
  489. },
  490. }));
  491. AFRAME.registerPrimitive('ar-camera-source', {
  492. defaultComponents: {
  493. 'ar-camera-source': {}
  494. },
  495. mappings: {
  496. 'resolution': 'ar-camera-source.resolution',
  497. 'facing-mode': 'ar-camera-source.facingMode',
  498. }
  499. });
  500. /**
  501. * AR Video Source
  502. */
  503. AFRAME.registerComponent('ar-video-source', ARComponent({
  504. schema: {
  505. /** selector for a <video> element */
  506. 'video': { type: 'selector' },
  507. },
  508. validate()
  509. {
  510. if(!this.el.parentNode.getAttribute('ar-sources'))
  511. throw new Error('ar-video-source must be a direct child of ar-sources');
  512. },
  513. source()
  514. {
  515. return AR.Source.Video(this.data.video);
  516. },
  517. }));
  518. AFRAME.registerPrimitive('ar-video-source', {
  519. defaultComponents: {
  520. 'ar-video-source': {}
  521. },
  522. mappings: {
  523. 'video': 'ar-video-source.video'
  524. }
  525. });
  526. /**
  527. * AR Canvas Source
  528. */
  529. AFRAME.registerComponent('ar-canvas-source', ARComponent({
  530. schema: {
  531. /** selector for a <canvas> element */
  532. 'canvas': { type: 'selector' },
  533. },
  534. validate()
  535. {
  536. if(!this.el.parentNode.getAttribute('ar-sources'))
  537. throw new Error('ar-canvas-source must be a direct child of ar-sources');
  538. },
  539. source()
  540. {
  541. return AR.Source.Canvas(this.data.canvas);
  542. },
  543. }));
  544. AFRAME.registerPrimitive('ar-canvas-source', {
  545. defaultComponents: {
  546. 'ar-canvas-source': {}
  547. },
  548. mappings: {
  549. 'canvas': 'ar-canvas-source.canvas'
  550. }
  551. });
  552. /* ========================================================================= */
  553. /**
  554. * AR Trackers
  555. */
  556. AFRAME.registerComponent('ar-trackers', ARComponent({
  557. validate()
  558. {
  559. if(this.el.parentNode !== this.el.sceneEl)
  560. throw new Error('ar-trackers must be a direct child of a-scene');
  561. },
  562. /* async */ trackers()
  563. {
  564. const trackers = [];
  565. for(const child of this.el.children) {
  566. if(child.components !== undefined) {
  567. for(const name in child.components) {
  568. const component = child.components[name];
  569. if(component.ar === this.ar && typeof component.tracker == 'function')
  570. trackers.push(component.tracker());
  571. }
  572. }
  573. }
  574. return Speedy.Promise.all(trackers);
  575. },
  576. }));
  577. AFRAME.registerPrimitive('ar-trackers', {
  578. defaultComponents: {
  579. 'ar-trackers': {}
  580. }
  581. });
  582. /**
  583. * AR Image Tracker
  584. */
  585. AFRAME.registerComponent('ar-image-tracker', ARComponent({
  586. schema: {
  587. /** resolution of the tracker: "xs" | "xs+" | "sm" | "sm+" | "md" | "md+" | "lg" | "lg+" */
  588. 'resolution': { type: 'string', default: 'sm+' },
  589. },
  590. validate()
  591. {
  592. if(!this.el.parentNode.getAttribute('ar-trackers'))
  593. throw new Error('ar-image-tracker must be a direct child of ar-trackers');
  594. },
  595. /* async */ tracker()
  596. {
  597. const tracker = AR.Tracker.ImageTracker();
  598. const referenceImages = [];
  599. tracker.resolution = this.data.resolution;
  600. for(const child of this.el.children) {
  601. if(child.components !== undefined) {
  602. for(const name in child.components) {
  603. const component = child.components[name];
  604. if(component.ar === this.ar && typeof component.referenceImage == 'function')
  605. referenceImages.push(component.referenceImage());
  606. }
  607. }
  608. }
  609. return tracker.database.add(referenceImages).then(() => tracker);
  610. },
  611. }));
  612. AFRAME.registerPrimitive('ar-image-tracker', {
  613. defaultComponents: {
  614. 'ar-image-tracker': {}
  615. }
  616. });
  617. /**
  618. * AR Reference Image
  619. */
  620. AFRAME.registerComponent('ar-reference-image', ARComponent({
  621. schema: {
  622. /** the name of the reference image */
  623. 'name': { type: 'string', default: '' },
  624. /** URL of an image */
  625. 'src': { type: 'string', default: '' }
  626. },
  627. validate()
  628. {
  629. if(!this.el.parentNode.getAttribute('ar-image-tracker'))
  630. throw new Error('ar-reference-image must be a direct child of ar-image-tracker');
  631. },
  632. referenceImage()
  633. {
  634. if(this.data.src == '')
  635. throw new Error('Unspecified src attribute of ar-reference-image');
  636. const img = new Image();
  637. img.src = this.data.src;
  638. return {
  639. name: this.data.name != '' ? this.data.name : undefined,
  640. image: img
  641. };
  642. }
  643. }));
  644. AFRAME.registerPrimitive('ar-reference-image', {
  645. defaultComponents: {
  646. 'ar-reference-image': {}
  647. },
  648. mappings: {
  649. 'name': 'ar-reference-image.name',
  650. 'src': 'ar-reference-image.src'
  651. }
  652. });
  653. /* ========================================================================= */
  654. /**
  655. * AR Viewport
  656. */
  657. AFRAME.registerComponent('ar-viewport', ARComponent({
  658. schema: {
  659. /** viewport resolution: "xs" | "xs+" | "sm" | "sm+" | "md" | "md+" | "lg" | "lg+" */
  660. 'resolution': { type: 'string', default: 'lg' },
  661. /** viewport style: "best-fit" | "stretch" | "inline" */
  662. 'style': { type: 'string', default: 'best-fit' },
  663. },
  664. validate()
  665. {
  666. if(this.el.parentNode !== this.el.sceneEl)
  667. throw new Error('ar-viewport must be a direct child of a-scene');
  668. },
  669. viewport()
  670. {
  671. const scene = this.el.sceneEl;
  672. const huds = [];
  673. for(const child of this.el.children) {
  674. if(child.components !== undefined) {
  675. for(const name in child.components) {
  676. const component = child.components[name];
  677. if(component.ar === this.ar && typeof component.hudContainer == 'function')
  678. huds.push(component.hudContainer());
  679. }
  680. }
  681. }
  682. if(huds.length > 1)
  683. throw new Error('Can\'t define multiple ar-hud\'s in an ar-viewport');
  684. else if(huds.length == 0)
  685. huds.push(undefined);
  686. return AR.Viewport({
  687. container: this.el,
  688. hudContainer: huds[0],
  689. canvas: scene.canvas,
  690. resolution: this.data.resolution,
  691. style: this.data.style
  692. });
  693. },
  694. }));
  695. AFRAME.registerPrimitive('ar-viewport', {
  696. defaultComponents: {
  697. 'ar-viewport': {}
  698. },
  699. mappings: {
  700. 'resolution': 'ar-viewport.resolution',
  701. 'style': 'ar-viewport.style'
  702. }
  703. });
  704. /**
  705. * AR Heads-Up Display
  706. */
  707. let hudStyle = (function() { // hide on page load
  708. const style = document.createElement('style');
  709. style.textContent = 'ar-hud, [ar-hud] { display: none; }';
  710. document.head.appendChild(style);
  711. return style;
  712. })();
  713. AFRAME.registerComponent('ar-hud', ARComponent({
  714. init()
  715. {
  716. if(hudStyle !== null) {
  717. document.head.removeChild(hudStyle);
  718. hudStyle = null;
  719. }
  720. this.el.hidden = true;
  721. },
  722. validate()
  723. {
  724. if(!this.el.parentNode.getAttribute('ar-viewport'))
  725. throw new Error('ar-hud must be a direct child of ar-viewport');
  726. },
  727. hudContainer()
  728. {
  729. return this.el;
  730. },
  731. }));
  732. AFRAME.registerPrimitive('ar-hud', {
  733. defaultComponents: {
  734. 'ar-hud': {}
  735. }
  736. });
  737. /* ========================================================================= */
  738. /**
  739. * Version check
  740. * @param {object} libs
  741. */
  742. function __THIS_PLUGIN_HAS_BEEN_TESTED_WITH__(libs)
  743. {
  744. window.addEventListener('load', () => {
  745. try { AR, AFRAME;
  746. const versionOf = { 'encantar.js': AR.version.replace(/-.*$/, ''), 'A-Frame': AFRAME.version };
  747. const check = (x,v,w) => v != w ? console.warn(`\n\n\nWARNING\n\nThis plugin has been tested with ${x} version ${v}. The version in use is ${w}. Usage of ${x} version ${v} is recommended instead.\n\n\n`) : void 0;
  748. for(const [lib, expected] of Object.entries(libs))
  749. check(lib, expected.version, versionOf[lib]);
  750. }
  751. catch(e) {
  752. alert(e.message);
  753. }
  754. });
  755. }
  756. })();