ソースを参照

Introduce the plugins folder

customisations
alemart 1年前
コミット
fd45c2858d

+ 1
- 1
demos/hello-aframe/index.html ファイルの表示

@@ -9,7 +9,7 @@
9 9
         <script src="https://cdn.jsdelivr.net/npm/aframe@1.4.2/dist/aframe-v1.4.2.min.js"></script>
10 10
         <script src="../assets/aframe-particle-system-component.min.js"></script>
11 11
         <script src="demo.js"></script>
12
-        <script src="../assets/aframe-with-encantar.js"></script>
12
+        <script src="../../plugins/aframe-with-encantar.js"></script>
13 13
     </head>
14 14
     <body>
15 15
         <div id="ar-viewport">

+ 1
- 1
demos/hello-aframe/video.html ファイルの表示

@@ -9,7 +9,7 @@
9 9
         <script src="https://cdn.jsdelivr.net/npm/aframe@1.4.2/dist/aframe-v1.4.2.min.js"></script>
10 10
         <script src="../assets/aframe-particle-system-component.min.js"></script>
11 11
         <script src="demo.js"></script>
12
-        <script src="../assets/aframe-with-encantar.js"></script>
12
+        <script src="../../plugins/aframe-with-encantar.js"></script>
13 13
     </head>
14 14
     <body>
15 15
         <div id="ar-viewport">

+ 1
- 1
demos/hello-three/index.html ファイルの表示

@@ -8,7 +8,7 @@
8 8
         <script src="../../dist/encantar.min.js"></script>
9 9
         <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
10 10
         <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/examples/js/loaders/GLTFLoader.js"></script>
11
-        <script src="../assets/three-with-encantar.js"></script>
11
+        <script src="../../plugins/three-with-encantar.js"></script>
12 12
         <script src="demo.js"></script>
13 13
     </head>
14 14
     <body>

+ 1
- 1
demos/hello-three/video.html ファイルの表示

@@ -8,7 +8,7 @@
8 8
         <script src="../../dist/encantar.min.js"></script>
9 9
         <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
10 10
         <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/examples/js/loaders/GLTFLoader.js"></script>
11
-        <script src="../assets/three-with-encantar.js"></script>
11
+        <script src="../../plugins/three-with-encantar.js"></script>
12 12
         <script src="demo.js"></script>
13 13
     </head>
14 14
     <body>

+ 1
- 1
demos/touch-three/index.html ファイルの表示

@@ -9,7 +9,7 @@
9 9
         <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
10 10
         <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/examples/js/loaders/FontLoader.js"></script>
11 11
         <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/examples/js/geometries/TextGeometry.js"></script>
12
-        <script src="../assets/three-with-encantar.js"></script>
12
+        <script src="../../plugins/three-with-encantar.js"></script>
13 13
         <script src="demo.js"></script>
14 14
     </head>
15 15
     <body>

+ 1
- 1
demos/touch-three/video.html ファイルの表示

@@ -9,7 +9,7 @@
9 9
         <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
10 10
         <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/examples/js/loaders/FontLoader.js"></script>
11 11
         <script src="https://cdn.jsdelivr.net/npm/three@0.147.0/examples/js/geometries/TextGeometry.js"></script>
12
-        <script src="../assets/three-with-encantar.js"></script>
12
+        <script src="../../plugins/three-with-encantar.js"></script>
13 13
         <script src="demo.js"></script>
14 14
     </head>
15 15
     <body>

+ 9
- 9
docs/getting-started/create-the-augmented-scene.md ファイルの表示

@@ -6,19 +6,19 @@ Now that the image is being tracked, the next step is to render a virtual scene
6 6
 
7 7
 encantAR.js is not a 3D rendering technology. It is an Augmented Reality technology that provides the data you need in order to augment your physical scenes. There are free and open-source 3D rendering technologies for the web that you can find online and use with encantAR.js. Popular solutions include: A-Frame, Babylon.js and Three.js. You can also use other solutions. encantAR.js lets you pick any 3D rendering technology.
8 8
 
9
-Once you pick a 3D rendering technology, you need to integrate it with encantAR.js. There is a code that is responsible for that integration. I call it a _glue code_. Among other things, a glue code transports the tracking results from encantAR.js to the 3D rendering technology of your choice - it really is a "glue" connecting them.
9
+Once you pick a 3D rendering technology, you need to integrate it with encantAR.js. There is a code that is responsible for that integration. I call it a _plugin_. Among other things, a plugin transports the tracking results from encantAR.js to the 3D rendering technology of your choice.
10 10
 
11
-## Write the glue code
11
+## Use a plugin
12 12
 
13
-Writing a glue code is a task of moderate complexity. It requires dealing with matrices, with performance issues, and with some idiosyncrasies of the 3D rendering technologies in order to make sure it all works as intended. It is advisable to have specialized knowledge of computer graphics programming in order to write a glue code that works correctly.
13
+Writing a plugin is a task of moderate complexity. It requires dealing with matrices, with performance issues, and with some idiosyncrasies of the 3D rendering technologies in order to make sure it all works as intended. It is advisable to have specialized knowledge of computer graphics programming in order to write a plugin that works correctly.
14 14
 
15
-I provide easy-to-use glue codes that work with different 3D rendering technologies in my demos, so that you don't need to deal with the complexity. Those glue codes are JavaScript (.js) files. You just need to add a glue code to your web page (e.g., via a `<script>` tag) and then the integration will be done for you. It's really that simple!
15
+I provide easy-to-use plugins that work with different 3D rendering technologies in my demos, so that you don't need to deal with the complexity. Those plugins are JavaScript (.js) files. You just need to add a plugin to your web page (e.g., via a `<script>` tag) and then the integration will be done for you. It's really that simple!
16 16
 
17
-[Find the glue codes in my demos](../demos.md){ .md-button ._blank }
17
+[Find the plugins in my demos](../demos.md){ .md-button ._blank }
18 18
 
19 19
 ## Create the virtual scene
20 20
 
21
-Once you plug in the glue code, you'll be using the 3D rendering technology of your choice to create the virtual scene. The physical scene will be automatically augmented with the virtual scene, thus creating the augmented scene.
21
+You will create the virtual scene using the 3D rendering technology of your choice. As soon as you combine it with a plugin, the physical scene will be automatically augmented with the virtual scene, thus creating the augmented scene.
22 22
 
23 23
 <figure markdown>
24 24
 <video poster="../../img/demo-cool2.webp" style="width:600px" controls muted loop playsinline autoplay oncanplay="this.muted=true;this.play()">
@@ -45,11 +45,11 @@ A-Frame is easy for beginners and pleasing for experts. A simple scene is declar
45 45
         <meta charset="utf-8">
46 46
         <meta name="viewport" content="width=device-width,initial-scale=1">
47 47
         <title>encantAR.js WebAR demo</title>
48
-        <!-- include A-Frame -->
48
+        <!-- include aframe -->
49 49
         <script src="aframe-vX.Y.Z.min.js"></script>
50 50
         <script src="encantar.js"></script>
51 51
         <script src="ar-demo.js"></script>
52
-        <!-- this is my glue code for A-Frame -->
52
+        <!-- include the aframe plugin for encantar.js -->
53 53
         <script src="aframe-with-encantar.js"></script>
54 54
         <style>body { background-color: #3d5afe; }</style>
55 55
     </head>
@@ -80,7 +80,7 @@ A-Frame is easy for beginners and pleasing for experts. A simple scene is declar
80 80
 </html>
81 81
 ```
82 82
 
83
-`<ar-root>` is not part of A-Frame, but it becomes available as soon as you plug in my glue code.
83
+`<ar-root>` is not part of A-Frame, but it becomes available as soon as you use my plugin.
84 84
 
85 85
 A-Frame lets you create animated scenes with special effects simply by declaring things, like in the above example. In many cases, writing new JavaScript code is not needed. A-Frame also includes a visual inspector that makes things really easy for non-coders.
86 86
 

+ 238
- 0
plugins/aframe-with-encantar.js ファイルの表示

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

+ 160
- 0
plugins/three-with-encantar.js ファイルの表示

@@ -0,0 +1,160 @@
1
+/**
2
+ * @file three.js plugin for encantar.js
3
+ * @author Alexandre Martins (https://github.com/alemart)
4
+ * @license LGPL-3.0-or-later
5
+ */
6
+
7
+/* Usage of the indicated versions is encouraged */
8
+__THIS_PLUGIN_HAS_BEEN_TESTED_WITH__({
9
+    'encantar.js': { version: '0.3.0' },
10
+       'three.js': { version: '147' }
11
+});
12
+
13
+/**
14
+ * Use this object to create your augmented scene
15
+ * @typedef {object} ARSystem
16
+ * @property {Session} session AR Session
17
+ * @property {Frame} frame current Frame
18
+ * @property {ReferenceImage | null} referenceImage corresponds to the target being tracked (if any)
19
+ * @property {THREE.Scene} scene three.js Scene
20
+ * @property {THREE.Group} root a 3D object that is automatically aligned with the physical target
21
+ * @property {THREE.Camera} camera a camera adjusted for AR
22
+ * @property {THREE.WebGLRenderer} renderer three.js renderer
23
+ */
24
+
25
+/**
26
+ * Do magic to connect encantar.js to three.js
27
+ * @param {() => Promise<Session> | SpeedyPromise<Session>} startARSession
28
+ * @param {(ar: ARSystem) => void} [animateVirtualScene] animation callback
29
+ * @param {(ar: ARSystem) => void | Promise<void> | SpeedyPromise<Session>} [initializeVirtualScene] initialization callback
30
+ * @returns {Promise<ARSystem> | SpeedyPromise<ARSystem>}
31
+ */
32
+function encantar(startARSession, animateVirtualScene, initializeVirtualScene)
33
+{
34
+    const ar = /** @type {ARSystem} */ ({
35
+        session: null,
36
+        frame: null,
37
+        referenceImage: null,
38
+        scene: null,
39
+        root: null,
40
+        camera: null,
41
+        renderer: null,
42
+    });
43
+
44
+    function mix(frame)
45
+    {
46
+        ar.root.visible = false;
47
+        ar.referenceImage = null;
48
+
49
+        for(const result of frame.results) {
50
+            if(result.tracker.type == 'image-tracker') {
51
+                if(result.trackables.length > 0) {
52
+                    const trackable = result.trackables[0];
53
+                    const projectionMatrix = result.viewer.view.projectionMatrix;
54
+                    const viewMatrixInverse = result.viewer.pose.transform.matrix;
55
+                    const modelMatrix = trackable.pose.transform.matrix;
56
+
57
+                    ar.root.visible = true;
58
+                    ar.referenceImage = trackable.referenceImage;
59
+
60
+                    align(projectionMatrix, viewMatrixInverse, modelMatrix);
61
+                }
62
+            }
63
+        }
64
+    }
65
+
66
+    function align(projectionMatrix, viewMatrixInverse, modelMatrix)
67
+    {
68
+        ar.camera.projectionMatrix.fromArray(projectionMatrix.read());
69
+        ar.camera.projectionMatrixInverse.copy(ar.camera.projectionMatrix).invert();
70
+        ar.camera.matrix.fromArray(viewMatrixInverse.read());
71
+        ar.camera.updateMatrixWorld(true);
72
+        ar.root.matrix.fromArray(modelMatrix.read());
73
+        ar.root.updateMatrixWorld(true);
74
+    }
75
+
76
+    function animate(time, frame)
77
+    {
78
+        ar.frame = frame;
79
+        mix(ar.frame);
80
+
81
+        animateVirtualScene.call(undefined, ar);
82
+
83
+        ar.renderer.render(ar.scene, ar.camera);
84
+        ar.session.requestAnimationFrame(animate);
85
+    }
86
+
87
+
88
+
89
+
90
+    if(typeof animateVirtualScene !== 'function')
91
+        animateVirtualScene = (ar => void 0);
92
+
93
+    if(typeof initializeVirtualScene !== 'function')
94
+        initializeVirtualScene = (ar => void 0);
95
+
96
+    return startARSession().then(session => {
97
+
98
+        ar.session = session;
99
+
100
+        ar.scene = new THREE.Scene();
101
+        ar.camera = new THREE.PerspectiveCamera();
102
+        ar.camera.matrixAutoUpdate = false;
103
+
104
+        ar.root = new THREE.Group();
105
+        ar.root.matrixAutoUpdate = false;
106
+        ar.scene.add(ar.root);
107
+
108
+        ar.renderer = new THREE.WebGLRenderer({
109
+            canvas: ar.session.viewport.canvas,
110
+            alpha: true,
111
+        });
112
+
113
+        ar.session.addEventListener('end', event => {
114
+            ar.root.visible = false;
115
+            ar.frame = null;
116
+            ar.referenceImage = null;
117
+        });
118
+
119
+        ar.session.viewport.addEventListener('resize', event => {
120
+            const size = ar.session.viewport.virtualSize;
121
+            ar.renderer.setPixelRatio(1.0);
122
+            ar.renderer.setSize(size.width, size.height, false);
123
+        });
124
+
125
+        let init = initializeVirtualScene.call(undefined, ar);
126
+        if(!(typeof init === 'object' && 'then' in init))
127
+            init = Promise.resolve();
128
+
129
+        return init.then(() => {
130
+            ar.session.requestAnimationFrame(animate);
131
+            return ar;
132
+        });
133
+
134
+    }).catch(error => {
135
+        
136
+        console.error(error);
137
+        alert(error.message);
138
+        return ar;
139
+   
140
+    });
141
+}
142
+
143
+/**
144
+ * Version check
145
+ * @param {object} json
146
+ */
147
+function __THIS_PLUGIN_HAS_BEEN_TESTED_WITH__(json)
148
+{
149
+    window.addEventListener('load', () => {
150
+        try { AR, __THREE__;
151
+            const versionOf = { 'encantar.js': AR.version.replace(/-.*$/, ''), 'three.js': __THREE__ };
152
+            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;
153
+            for(const [x, expected] of Object.entries(json))
154
+                check(x, expected.version, versionOf[x]);
155
+        }
156
+        catch(e) {
157
+            alert(e.message);
158
+        }
159
+    });
160
+}

+ 1
- 1
webpack.config.js ファイルの表示

@@ -75,7 +75,7 @@ module.exports = (env, argv) => ({
75 75
         server: 'https',
76 76
         host: env.HOST || '0.0.0.0',
77 77
         port: env.PORT || 8000,
78
-        static: ['demos', 'tests'].map(dir => ({
78
+        static: ['demos', 'plugins', 'tests'].map(dir => ({
79 79
             directory: path.resolve(__dirname, dir),
80 80
             publicPath: `/${dir}/`,
81 81
         })),

読み込み中…
キャンセル
保存