123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- /**
- * @file MARTINS.js & AFRAME glue code
- * @version 1.0.1
- * @author Alexandre Martins (https://github.com/alemart)
- * @license AGPL-3.0-only or PolyForm-Perimeter-1.0.0
- */
-
- /* Usage of the indicated versions is encouraged */
- __THIS_GLUE_CODE_HAS_BEEN_TESTED_WITH__({
- 'MARTINS.js': { version: '0.1.1' },
- 'AFRAME': { version: '1.3.0' }
- });
-
- /**
- * Connect MARTINS.js to AFRAME
- * @param {(canvas: HTMLCanvasElement) => Promise<Session> | SpeedyPromise<Session>} startARSession
- */
- function linkMartinsToAFRAME(startARSession)
- {
- AFRAME.registerSystem('ar-system', {
- init()
- {
- this.state = {
- isTracking: false,
- frame: null,
- referenceImage: null,
- projectionMatrix: null,
- viewMatrixInverse: null,
- modelMatrix: null,
- };
- this.session = null;
-
- return startARSession(this.el.canvas).then(session => {
-
- this.session = session;
-
- session.addEventListener('end', event => {
- this.state.isTracking = false;
- });
- session.viewport.addEventListener('resize', event => {
- this.updateRenderer(session.viewport.virtualSize);
- });
-
- if(session.viewport.canvas !== this.el.canvas) {
- session.end();
- throw new Error('Invalid AFRAME <canvas>');
- }
-
- const animate = (time, frame) => {
- this.updateState(frame);
- this.renderVirtualScene();
- session.requestAnimationFrame(animate);
- };
- session.requestAnimationFrame(animate);
-
- }).catch(error => {
-
- console.error(error);
- alert(error.message);
-
- });
- },
-
- updateState(frame)
- {
- const wasTracking = this.state.isTracking;
-
- this.state.frame = frame;
- this.state.isTracking = false;
- this.state.referenceImage = null;
-
- for(const result of frame.results) {
- if(result.tracker.type == 'image-tracker') {
- if(result.trackables.length > 0) {
- const trackable = result.trackables[0];
- this.state.projectionMatrix = result.viewer.view.projectionMatrix;
- this.state.viewMatrixInverse = result.viewer.pose.transform.matrix;
- this.state.modelMatrix = trackable.pose.transform.matrix;
- this.state.referenceImage = trackable.referenceImage;
- this.state.isTracking = true;
- }
- }
- }
-
- if(this.state.isTracking && !wasTracking)
- this.updateRenderer(frame.session.viewport.virtualSize);
- },
-
- updateRenderer(size)
- {
- const renderer = this.el.renderer;
- const resize = () => {
- renderer.setPixelRatio(1.0);
- renderer.setSize(size.width, size.height, false);
- };
-
- resize();
- setTimeout(resize, 200); // internals of AFRAME (a-scene.js)
- },
-
- renderVirtualScene()
- {
- const scene = this.el;
- if(!scene.camera || !scene.object3D)
- return;
-
- scene.delta = scene.clock.getDelta() * 1000;
- scene.time = scene.clock.elapsedTime * 1000;
- if(scene.isPlaying)
- scene.tick(scene.time, scene.delta);
-
- scene.object3D.background = null;
- scene.renderer.render(scene.object3D, scene.camera);
- scene.renderer.setAnimationLoop(null);
- },
- });
-
- AFRAME.registerComponent('ar-root', {
- schema: {
- 'image-target': { type: 'string', default: '' },
- },
-
- init()
- {
- this.arSystem = this.el.sceneEl.systems['ar-system'];
-
- this.el.object3D.matrixAutoUpdate = false;
- this.el.object3D.visible = false;
- },
-
- remove()
- {
- const session = this.arSystem.session;
- session.end();
- },
-
- tick()
- {
- const ANY = '', target = this.data['image-target'];
- const state = this.arSystem.state;
-
- if(state.isTracking && (target === ANY || target === state.referenceImage.name)) {
- this.alignVirtualScene(state.modelMatrix);
- this.el.object3D.visible = true;
- }
- else
- this.el.object3D.visible = false;
- },
-
- alignVirtualScene(modelMatrix)
- {
- const arRoot = this.el.object3D;
-
- arRoot.matrix.fromArray(modelMatrix.read());
- arRoot.updateMatrixWorld(true);
- }
- });
-
- AFRAME.registerPrimitive('ar-root', AFRAME.utils.extendDeep({}, AFRAME.primitives.getMeshMixin(), {
- defaultComponents: {
- 'ar-root': {}
- },
- mappings: {
- 'image-target': 'ar-root.image-target'
- }
- }));
-
- AFRAME.registerComponent('ar-camera', {
- init()
- {
- this.arSystem = this.el.sceneEl.systems['ar-system'];
- this.arCamera = this.el.getObject3D('camera');
-
- this.arCamera.matrixAutoUpdate = false;
-
- this.el.setAttribute('camera', { active: true });
- this.el.setAttribute('wasd-controls', { enabled: false });
- this.el.setAttribute('look-controls', { enabled: false });
- this.el.setAttribute('position', { x: 0, y: 0, z: 0 }); // AFRAME sets y = 1.6m for VR
- },
-
- tick()
- {
- const state = this.arSystem.state;
-
- if(state.isTracking)
- this.updateCamera(state.projectionMatrix, state.viewMatrixInverse);
- },
-
- updateCamera(projectionMatrix, viewMatrixInverse)
- {
- const arCamera = this.arCamera;
-
- arCamera.projectionMatrix.fromArray(projectionMatrix.read());
- arCamera.projectionMatrixInverse.copy(arCamera.projectionMatrix).invert();
- arCamera.matrix.fromArray(viewMatrixInverse.read());
- arCamera.updateMatrixWorld(true);
- }
- });
-
- /*
- // AFRAME won't catch this in setupInitialCamera()
- AFRAME.registerPrimitive('ar-camera', AFRAME.utils.extendDeep({}, AFRAME.primitives.getMeshMixin(), {
- defaultComponents: {
- 'ar-camera': {}
- }
- }));
- */
-
- AFRAME.registerComponent('ar-scene', {
- init()
- {
- this.el.setAttribute('vr-mode-ui', { enabled: false });
- this.el.setAttribute('embedded', true);
- this.el.setAttribute('renderer', { alpha: true });
- }
- });
- };
-
- // Start automatically
- if(typeof startARSession === 'function')
- linkMartinsToAFRAME(startARSession);
- else
- alert('Missing startARSession');
-
- /**
- * Version check
- * @param {object} json
- */
- function __THIS_GLUE_CODE_HAS_BEEN_TESTED_WITH__(json)
- {
- try { Martins, AFRAME;
- const versionOf = { 'MARTINS.js': Martins.version.replace(/-.*$/, ''), 'AFRAME': AFRAME.version };
- const check = (x,v,w) => v !== w ? console.warn(`\n\n\nWARNING\n\nThis glue code 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;
- for(const [x, expected] of Object.entries(json)) check(x, expected.version, versionOf[x]);
- } catch(e) { alert(e.message); }
- }
|