-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAviator.html
677 lines (559 loc) · 20.5 KB
/
Aviator.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
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
<html>
<head lang="en">
<meta charset="UTF-8">
<title>The Making of “The Aviator”</title>
<!--添加three.js。-->
<script src="js/three.js"></script>
<script src="js/OBJLoader.js"></script>
</head>
<body>
<!--添加一个元素作为容器。-->
<!--
<div style="position:absolute; width:100%; height:100%; overflow:hidden; background:-webkit-linear-gradient(#e4e0ba, #f7d9aa); background: linear-gradient(#e4e0ba, #f7d9aa);" id="world">
</div>
-->
<!--video放于下方-->
<video style="position: fixed; right: 0; bottom: 0; min-width: 100%; min-height: 100%; width: auto;height: auto;z-index: -100;" id="video" autoplay></video>
<div id="world"></div>
<!--
<canvas id="canvas" width="640" height="480"></canvas>
<video id="video" autoplay>
</video>
-->
<script>
var Colors = {
red:0xf25346,
white:0xd8d0d1,
brown:0x59332e,
pink:0xF5986E,
brownDark:0x23190f,
blue:0x68c3c0
};
<!--addEventListener() 方法用于向指定元素添加事件句柄。-->
window.addEventListener('load', init, false);
function init() {
// 创建场景,相机和渲染器
createScene();
// 添加光源
createLights();
// 添加对象
createPlane();
createSea();
createSky();
// 调用循环函数,在每帧更新对象的位置和渲染场景
loop();
//添加监听器,检测鼠标是否有移动
document.addEventListener('mousemove', handleMouseMove, false);
}
var video, canvas, context, imageData;
var scene,
camera, fieldOfView, aspectRatio, nearPlane, farPlane, HEIGHT, WIDTH,
renderer, container;
<!--在 createScene 函数中创建场景,相机以及渲染器-->
function createScene() {
// 获取屏幕的宽和高,
// 可以设置相机的宽高比
// 和渲染器的大小
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
// 创建场景
scene = new THREE.Scene();
// 场景中添加一个雾; same color as the
// background color used in the style sheet
scene.fog = new THREE.Fog(0xf7d9aa, 100, 950);
// 创建相机
aspectRatio = WIDTH / HEIGHT;
fieldOfView = 60;
nearPlane = 1;
farPlane = 10000;
//PerspectiveCamera 透视相机
//fieldOfView 视角
//aspectRatio 纵横比
//nearPlane 近平面
//farPlane 远平面
camera = new THREE.PerspectiveCamera(
fieldOfView,
aspectRatio,
nearPlane,
farPlane
);
// 设置相机的位置
camera.position.x = 0;
camera.position.z = 200;
camera.position.y = 100;
// 创建渲染器
renderer = new THREE.WebGLRenderer({
//在 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;
// 在 HTML 创建的容器中添加渲染器的 DOM 元素
// Add the DOM element of the renderer to the
// container we created in the HTML
container = document.getElementById('world');
container.appendChild(renderer.domElement);
video = document.getElementById("video")
canvas = document.createElement("canvas")
canvas.width = parseInt(container.style.width)
canvas.height = parseInt(container.style.height)
canvas.id = 'bg'
context = canvas.getContext("2d")
container.appendChild(canvas)
// 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() {
// 更新渲染器的高度和宽度以及相机的纵横比
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
}
//光源
var hemisphereLight, shadowLight;
function createLights() {
// 环境光源修改场景中的全局颜色和使阴影更加柔和
ambientLight = new THREE.AmbientLight(0xdc8874, .5);scene.add(ambientLight);
// 半球光就是渐变的光;
// 第一个参数是天空的颜色,第二个参数是地上的颜色,第三个参数是光源的强度
hemisphereLight = new THREE.HemisphereLight(0xaaaaaa,0x000000, .9);
// 方向光是从一个特定的方向的照射
// 类似太阳,即所有光源是平行的
// 第一个参数是关系颜色,第二个参数是光源强度
shadowLight = new THREE.DirectionalLight(0xffffff, .9);
// 设置光源的方向。
// 位置不同,方向光作用于物体的面也不同,看到的颜色也不同
shadowLight.position.set(150, 350, 350);
// 开启光源投影
shadowLight.castShadow = true;
// 定义可见域的投射阴影
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;
// 定义阴影的分辨率;虽然分辨率越高越好,但是需要付出更加昂贵的代价维持高性能的表现。
shadowLight.shadow.mapSize.width = 2048;
shadowLight.shadow.mapSize.height = 2048;
// 为了使这些光源呈现效果,只需要将它们添加到场景中
scene.add(hemisphereLight);
scene.add(shadowLight);
}
//首先定义一个大海对象
Sea = function(){
var geom = new THREE.CylinderGeometry(600,600,800,40,10);
geom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2));
// 重点:通过合并顶点,我们确保海浪的连续性
geom.mergeVertices();
// 获得顶点
var l = geom.vertices.length;
// 创建一个新的数组存储与每个顶点关联的值:
this.waves = [];
for (var i=0; i<l; i++){
// 获取每个顶点
var v = geom.vertices[i];
// 存储一些关联的数值
this.waves.push({y:v.y,
x:v.x,
z:v.z,
// 随机角度
ang:Math.random()*Math.PI*2,
// 随机距离
amp:5 + Math.random()*15,
// 在0.016至0.048度/帧之间的随机速度
speed:0.016 + Math.random()*0.032
});
};
var mat = new THREE.MeshPhongMaterial({
color:Colors.blue,
transparent:true,
opacity:.8,
shading:THREE.FlatShading,
});
this.mesh = new THREE.Mesh(geom, mat);
this.mesh.receiveShadow = true;
}
// 现在我们创建一个在每帧可以调用的函数,用于更新顶点的位置来模拟海浪。
Sea.prototype.moveWaves = function (){
// 获取顶点
var verts = this.mesh.geometry.vertices;
var l = verts.length;
for (var i=0; i<l; i++){
var v = verts[i];
// 获取关联的值
var vprops = this.waves[i];
// 更新顶点的位置
v.x = vprops.x + Math.cos(vprops.ang)*vprops.amp;
v.y = vprops.y + Math.sin(vprops.ang)*vprops.amp;
// 下一帧自增一个角度
vprops.ang += vprops.speed;
}
// 告诉渲染器代表大海的几何体发生改变
// 事实上,为了维持最好的性能
// Three.js 会缓存几何体和忽略一些修改
// 除非加上这句
this.mesh.geometry.verticesNeedUpdate=true;
sea.mesh.rotation.z += .005;
}
//实例化大海对象,并添加至场景
var sea;
function createSea(){
sea = new Sea();
// 在场景底部,稍微推挤一下
sea.mesh.position.y = -600;
// 添加大海的网格至场景
scene.add(sea.mesh);
}
//云
Cloud = function(){
// 创建一个空的容器放置不同形状的云
this.mesh = new THREE.Object3D();
// 创建一个正方体
// 这个形状会被复制创建云
var geom = new THREE.BoxGeometry(20,20,20);
// 创建材质;一个简单的白色材质就可以达到效果
var mat = new THREE.MeshPhongMaterial({
color:Colors.white,
});
// 随机多次复制几何体
var nBlocs = 3+Math.floor(Math.random()*3);
for (var i=0; i<nBlocs; i++ ){
// 通过复制几何体创建网格
var m = new THREE.Mesh(geom, mat);
// 随机设置每个正方体的位置和旋转角度
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;
// 随机设置正方体的大小
var s = .1 + Math.random()*.9;
m.scale.set(s,s,s);
// 允许每个正方体生成投影和接收阴影
m.castShadow = true;
m.receiveShadow = true;
// 将正方体添加至开始时我们创建的容器中
this.mesh.add(m);
}
}
// 定义一个天空对象
Sky = function(){
// 创建一个空的容器
this.mesh = new THREE.Object3D();
// 选取若干朵云散布在天空中
this.nClouds = 20;
// 把云均匀地散布
// 我们需要根据统一的角度放置它们
var stepAngle = Math.PI*2 / this.nClouds;
// 创建云对象
for(var i=0; i<this.nClouds; i++){
var c = new Cloud();
// 设置每朵云的旋转角度和位置
// 因此我们使用了一点三角函数
var a = stepAngle*i; //这是云的最终角度
var h = 750 + Math.random()*200; // 这是轴的中心和云本身之间的距离
// 三角函数!!!希望你还记得数学学过的东西 :)
// 假如你不记得:
// 我们简单地把极坐标转换成笛卡坐标
c.mesh.position.y = Math.sin(a)*h;
c.mesh.position.x = Math.cos(a)*h;
// 根据云的位置旋转它
c.mesh.rotation.z = a + Math.PI/2;
// 为了有更好的效果,我们把云放置在场景中的随机深度位置
c.mesh.position.z = -400-Math.random()*400;
// 而且我们为每朵云设置一个随机大小
var s = 1+Math.random()*2;
c.mesh.scale.set(s,s,s);
// 不要忘记将每朵云的网格添加到场景中
this.mesh.add(c.mesh);
}
}
// 现在我们实例化天空对象,而且将它放置在屏幕中间稍微偏下的位置。
var sky;
function createSky(){
sky = new Sky();
sky.mesh.position.y = -600;
scene.add(sky.mesh);
}
//创建飞机
var AirPlane = function() {
this.mesh = new THREE.Object3D();
// 驾驶舱
var geomCockpit = new THREE.BoxGeometry(80,50,50,1,1,1);
var matCockpit = new THREE.MeshPhongMaterial({color:Colors.red, shading:THREE.FlatShading});
// 我们可以通过访问形状中顶点数组中一组特定的顶点
// 然后移动它的 x, y, z 属性:
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;
var cockpit = new THREE.Mesh(geomCockpit, matCockpit);
cockpit.castShadow = true;
cockpit.receiveShadow = true;
this.mesh.add(cockpit);
// 创建引擎
var geomEngine = new THREE.BoxGeometry(20, 50, 50, 1, 1, 1);
var matEngine = new THREE.MeshPhongMaterial({
color: Colors.white,
shading: THREE.FlatShading
});
var engine = new THREE.Mesh(geomEngine, matEngine);
engine.position.x = 40;
engine.castShadow = true;
engine.receiveShadow = true;
this.mesh.add(engine);
// 创建机尾
var geomTailPlane = new THREE.BoxGeometry(15, 20, 5, 1, 1, 1);
var matTailPlane = new THREE.MeshPhongMaterial({
color: Colors.red,
shading: THREE.FlatShading
});
var tailPlane = new THREE.Mesh(geomTailPlane, matTailPlane);
tailPlane.position.set(-35, 25, 0);
tailPlane.castShadow = true;
tailPlane.receiveShadow = true;
this.mesh.add(tailPlane);
// 创建机翼
var geomSideWing = new THREE.BoxGeometry(40, 8, 150, 1, 1, 1);
var matSideWing = new THREE.MeshPhongMaterial({
color: Colors.red,
shading: THREE.FlatShading
});
var sideWing = new THREE.Mesh(geomSideWing, matSideWing);
sideWing.castShadow = true;
sideWing.receiveShadow = true;
this.mesh.add(sideWing);
// 创建螺旋桨
var geomPropeller = new THREE.BoxGeometry(20, 10, 10, 1, 1, 1);
var 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;
// 创建螺旋桨的桨叶
var geomBlade = new THREE.BoxGeometry(1, 100, 20, 1, 1, 1);
var matBlade = new THREE.MeshPhongMaterial({
color: Colors.brownDark,
shading: THREE.FlatShading
});
var 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);
//添加飞行员
this.pilot = new Pilot();
this.pilot.mesh.position.set(-10,27,0);
this.mesh.add(this.pilot.mesh);
};
var airplane;
function createPlane(){
airplane = new AirPlane();
airplane.mesh.scale.set(.25,.25,.25);
airplane.mesh.position.y = 100;
scene.add(airplane.mesh);
}
//创建飞行员
var Pilot = function(){
this.mesh = new THREE.Object3D();
this.mesh.name = "pilot";
// angleHairs是用于后面头发的动画的属性
this.angleHairs=0;
// 飞行员的身体
var bodyGeom = new THREE.BoxGeometry(15,15,15);
var bodyMat = new THREE.MeshPhongMaterial({color:Colors.brown, shading:THREE.FlatShading});
var body = new THREE.Mesh(bodyGeom, bodyMat);
body.position.set(2,-12,0);
this.mesh.add(body);
// 飞行员的脸部
var faceGeom = new THREE.BoxGeometry(10,10,10);
var faceMat = new THREE.MeshLambertMaterial({color:Colors.pink});
var face = new THREE.Mesh(faceGeom, faceMat);
this.mesh.add(face);
// 飞行员的头发
var hairGeom = new THREE.BoxGeometry(4,4,4);
var hairMat = new THREE.MeshLambertMaterial({color:Colors.brown});
var hair = new THREE.Mesh(hairGeom, hairMat);
// 调整头发的形状至底部的边界,这将使它更容易扩展。
hair.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0,2,0));
// 创建一个头发的容器
var hairs = new THREE.Object3D();
// 创建一个头发顶部的容器(这会有动画效果)
this.hairsTop = new THREE.Object3D();
// 创建头顶的头发并放置他们在一个3*4的网格中
for (var i=0; i<12; i++){
var h = hair.clone();
var col = i%3;
var row = Math.floor(i/3);
var startPosZ = -4;
var startPosX = -4;
h.position.set(startPosX + row*4, 0, startPosZ + col*4);
this.hairsTop.add(h);
}
hairs.add(this.hairsTop);
// 创建脸庞的头发
var hairSideGeom = new THREE.BoxGeometry(12,4,2);
hairSideGeom.applyMatrix(new THREE.Matrix4().makeTranslation(-6,0,0));
var hairSideR = new THREE.Mesh(hairSideGeom, hairMat);
var hairSideL = hairSideR.clone();
hairSideR.position.set(8,-2,6);
hairSideL.position.set(8,-2,-6);
hairs.add(hairSideR);
hairs.add(hairSideL);
// 创建后脑勺的头发
var hairBackGeom = new THREE.BoxGeometry(2,8,10);
var 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);
var glassGeom = new THREE.BoxGeometry(5,5,5);
var glassMat = new THREE.MeshLambertMaterial({color:Colors.brown});
var glassR = new THREE.Mesh(glassGeom,glassMat);
glassR.position.set(6,0,3);
var glassL = glassR.clone();
glassL.position.z = -glassR.position.z;
var glassAGeom = new THREE.BoxGeometry(11,1,11);
var glassA = new THREE.Mesh(glassAGeom, glassMat);
this.mesh.add(glassR);
this.mesh.add(glassL);
this.mesh.add(glassA);
var earGeom = new THREE.BoxGeometry(2,3,2);
var earL = new THREE.Mesh(earGeom,faceMat);
earL.position.set(0,0,-6);
var earR = earL.clone();
earR.position.set(0,0,6);
this.mesh.add(earL);
this.mesh.add(earR);
}
// 移动头发
Pilot.prototype.updateHairs = function(){
// 获得头发
var hairs = this.hairsTop.children;
// 根据 angleHairs 的角度更新头发
var l = hairs.length;
for (var i=0; i<l; i++){
var h = hairs[i];
// 每根头发将周期性的基础上原始大小的75%至100%之间作调整。
h.scale.y = .75 + Math.cos(this.angleHairs+i/3)*.25;
}
// 在下一帧增加角度
this.angleHairs += 0.16;
}
function loop(){
// 使螺旋桨旋转并转动大海和云
//无限循环
airplane.propeller.rotation.x += 0.3;
sea.mesh.rotation.z += .005;
sky.mesh.rotation.z += .01;
// 更新每帧的飞机
updatePlane();
//更新头发
airplane.pilot.updateHairs();
//更新海浪
sea.moveWaves();
// 渲染场景
renderer.render(scene, camera);
// 重新调用 render() 函数
//将渲染器的 render() 函数移动到 loop() 函数中。因为每次修改物体的位置或颜色之类的属性就需要重新调用一次 render() 函数。
requestAnimationFrame(loop);
}
var mousePos={x:0, y:0};
// mousemove 事件处理函数
function handleMouseMove(event) {
// 这里我把接收到的鼠标位置的值转换成归一化值,在-1与1之间变化
// 这是x轴的公式:
var tx = -1 + (event.clientX / WIDTH)*2;
// 对于 y 轴,我们需要一个逆公式
// 因为 2D 的 y 轴与 3D 的 y 轴方向相反
var ty = 1 - (event.clientY / HEIGHT)*2;
mousePos = {x:tx, y:ty};
}
function updatePlane(){
// 让我们在x轴上-100至100之间和y轴25至175之间移动飞机
// 根据鼠标的位置在-1与1之间的范围,我们使用的 normalize 函数实现(如下)
var targetX = normalize(mousePos.x, -1, 1, -100, 100);
var targetY = normalize(mousePos.y, -1, 1, 25, 175);
// 更新飞机的位置
airplane.mesh.position.y = targetY;
airplane.mesh.position.x = targetX;
airplane.propeller.rotation.x += 0.3;
}
function normalize(v,vmin,vmax,tmin, tmax){
var nv = Math.max(Math.min(v,vmax), vmin);
var dv = vmax-vmin;
var pc = (nv-vmin)/dv;
var dt = tmax-tmin;
var tv = tmin + (pc*dt);
return tv;
}
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
if (navigator.getUserMedia === undefined) {
navigator.getUserMedia = function(constraints) {
var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
}
window.URL = (window.URL || window.webkitURL || window.mozURL || window.msURL);
var mediaOpts = {
audio: false,
// Prefer camera resolution nearest to 1280x720.
video: true,
}
function successFunc(stream) {
// video = document.getElementById("video")
var video = document.querySelector('video');
if ("srcObject" in video) {
video.srcObject = stream
} else {
video.src = window.URL && window.URL.createObjectURL(stream) || stream
}
video.play();
}
function errorFunc(err) {
alert(err.name);
}
MediaErr = function (error)
{
if (error.PERMISSION_DENIED)
{
alert('用户拒绝了浏览器请求媒体的权限', '提示');
} else if (error.NOT_SUPPORTED_ERROR) {
alert('对不起,您的浏览器不支持拍照功能,请使用其他浏览器', '提示');
} else if (error.MANDATORY_UNSATISFIED_ERROR) {
alert('指定的媒体类型未接收到媒体流', '提示');
} else {
alert('系统未能获取到摄像头,请确保摄像头已正确安装。或尝试刷新页面,重试', '提示');
}
};
navigator.getUserMedia(mediaOpts, successFunc, MediaErr);
</script>
</body>
</html>