-
Notifications
You must be signed in to change notification settings - Fork 0
/
flight_mouse_move.html
654 lines (540 loc) · 23.4 KB
/
flight_mouse_move.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.1/dat.gui.min.js"></script>
<html>
<body>
<div class="aviator" id="world"></div>
<script>
const Colors = {
red: 0xf25346,
white: 0xd8d0d1,
brown: 0x59332e,
pink: 0xf5986e,
brownDark: 0x23190f,
blue: 0x68c3c0
};
let scene,
camera,
fieldOfView,
aspectRatio,
nearPlane,
farPlane,
HEIGHT,
WIDTH,
renderer,
container;
function createScene() {
// Get the width and the height of the screen,
// use them to set up the aspect ratio of the camera
// and the size of the renderer.
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
// Create the scene
scene = new THREE.Scene();
// Add a fog effect to the scene; same color as the
// background color used in the style sheet
scene.fog = new THREE.Fog(0xf7d9aa, 100, 950);
// Create the camera
aspectRatio = WIDTH / HEIGHT;
fieldOfView = 60;
nearPlane = 1;
farPlane = 10000;
camera = new THREE.PerspectiveCamera(
fieldOfView,
aspectRatio,
nearPlane,
farPlane
);
// Set the position of the camera
camera.position.x = 0;
camera.position.z = 200;
camera.position.y = 100;
// Create the renderer
renderer = new THREE.WebGLRenderer({
// Allow transparency to show the gradient background
// we defined in the CSS
alpha: true,
// Activate the anti-aliasing; this is less performant,
// but, as our project is low-poly based, it should be fine :)
antialias: true
});
// Define the size of the renderer; in this case,
// it will fill the entire screen
renderer.setSize(WIDTH, HEIGHT);
// Enable shadow rendering
renderer.shadowMap.enabled = true;
// Add the DOM element of the renderer to the
// container we created in the HTML
container = document.getElementById("world");
container.appendChild(renderer.domElement);
// Listen to the screen: if the user resizes it
// we have to update the camera and the renderer size
window.addEventListener("resize", handleWindowResize, false);
}
function handleWindowResize() {
// update height and width of the renderer and the camera
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
}
let hemisphereLight, shadowLight, ambientLight;
function createLights() {
// an ambient light modifies the global color of a scene and makes the shadows softer
ambientLight = new THREE.AmbientLight(0xdc8874, 0.5);
// A hemisphere light is a gradient colored light
// the first parameter is the sky color, the second parameter is the ground color,
// the third parameter is the intensity of the light
hemisphereLight = new THREE.HemisphereLight(
0xaaaaaa,
0x000000,
0.9
);
// A directional light shines from a specific direction.
// It acts like the sun, that means that all the rays produced are parallel.
shadowLight = new THREE.DirectionalLight(0xffffff, 0.9);
// Set the direction of the light
shadowLight.position.set(150, 350, 350);
// Allow shadow casting
shadowLight.castShadow = true;
// define the visible area of the projected shadow
shadowLight.shadow.camera.left = -400;
shadowLight.shadow.camera.right = 400;
shadowLight.shadow.camera.top = 400;
shadowLight.shadow.camera.bottom = -400;
shadowLight.shadow.camera.near = 1;
shadowLight.shadow.camera.far = 1000;
// define the resolution of the shadow; the higher the better,
// but also the more expensive and less performant
shadowLight.shadow.mapSize.width = 2048;
shadowLight.shadow.mapSize.height = 2048;
// to activate the lights, just add them to the scene
scene.add(ambientLight);
scene.add(hemisphereLight);
scene.add(shadowLight);
}
// First let's define a Sea object :
const Sea = function() {
// create the geometry (shape) of the cylinder;
// the parameters are:
// radius top, radius bottom, height, number of segments on the radius, number of segments vertically
const geom = new THREE.CylinderGeometry(600, 600, 800, 40, 10);
geom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2));
// important: by merging vertices we ensure the continuity of the waves
geom.mergeVertices();
// get the vertices
const l = geom.vertices.length;
// create an array to store new data associated to each vertex
this.waves = [];
for (let i = 0; i < l; i++) {
// get each vertex
const v = geom.vertices[i];
// store some data associated to it
this.waves.push({
y: v.y,
x: v.x,
z: v.z,
// a random angle
ang: Math.random() * Math.PI * 2,
// a random distance
amp: 5 + Math.random() * 15,
// a random speed between 0.016 and 0.048 radians / frame
speed: 0.016 + Math.random() * 0.032
});
}
// create the material
let mat = new THREE.MeshPhongMaterial({
color: Colors.blue,
transparent: true,
opacity: 0.8,
shading: THREE.FlatShading
});
// To create an object in Three.js, we have to create a mesh
// which is a combination of a geometry and some material
this.mesh = new THREE.Mesh(geom, mat);
// Allow the sea to receive shadows
this.mesh.receiveShadow = true;
};
// now we create the function that will be called in each frame
// to update the position of the vertices to simulate the waves
Sea.prototype.moveWaves = function() {
// get the vertices
const verts = this.mesh.geometry.vertices;
const l = verts.length;
for (let i = 0; i < l; i++) {
var v = verts[i];
// get the data associated to it
var vprops = this.waves[i];
// update the position of the vertex
v.x = vprops.x + Math.cos(vprops.ang) * vprops.amp;
v.y = vprops.y + Math.sin(vprops.ang) * vprops.amp;
// increment the angle for the next frame
vprops.ang += vprops.speed;
}
// Tell the renderer that the geometry of the sea has changed.
// In fact, in order to maintain the best level of performance,
// three.js caches the geometries and ignores any changes
// unless we add this line
this.mesh.geometry.verticesNeedUpdate = true;
sea.mesh.rotation.z += 0.005;
};
// Instantiate the sea and add it to the scene:
let sea;
function createSea() {
sea = new Sea();
// push it a little bit at the bottom of the scene
sea.mesh.position.y = -600;
// add the mesh of the sea to the scene
scene.add(sea.mesh);
}
const Cloud = function() {
// Create an empty container that will hold the different parts of the cloud
this.mesh = new THREE.Object3D();
// create a cube geometry;
// this shape will be duplicated to create the cloud
const geom = new THREE.BoxGeometry(20, 20, 20);
// create a material; a simple white material will do the trick
const mat = new THREE.MeshPhongMaterial({
color: Colors.white
});
// duplicate the geometry a random number of times
const nBlocs = 3 + Math.floor(Math.random() * 3);
for (let i = 0; i < nBlocs; i++) {
// create the mesh by cloning the geometry
const m = new THREE.Mesh(geom, mat);
// set the position and the rotation of each cube randomly
m.position.x = i * 15;
m.position.y = Math.random() * 10;
m.position.z = Math.random() * 10;
m.rotation.z = Math.random() * Math.PI * 2;
m.rotation.y = Math.random() * Math.PI * 2;
// set the size of the cube randomly
const s = 0.1 + Math.random() * 0.9;
m.scale.set(s, s, s);
// allow each cube to cast and to receive shadows
m.castShadow = true;
m.receiveShadow = true;
// add the cube to the container we first created
this.mesh.add(m);
}
};
// Define a Sky Object
const Sky = function() {
// Create an empty container
this.mesh = new THREE.Object3D();
// choose a number of clouds to be scattered in the sky
this.nClouds = 20;
// To distribute the clouds consistently,
// we need to place them according to a uniform angle
const stepAngle = (Math.PI * 2) / this.nClouds;
// create the clouds
for (let i = 0; i < this.nClouds; i++) {
const c = new Cloud();
// set the rotation and the position of each cloud;
// for that we use a bit of trigonometry
const a = stepAngle * i; // this is the final angle of the cloud
const h = 750 + Math.random() * 200; // this is the distance between the center of the axis and the cloud itself
// Trigonometry!!! I hope you remember what you've learned in Math :)
// in case you don't:
// we are simply converting polar coordinates (angle, distance) into Cartesian coordinates (x, y)
c.mesh.position.y = Math.sin(a) * h;
c.mesh.position.x = Math.cos(a) * h;
// rotate the cloud according to its position
c.mesh.rotation.z = a + Math.PI / 2;
// for a better result, we position the clouds
// at random depths inside of the scene
c.mesh.position.z = -400 - Math.random() * 400;
// we also set a random scale for each cloud
const s = 1 + Math.random() * 2;
c.mesh.scale.set(s, s, s);
// do not forget to add the mesh of each cloud in the scene
this.mesh.add(c.mesh);
}
};
// Now we instantiate the sky and push its center a bit
// towards the bottom of the screen
let sky;
function createSky() {
sky = new Sky();
sky.mesh.position.y = -600;
scene.add(sky.mesh);
}
const AirPlane = function() {
this.mesh = new THREE.Object3D();
// Create the cabin
const geomCockpit = new THREE.BoxGeometry(80, 50, 50, 1, 1, 1);
const matCockpit = new THREE.MeshPhongMaterial({
color: Colors.red,
shading: THREE.FlatShading
});
// we can access a specific vertex of a shape through
// the vertices array, and then move its x, y and z property:
geomCockpit.vertices[4].y -= 10;
geomCockpit.vertices[4].z += 20;
geomCockpit.vertices[5].y -= 10;
geomCockpit.vertices[5].z -= 20;
geomCockpit.vertices[6].y += 30;
geomCockpit.vertices[6].z += 20;
geomCockpit.vertices[7].y += 30;
geomCockpit.vertices[7].z -= 20;
const cockpit = new THREE.Mesh(geomCockpit, matCockpit);
cockpit.castShadow = true;
cockpit.receiveShadow = true;
this.mesh.add(cockpit);
// Create the engine
const geomEngine = new THREE.BoxGeometry(20, 50, 50, 1, 1, 1);
const matEngine = new THREE.MeshPhongMaterial({
color: Colors.white,
shading: THREE.FlatShading
});
const engine = new THREE.Mesh(geomEngine, matEngine);
engine.position.x = 40;
engine.castShadow = true;
engine.receiveShadow = true;
this.mesh.add(engine);
// Create the tail
const geomTailPlane = new THREE.BoxGeometry(15, 20, 5, 1, 1, 1);
const matTailPlane = new THREE.MeshPhongMaterial({
color: Colors.red,
shading: THREE.FlatShading
});
const tailPlane = new THREE.Mesh(geomTailPlane, matTailPlane);
tailPlane.position.set(-35, 25, 0);
tailPlane.castShadow = true;
tailPlane.receiveShadow = true;
this.mesh.add(tailPlane);
// Create the wing
const geomSideWing = new THREE.BoxGeometry(40, 8, 150, 1, 1, 1);
const matSideWing = new THREE.MeshPhongMaterial({
color: Colors.red,
shading: THREE.FlatShading
});
const sideWing = new THREE.Mesh(geomSideWing, matSideWing);
sideWing.castShadow = true;
sideWing.receiveShadow = true;
this.mesh.add(sideWing);
// propeller
const geomPropeller = new THREE.BoxGeometry(20, 10, 10, 1, 1, 1);
const matPropeller = new THREE.MeshPhongMaterial({
color: Colors.brown,
shading: THREE.FlatShading
});
this.propeller = new THREE.Mesh(geomPropeller, matPropeller);
this.propeller.castShadow = true;
this.propeller.receiveShadow = true;
// blades
const geomBlade = new THREE.BoxGeometry(1, 100, 20, 1, 1, 1);
const matBlade = new THREE.MeshPhongMaterial({
color: Colors.brownDark,
shading: THREE.FlatShading
});
const blade = new THREE.Mesh(geomBlade, matBlade);
blade.position.set(8, 0, 0);
blade.castShadow = true;
blade.receiveShadow = true;
this.propeller.add(blade);
this.propeller.position.set(50, 0, 0);
this.mesh.add(this.propeller);
// pilot
this.pilot = new Pilot();
this.pilot.mesh.position.set(-10, 27, 0);
this.mesh.add(this.pilot.mesh);
};
let airplane;
function createPlane() {
airplane = new AirPlane();
airplane.mesh.scale.set(0.25, 0.25, 0.25);
airplane.mesh.position.y = 100;
scene.add(airplane.mesh);
}
const Pilot = function() {
this.mesh = new THREE.Object3D();
this.mesh.name = "pilot";
// angleHairs is a property used to animate the hair later
this.angleHairs = 0;
// Body of the pilot
const bodyGeom = new THREE.BoxGeometry(15, 15, 15);
const bodyMat = new THREE.MeshPhongMaterial({
color: Colors.brown,
shading: THREE.FlatShading
});
const body = new THREE.Mesh(bodyGeom, bodyMat);
body.position.set(2, -12, 0);
this.mesh.add(body);
// Face of the pilot
const faceGeom = new THREE.BoxGeometry(10, 10, 10);
const faceMat = new THREE.MeshLambertMaterial({
color: Colors.pink
});
const face = new THREE.Mesh(faceGeom, faceMat);
this.mesh.add(face);
// Hair element
const hairGeom = new THREE.BoxGeometry(4, 4, 4);
const hairMat = new THREE.MeshLambertMaterial({
color: Colors.brown
});
const hair = new THREE.Mesh(hairGeom, hairMat);
// Align the shape of the hair to its bottom boundary, that will make it easier to scale.
hair.geometry.applyMatrix(
new THREE.Matrix4().makeTranslation(0, 2, 0)
);
// create a container for the hair
const hairs = new THREE.Object3D();
// create a container for the hairs at the top
// of the head (the ones that will be animated)
this.hairsTop = new THREE.Object3D();
// create the hairs at the top of the head
// and position them on a 3 x 4 grid
for (let i = 0; i < 12; i++) {
const h = hair.clone();
const col = i % 3;
const row = Math.floor(i / 3);
const startPosZ = -4;
const startPosX = -4;
h.position.set(startPosX + row * 4, 0, startPosZ + col * 4);
this.hairsTop.add(h);
}
hairs.add(this.hairsTop);
// create the hairs at the side of the face
const hairSideGeom = new THREE.BoxGeometry(12, 4, 2);
hairSideGeom.applyMatrix(
new THREE.Matrix4().makeTranslation(-6, 0, 0)
);
const hairSideR = new THREE.Mesh(hairSideGeom, hairMat);
const hairSideL = hairSideR.clone();
hairSideR.position.set(8, -2, 6);
hairSideL.position.set(8, -2, -6);
hairs.add(hairSideR);
hairs.add(hairSideL);
// create the hairs at the back of the head
const hairBackGeom = new THREE.BoxGeometry(2, 8, 10);
const hairBack = new THREE.Mesh(hairBackGeom, hairMat);
hairBack.position.set(-1, -4, 0);
hairs.add(hairBack);
hairs.position.set(-5, 5, 0);
this.mesh.add(hairs);
const glassGeom = new THREE.BoxGeometry(5, 5, 5);
const glassMat = new THREE.MeshLambertMaterial({
color: Colors.brown
});
const glassR = new THREE.Mesh(glassGeom, glassMat);
glassR.position.set(6, 0, 3);
const glassL = glassR.clone();
glassL.position.z = -glassR.position.z;
const glassAGeom = new THREE.BoxGeometry(11, 1, 11);
const glassA = new THREE.Mesh(glassAGeom, glassMat);
this.mesh.add(glassR);
this.mesh.add(glassL);
this.mesh.add(glassA);
const earGeom = new THREE.BoxGeometry(2, 3, 2);
const earL = new THREE.Mesh(earGeom, faceMat);
earL.position.set(0, 0, -6);
const earR = earL.clone();
earR.position.set(0, 0, 6);
this.mesh.add(earL);
this.mesh.add(earR);
};
//move the hair
Pilot.prototype.updateHairs = function() {
// get the hair
const hairs = this.hairsTop.children;
// update them according to the angle angleHairs
const l = hairs.length;
for (let i = 0; i < l; i++) {
const h = hairs[i];
// each hair element will scale on cyclical basis between 75% and 100% of its original size
h.scale.y = 0.75 + Math.cos(this.angleHairs + i / 3) * 0.25;
}
// increment the angle for the next frame
this.angleHairs += 0.16;
};
function updatePlane() {
// let's move the airplane between -100 and 100 on the horizontal axis,
// and between 25 and 175 on the vertical axis,
// depending on the mouse position which ranges between -1 and 1 on both axes;
// to achieve that we use a normalize function (see below)
const targetY = normalize(mousePos.y, -0.75, 0.75, 25, 175);
const targetX = normalize(mousePos.x, -0.75, 0.75, -100, 100);
// Move the plane at each frame by adding a fraction of the remaining distance
airplane.mesh.position.y +=
(targetY - airplane.mesh.position.y) * 0.1;
// Rotate the plane proportionally to the remaining distance
airplane.mesh.rotation.z =
(targetY - airplane.mesh.position.y) * 0.0128;
airplane.mesh.rotation.x =
(airplane.mesh.position.y - targetY) * 0.0064;
airplane.propeller.rotation.x += 0.3;
}
function normalize(v, vmin, vmax, tmin, tmax) {
const nv = Math.max(Math.min(v, vmax), vmin);
const dv = vmax - vmin;
const pc = (nv - vmin) / dv;
const dt = tmax - tmin;
const tv = tmin + pc * dt;
return tv;
}
let mousePos = {
x: 0,
y: 0
};
// now handle the mousemove event
function handleMouseMove(event) {
// here we are converting the mouse position value received
// to a normalized value varying between -1 and 1
// this is the formula for the horizontal axis:
const tx = -1 + (event.clientX / WIDTH) * 2;
// for the vertical axis, we need to inverse the formula
// because the 2D y-axis goes the opposite direction of the 3D y-axis
const ty = 1 - (event.clientY / HEIGHT) * 2;
mousePos = {
x: tx,
y: ty
};
}
function loop() {
// Rotate the propeller, the sea and the sky
airplane.propeller.rotation.x += 0.3;
sea.mesh.rotation.z += 0.005;
sky.mesh.rotation.z += 0.01;
airplane.pilot.updateHairs();
sea.moveWaves();
// update the plane on each frame
updatePlane();
// render the scene
renderer.render(scene, camera);
// call the loop function again
requestAnimationFrame(loop);
}
function init() {
// set up the scene, the camera and the renderer
createScene();
// add the lights
createLights();
// add the objects
createPlane();
createSea();
createSky();
//add the listener
document.addEventListener("mousemove", handleMouseMove, false);
// start a loop that will update the objects' positions
// and render the scene on each frame
loop();
}
window.addEventListener("load", init, false);
</script>
</body>
</html>
</head>
<style>
.aviator {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
background: linear-gradient(#e4e0ba, #f7d9aa);
}
</style>
</html>