選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

aframe-with-encantar.js 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. /**
  2. * @file aframe plugin for encantar.js
  3. * @author Alexandre Martins (https://github.com/alemart)
  4. * @license LGPL-3.0-or-later
  5. */
  6. /* Usage of the indicated versions is encouraged */
  7. __THIS_PLUGIN_HAS_BEEN_TESTED_WITH__({
  8. 'encantar.js': { version: '0.2.0' },
  9. 'aframe': { version: '1.4.2' }
  10. });
  11. /**
  12. * Connect encantar.js to aframe
  13. * @param {(canvas: HTMLCanvasElement) => Promise<Session> | SpeedyPromise<Session>} startARSession
  14. */
  15. function encantar(startARSession)
  16. {
  17. AFRAME.registerSystem('ar-system', {
  18. init()
  19. {
  20. this.state = {
  21. isTracking: false,
  22. frame: null,
  23. referenceImage: null,
  24. projectionMatrix: null,
  25. viewMatrixInverse: null,
  26. modelMatrix: null,
  27. };
  28. this.session = null;
  29. return startARSession(this.el.canvas).then(session => {
  30. this.session = session;
  31. session.addEventListener('end', event => {
  32. this.state.isTracking = false;
  33. });
  34. session.viewport.addEventListener('resize', event => {
  35. this.updateRenderer(session.viewport.virtualSize);
  36. });
  37. if(session.viewport.canvas !== this.el.canvas) {
  38. session.end();
  39. throw new Error('Invalid AFRAME <canvas>');
  40. }
  41. const animate = (time, frame) => {
  42. this.updateState(frame);
  43. this.renderVirtualScene();
  44. session.requestAnimationFrame(animate);
  45. };
  46. session.requestAnimationFrame(animate);
  47. }).catch(error => {
  48. console.error(error);
  49. alert(error.message);
  50. });
  51. },
  52. updateState(frame)
  53. {
  54. const wasTracking = this.state.isTracking;
  55. this.state.frame = frame;
  56. this.state.isTracking = false;
  57. this.state.referenceImage = null;
  58. for(const result of frame.results) {
  59. if(result.tracker.type == 'image-tracker') {
  60. if(result.trackables.length > 0) {
  61. const trackable = result.trackables[0];
  62. this.state.projectionMatrix = result.viewer.view.projectionMatrix;
  63. this.state.viewMatrixInverse = result.viewer.pose.transform.matrix;
  64. this.state.modelMatrix = trackable.pose.transform.matrix;
  65. this.state.referenceImage = trackable.referenceImage;
  66. this.state.isTracking = true;
  67. }
  68. }
  69. }
  70. if(this.state.isTracking && !wasTracking)
  71. this.updateRenderer(frame.session.viewport.virtualSize);
  72. },
  73. updateRenderer(size)
  74. {
  75. const renderer = this.el.renderer;
  76. const resize = () => {
  77. renderer.setPixelRatio(1.0);
  78. renderer.setSize(size.width, size.height, false);
  79. };
  80. resize();
  81. setTimeout(resize, 200); // internals of AFRAME (a-scene.js)
  82. },
  83. renderVirtualScene()
  84. {
  85. const scene = this.el;
  86. if(!scene.camera || !scene.object3D)
  87. return;
  88. scene.delta = scene.clock.getDelta() * 1000;
  89. scene.time = scene.clock.elapsedTime * 1000;
  90. if(scene.isPlaying)
  91. scene.tick(scene.time, scene.delta);
  92. scene.object3D.background = null;
  93. scene.renderer.render(scene.object3D, scene.camera);
  94. scene.renderer.setAnimationLoop(null);
  95. },
  96. });
  97. AFRAME.registerComponent('ar-root', {
  98. schema: {
  99. 'image-target': { type: 'string', default: '' },
  100. },
  101. init()
  102. {
  103. this.arSystem = this.el.sceneEl.systems['ar-system'];
  104. this.el.object3D.matrixAutoUpdate = false;
  105. this.el.object3D.visible = false;
  106. },
  107. remove()
  108. {
  109. const session = this.arSystem.session;
  110. session.end();
  111. },
  112. tick()
  113. {
  114. const ANY = '', target = this.data['image-target'];
  115. const state = this.arSystem.state;
  116. if(state.isTracking && (target === ANY || target === state.referenceImage.name)) {
  117. this.alignVirtualScene(state.modelMatrix);
  118. this.el.object3D.visible = true;
  119. }
  120. else
  121. this.el.object3D.visible = false;
  122. },
  123. alignVirtualScene(modelMatrix)
  124. {
  125. const arRoot = this.el.object3D;
  126. arRoot.matrix.fromArray(modelMatrix.read());
  127. arRoot.updateMatrixWorld(true);
  128. }
  129. });
  130. AFRAME.registerPrimitive('ar-root', AFRAME.utils.extendDeep({}, AFRAME.primitives.getMeshMixin(), {
  131. defaultComponents: {
  132. 'ar-root': {}
  133. },
  134. mappings: {
  135. 'image-target': 'ar-root.image-target'
  136. }
  137. }));
  138. AFRAME.registerComponent('ar-camera', {
  139. init()
  140. {
  141. this.arSystem = this.el.sceneEl.systems['ar-system'];
  142. this.arCamera = this.el.getObject3D('camera');
  143. this.arCamera.matrixAutoUpdate = false;
  144. this.el.setAttribute('camera', { active: true });
  145. this.el.setAttribute('wasd-controls', { enabled: false });
  146. this.el.setAttribute('look-controls', { enabled: false });
  147. this.el.setAttribute('position', { x: 0, y: 0, z: 0 }); // AFRAME sets y = 1.6m for VR
  148. },
  149. tick()
  150. {
  151. const state = this.arSystem.state;
  152. if(state.isTracking)
  153. this.updateCamera(state.projectionMatrix, state.viewMatrixInverse);
  154. },
  155. updateCamera(projectionMatrix, viewMatrixInverse)
  156. {
  157. const arCamera = this.arCamera;
  158. arCamera.projectionMatrix.fromArray(projectionMatrix.read());
  159. arCamera.projectionMatrixInverse.copy(arCamera.projectionMatrix).invert();
  160. arCamera.matrix.fromArray(viewMatrixInverse.read());
  161. arCamera.updateMatrixWorld(true);
  162. }
  163. });
  164. /*
  165. // AFRAME won't catch this in setupInitialCamera()
  166. AFRAME.registerPrimitive('ar-camera', AFRAME.utils.extendDeep({}, AFRAME.primitives.getMeshMixin(), {
  167. defaultComponents: {
  168. 'ar-camera': {}
  169. }
  170. }));
  171. */
  172. AFRAME.registerComponent('ar-scene', {
  173. init()
  174. {
  175. this.el.setAttribute('vr-mode-ui', { enabled: false });
  176. this.el.setAttribute('embedded', true);
  177. this.el.setAttribute('renderer', { alpha: true });
  178. }
  179. });
  180. };
  181. // Start automatically
  182. if(typeof startARSession === 'function')
  183. encantar(startARSession);
  184. else
  185. alert('Missing startARSession');
  186. /**
  187. * Version check
  188. * @param {object} json
  189. */
  190. function __THIS_PLUGIN_HAS_BEEN_TESTED_WITH__(json)
  191. {
  192. try { Martins, AFRAME;
  193. const versionOf = { 'encantar.js': Martins.version.replace(/-.*$/, ''), 'aframe': AFRAME.version };
  194. 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;
  195. for(const [x, expected] of Object.entries(json)) check(x, expected.version, versionOf[x]);
  196. } catch(e) { alert(e.message); }
  197. }