Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

aframe-with-encantar.js 24KB

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