|
@@ -2,99 +2,113 @@ window.addEventListener('load', () => {
|
2
|
2
|
|
3
|
3
|
const my = { };
|
4
|
4
|
|
5
|
|
- async function initialize(ar)
|
|
5
|
+ // initialize the virtual scene
|
|
6
|
+ async function init(ar)
|
6
|
7
|
{
|
7
|
8
|
// add lights
|
8
|
|
- const ambientLight = new THREE.AmbientLight(0xb7b7b7);
|
9
|
|
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.4);
|
10
|
|
- directionalLight.position.set(0, 0, -1);
|
11
|
|
- directionalLight.target.position.set(0, 0, 0);
|
12
|
|
- ar.scene.add(directionalLight);
|
|
9
|
+ const ambientLight = new THREE.AmbientLight(0xffffff);
|
13
|
10
|
ar.scene.add(ambientLight);
|
14
|
11
|
|
15
|
|
- // create a group of objects as a child of ar.root
|
16
|
|
- const group = createGroup('in-front');
|
17
|
|
- //const group = createGroup('on-top'); // try this option!
|
|
12
|
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
|
|
13
|
+ directionalLight.position.set(0, 1, 0);
|
|
14
|
+ ar.root.add(directionalLight);
|
|
15
|
+
|
|
16
|
+ //const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 0.5);
|
|
17
|
+ //ar.scene.add(directionalLightHelper);
|
|
18
|
+
|
|
19
|
+ // create a group as a child of ar.root, which is aligned to the physical scene
|
|
20
|
+ const group = createMainGroup(true);
|
|
21
|
+ group.position.set(0, -0.5, 0);
|
|
22
|
+ group.scale.set(0.7, 0.7, 0.7);
|
18
|
23
|
ar.root.add(group);
|
19
|
24
|
|
20
|
|
- // create cubes
|
21
|
|
- const cubeA = createCube(-0.75, 0, 0xffff00);
|
22
|
|
- const cubeB = createCube(0.75, 0, 0x00ff00);
|
23
|
|
- group.add(cubeA, cubeB);
|
|
25
|
+ // create the magic circle
|
|
26
|
+ const magicCircle = createPlane('../assets/magic-circle.png');
|
|
27
|
+ magicCircle.material.transparent = true;
|
|
28
|
+ magicCircle.material.opacity = 0.85;
|
|
29
|
+ magicCircle.material.color = new THREE.Color(0xbeefff);
|
|
30
|
+ magicCircle.scale.set(6, 6, 1);
|
|
31
|
+ group.add(magicCircle);
|
24
|
32
|
|
25
|
|
- // create the ground
|
26
|
|
- const ground = createGround(0x3d5afe);
|
27
|
|
- group.add(ground);
|
|
33
|
+ // load the mage
|
|
34
|
+ const gltf = await loadGLTF('../assets/mage.glb');
|
|
35
|
+ const mage = gltf.scene;
|
|
36
|
+ group.add(mage);
|
28
|
37
|
|
29
|
|
- // load a 3D model
|
30
|
|
- const modelURL = '../assets/my-3d-model.glb';
|
31
|
|
- const model = await loadModel(modelURL);
|
32
|
|
- group.add(model);
|
|
38
|
+ // prepare the animation of the mage
|
|
39
|
+ const animationAction = createAnimationAction(gltf, 'Idle');
|
|
40
|
+ animationAction.loop = THREE.LoopRepeat;
|
|
41
|
+ animationAction.play();
|
33
|
42
|
|
34
|
43
|
// export objects
|
35
|
|
- my.cubes = [ cubeA, cubeB ];
|
36
|
44
|
my.group = group;
|
37
|
|
- my.model = model;
|
38
|
|
- my.ground = ground;
|
|
45
|
+ my.magicCircle = magicCircle;
|
|
46
|
+ my.mage = mage;
|
|
47
|
+ my.animationAction = animationAction;
|
39
|
48
|
}
|
40
|
49
|
|
41
|
|
- function animate(ar)
|
|
50
|
+ // animate the virtual scene
|
|
51
|
+ function animate(ar, deltaSeconds)
|
42
|
52
|
{
|
43
|
|
- const ROTATION_CYCLES_PER_SECOND = 1.0;
|
44
|
53
|
const TWO_PI = 2.0 * Math.PI;
|
45
|
|
- const delta = ar.session.time.delta;
|
|
54
|
+ const ROTATIONS_PER_SECOND = 0.25;
|
|
55
|
+
|
|
56
|
+ // animate the mage
|
|
57
|
+ const mixer = my.animationAction.getMixer();
|
|
58
|
+ mixer.update(deltaSeconds);
|
46
|
59
|
|
47
|
|
- // rotate the cubes
|
48
|
|
- for(const cube of my.cubes)
|
49
|
|
- cube.rotateY(TWO_PI * ROTATION_CYCLES_PER_SECOND * delta);
|
|
60
|
+ // animate the magic circle
|
|
61
|
+ my.magicCircle.rotateZ(TWO_PI * ROTATIONS_PER_SECOND * deltaSeconds);
|
50
|
62
|
}
|
51
|
63
|
|
52
|
|
- function createGroup(mode = 'in-front')
|
|
64
|
+ function createMainGroup(frontView = false)
|
53
|
65
|
{
|
54
|
66
|
const group = new THREE.Group();
|
55
|
67
|
|
56
|
|
- if(mode == 'in-front') {
|
57
|
|
- group.rotation.set(-Math.PI/2, 0, 0);
|
58
|
|
- group.position.set(0, -0.5, 0.5);
|
59
|
|
- }
|
60
|
|
- else if(mode == 'on-top') {
|
61
|
|
- group.rotation.set(0, 0, 0);
|
62
|
|
- group.position.set(0, 0, 0);
|
63
|
|
- }
|
|
68
|
+ // top view is the default
|
|
69
|
+ if(frontView)
|
|
70
|
+ group.rotateX(-Math.PI / 2);
|
64
|
71
|
|
65
|
72
|
return group;
|
66
|
73
|
}
|
67
|
74
|
|
68
|
|
- function createCube(x, y, color, length = 0.25)
|
|
75
|
+ async function loadGLTF(filepath, yAxisIsUp = true)
|
69
|
76
|
{
|
70
|
|
- const geometry = new THREE.BoxGeometry(length, length, length);
|
71
|
|
- const material = new THREE.MeshPhongMaterial({ color });
|
72
|
|
- const cube = new THREE.Mesh(geometry, material);
|
|
77
|
+ const loader = new THREE.GLTFLoader();
|
|
78
|
+ const gltf = await loader.loadAsync(filepath);
|
73
|
79
|
|
74
|
|
- cube.position.set(x, y, 1.25);
|
|
80
|
+ // glTF defines +y as up. We expect +z to be up.
|
|
81
|
+ if(yAxisIsUp)
|
|
82
|
+ gltf.scene.rotateX(Math.PI / 2);
|
75
|
83
|
|
76
|
|
- return cube;
|
|
84
|
+ return gltf;
|
77
|
85
|
}
|
78
|
86
|
|
79
|
|
- function createGround(color)
|
|
87
|
+ function createAnimationAction(gltf, name = null)
|
80
|
88
|
{
|
81
|
|
- const geometry = new THREE.RingGeometry(0.001, 1, 8);
|
82
|
|
- const material = new THREE.MeshPhongMaterial({ color: color, side: THREE.DoubleSide });
|
83
|
|
- const ground = new THREE.Mesh(geometry, material);
|
|
89
|
+ const mixer = new THREE.AnimationMixer(gltf.scene);
|
|
90
|
+ const clips = gltf.animations;
|
|
91
|
+
|
|
92
|
+ if(clips.length == 0)
|
|
93
|
+ throw new Error('No animation clips');
|
84
|
94
|
|
85
|
|
- material.transparent = true;
|
86
|
|
- material.opacity = 0.75;
|
|
95
|
+ const clip = (name !== null) ? THREE.AnimationClip.findByName(clips, name) : clips[0];
|
|
96
|
+ const action = mixer.clipAction(clip);
|
87
|
97
|
|
88
|
|
- return ground;
|
|
98
|
+ return action;
|
89
|
99
|
}
|
90
|
100
|
|
91
|
|
- async function loadModel(filepath)
|
|
101
|
+ function createPlane(imagepath)
|
92
|
102
|
{
|
93
|
|
- const loader = new THREE.GLTFLoader();
|
94
|
|
- const gltf = await loader.loadAsync(filepath);
|
95
|
|
- const model = gltf.scene;
|
|
103
|
+ const texture = new THREE.TextureLoader().load(imagepath);
|
|
104
|
+ const geometry = new THREE.PlaneGeometry(1, 1);
|
|
105
|
+ const material = new THREE.MeshBasicMaterial({
|
|
106
|
+ map: texture,
|
|
107
|
+ side: THREE.DoubleSide,
|
|
108
|
+ });
|
|
109
|
+ const mesh = new THREE.Mesh(geometry, material);
|
96
|
110
|
|
97
|
|
- return model;
|
|
111
|
+ return mesh;
|
98
|
112
|
}
|
99
|
113
|
|
100
|
114
|
async function startARSession()
|
|
@@ -152,6 +166,6 @@ window.addEventListener('load', () => {
|
152
|
166
|
}
|
153
|
167
|
|
154
|
168
|
// enchant!
|
155
|
|
- encantar(startARSession, animate, initialize);
|
|
169
|
+ encantar(startARSession, animate, init);
|
156
|
170
|
|
157
|
171
|
});
|