The Aviator源码分析(部分内容有改动)

The-Aviator

  • part one
    这里写图片描述
    嘿嘿。我就是喜欢放一些没什么关系的图片= =。。。
    来,重飞大师的小飞机,第一部分:
    简单粗暴,直接上代码
    (我有改过、
        我有改过、
        我有改过
        不是跟源码完全一样哈)
<!DOCTYPE html>
<html lang="en" class="no-js">
    <head>
        <meta charset="UTF-8" />
        <!-- 兼容性设置 -->
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>小飞机1</title>
        <meta name="description" content="Demo 1:使用Three.js来搭建一个基础3D场景" />
        <meta name="作者" content="Karim Maaloul" />
        <link rel="shortcut icon" href="favicon.ico">
        <link rel="stylesheet" href="css/font-awesome.min.css">

        <!-- <link href='https://fonts.googleapis.com/css?family=Playfair+Display:400,700,700italic' rel='stylesheet' type='text/css'> -->
        <link rel="stylesheet" type="text/css" href="css/css.css" />
        <link rel="stylesheet" type="text/css" href="css/demo.css" />
        <link rel="stylesheet" type="text/css" href="css/styles.css" />
        <!-- three.js是JavaScript编写的WebGL第三方库 -->
        <script type="text/javascript" src="js/three.min.js"></script>
        <script type="text/javascript" src="js/main_step1.js"/></script>
        <!--[if IE]>
        <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>
    <body>
        <div class="icon-test-list"></div>
        <div class="world" id="world"></div>
        <nav class="meta">
            <!-- <a class="codrops-icon codrops-icon--prev" href="http://tympanus.net/Development/Interactive3DMallMap/" title="Previous Demo"><span>Previous Demo</span></a> -->
            <a class="fa fa-heart" href="http://tympanus.net/codrops/?p=26501" title="返回文章">返回文章</a>
            <a href="part1.html"><i class="fa fa-cog fa-spin fa-1g fa-fw"></i>动了动</a>
            <a class="fa fa-heart" href="part1_1.html">飞机本尊</a>
            <!-- <a href="index.html" title="游戏">游戏</a> -->

        </nav>
    </body>
</html>

然后是史上最全注释系列(笑)之对象的创建,各种各种各种……

//定义一个调色板
var Colors = {
    red:0xf25346,
    white:0xd8d0d1,
    brown:0x59332e,
    pink:0xFFF0F5,
    brownDark:0x23190f,
    blue:0x6495ED,
};

//创建场景(scene场景是所有相机的容器,它可以看成一个所有的对象渲染的平台)
//相机(决定了场景中哪个角度的景色会显现出来,此项目中我们使用PerspectiveCamera透视相机,也可以使用OrthographicCamera正交投影相机)
//渲染器(渲染器会将所有的场景在WebGL上渲染出来,决定了渲染的结果应该显示在页面的什么元素上,并以怎样的方式绘制)
var scene,
    camera, fieldOfView, aspectRatio, nearPlane, farPlane,
    renderer, container;

//SCREEN & MOUSE VARIABLES

var HEIGHT, WIDTH,
    mousePos = { x: 0, y: 0 };

//INIT THREE JS, SCREEN AND MOUSE EVENTS

function createScene() {
  //获取屏幕的宽和高,用他们设置相机的纵横比和渲染器的大小
  HEIGHT = window.innerHeight;
  WIDTH = window.innerWidth;

  //创建场景
  scene = new THREE.Scene();

  //创建相机
  aspectRatio = WIDTH / HEIGHT;
  fieldOfView = 60;
  nearPlane = 1;
  farPlane = 10000;

  /* PerspectiveCamera 透视相机 遵循近大远小的空间规则
     fieldOfView 视角
     aspectRatio 纵横比
     nearPlane 近平面
     farPlane 远平面
     */
  camera = new THREE.PerspectiveCamera(
    fieldOfView,
    aspectRatio,
    nearPlane,
    farPlane
    );
  // var axisHelper = new axisHelper();
  // scene.add(axisHelper);

  //在场景中添加雾的效果,样式上使用和背景一样的颜色
  //fog对象的构造函数,用来在场景内创建线性雾效,线性雾效就是从雾效的起始点参数near,到结束点参数far雾效强度线性递增。
  //fog对象的功能函数采用定义构造的函数原型对象来实现。
  //用法: var fog = new THREE.Fog(THREE.colorKeywords.cyan,near,far); 
  //从相机的起始处长度为100开始,950结束的区域在场景中添加雾效,雾效的颜色是0xf7d9aa
  /*0xAARRGGBB
    #RRGGBB
    rgba(r,g,b,a)      --- (0<=a<=1)*/
  scene.fog = new THREE.Fog(0xf7d9aa, 100, 950);

  //设置摄像机的坐标,控制相机在整个3D环境中的位置
  camera.position.x = 0;
  camera.position.z = 200;
  camera.position.y = 100;
  // camera.up.x = 0;  //相机以哪个方向为上方
  // camera.up.y = 1;
  // camera.up.z = 0;
  // camera.lookAt({x:0, y:0, z:-10});  //相机看向哪个坐标
  //代码设置z轴为1,表示以z轴为相机的上方。(默认y轴为上方)
  //就是躺着看,趴着看,侧着看的区别(手机正着拍,倒着拍,旋转拍~~~
  //控制相机的焦点位置,决定相机的朝向(取值为三维坐标)
  //camera.lookAt(new THREE.Vector3(0,0,0));

  //创建渲染器
  renderer = new THREE.WebGLRenderer({ 
    //允许背景透明,这样可以显示我们在css中定义的背景色
    alpha: true, 
    //开启抗锯齿(抗图像折叠失真)效果;性能会变低
    //由于在3D图像中,受分辨的制约,物体边缘总会或多或少的呈现三角形的锯齿,
    //而抗锯齿就是指对图像边缘进行柔化处理,使图像边缘看起来更平滑,更接近实物的物体。
    //它是提高画质以使之柔和的一种方法。
    antialias: true 
  });

  //定义渲染器的尺寸,此项目中它充满整个屏幕
  renderer.setSize(WIDTH, HEIGHT);

  //启用阴影渲染(shadowMap是阴影渲染的一种方法)
  renderer.shadowMap.enabled = true;

  //将渲染器元素追加到我们在HTML里创建的容器元素里
  container = document.getElementById('world');
  container.appendChild(renderer.domElement);

  //监听屏幕:如果用户改变屏幕尺寸,必须更新摄像机和渲染器的尺寸
  window.addEventListener('resize', handleWindowResize, false);
}

// HANDLE SCREEN EVENTS
//随着屏幕尺寸的改变,我们需要更新渲染器的尺寸和摄像机的纵横比
function handleWindowResize() {
  //更新渲染器和摄像机的宽高
  HEIGHT = window.innerHeight;
  WIDTH = window.innerWidth;
  renderer.setSize(WIDTH, HEIGHT);
  camera.aspect = WIDTH / HEIGHT;
  camera.updateProjectionMatrix();
}


// LIGHTS
//光源

var ambientLight, hemisphereLight, shadowLight;

function createLights() {
  //创建半球光(天光效果,经常用在室外,将各个位置的物体都照亮);
  //其余还有聚光灯(THREE.SpotLight)[可产生阴影]、平行光源(THREE.DirectionalLight)[可产生阴影]、环境光源(THREE.AmbientLight)、区域光(AreaLight)、点光源(THREE.PointLight)
  //THREE.HemisphereLight = function (skyColor, groundColor, intensity)
  hemisphereLight = new THREE.HemisphereLight(0xaaaaaa,0x000000, .9) //浅灰,黑

  //平行光
  //shadowLight = new THREE.DirectionalLight(16进制的颜色值,光线的强度[默认为1])
  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);
}


//创建对象的步骤:
//1.创建几何体;
//2.创建材质;
//3.将它们传入网格(Mesh);
//4.将网格添加至场景.
//three.js中有很多可用的简单形状,比如立方体、球、环形、圆柱和平面
//定义小飞机
var AirPlane = function(){

  //创建一个空的容器(顺便起个名)
    this.mesh = new THREE.Object3D();
  this.mesh.name = "airPlane";

  //机舱
  var geomCockpit = new THREE.BoxGeometry(80,50,50,1,1,1);
  var matCockpit = new THREE.MeshPhongMaterial({color:Colors.red, shading:THREE.FlatShading});

  //vertices 顶点
  //这里 访问它的定点改变定点的位置
  //4 5是在飞机尾部上方的两个点
  //6 7是飞机尾部下方的两个点
  //可以通过访问形状中顶点数组中一组特定的顶点
  //然后移动它的 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;
  cockpit.position.x = -60;
  this.mesh.add(cockpit);

  //Create the cabin  创建机舱
  //立方体:BoxGeometry(width, height, dept, widthSegments, heightSegments, depthSegments)
  //-width,height,dept分别是长宽高,-widthSegments, heightSegments, deptSegments是对应长宽高的分段,在使用线模式({wireframe:true})
    //var geomCockpit = new THREE.BoxGeometry(60,50,50,1,1,1);

  //创建材
  //shading着色处理
  //着色方式
             /*绝大多数的3D物体是由多边形(polygon)所构成的,它们都必须经过某些着色处理的手续,才不会以线结构(wireframe)的方式显示。
             这些着色处理方式有差到好,依次主要分为FlatShading、GouraudShading、PhoneShading、ScanlineRenderer、Ray-Traced。
            FlatShading(平面着色)
                也叫做“恒量着色”,平面着色是最简单也是最快速的着色方法,每个多边形都会被指定一个单一且没有变化的颜色。这种方法虽然会产生出不真实
                的效果,不过它非常适用于快速成像及其它要求速度重于细致度的场合,如:生成预览动画。

            GouraudShading(高洛德着色/高氏着色)
                这种着色的效果要好得多,也是在游戏中使用最广泛的一种着色方式。它可对3D模型各顶点的颜色进行平滑、融合处理,将每个多边形上的每个点
                赋以一组色调值,同时将多边形着上较为顺滑的渐变色,使其外观具有更强烈的实时感和立体动感,不过其着色速度比平面着色慢得多。

            PhoneShading(补色着色)
                首先,找出每个多边形顶点,然后根据内插值的方法,算出顶点间算连线上像素的光影值,接着再次运用线性的插值处理,算出其他所有像素高氏着
                色在取样计算时,只顾及每个多边形顶点的光影效果,而补色着色却会把所有的点都计算进去。

            ScanlineRenderer(扫描线着色)
                这是3ds Max的默认渲染方式,它是一种基于一组连续水平线的着色方式,由于它渲染速度较快,一般被使用在预览场景中。

            Ray-Traced(光线跟踪着色)
                光线跟踪是真实按照物理照射光线的入射路径投射在物体上,最终反射回摄象机所得到每一个像素的真实值的着色算法,由于它计算精确,所得到的
                图象效果优质,因此制作CG一定要使用该选项。

            Radiosity(辐射着色)
                这是一种类似光线跟踪的特效。它通过制定在场景中光线的来源并且根据物体的位置和反射情况来计算从观察者到光源的整个路径上的光影效果。在
                这条线路上,光线受到不同物体的相互影响,如:反射、吸收、折射等情况都被计算在内。


            glShadeModel( GLenum mode )可以设置的着色模型有:GL_SMOOTH和GL_FLAT
                GL_FLAT恒定着色:对点,直线或多边形采用一种颜色进行绘制,整个图元的颜色就是它的任何一点的颜色。
                GL_SMOOTH平滑着色:用多种颜色进行绘制,每个顶点都是单独进行处理的,各顶点和各图元之间采用均匀插值。*/
  //var matCockpit = new THREE.MeshPhongMaterial({
    //color:Colors.red, shading:THREE.FlatShading});
  //var cockpit = new THREE.Mesh(geomCockpit, matCockpit);

  //castShadow = true 把模型设置为生成投影
    //cockpit.castShadow = true;

  //receiveShadow = true 把模型设置为接受阴影
  //cockpit.receiveShadow = true;

  //将模型追加到场景中
  //this.mesh.add(cockpit);

  // Create Engine  创建引擎
  var geomEngine = new THREE.BoxGeometry(50,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 = 0;
  engine.castShadow = true;
  engine.receiveShadow = true;
    this.mesh.add(engine);

  // Create Tailplane 创建机尾
  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(-90,25,0);
  tailPlane.castShadow = true;
  tailPlane.receiveShadow = true;
    this.mesh.add(tailPlane);

  // Create Wing  创建机翼
  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.position.set(-50,0,0);
  sideWing.castShadow = true;
  sideWing.receiveShadow = true;
    this.mesh.add(sideWing);

  //Propeller  创建螺旋桨
  var geomPropeller = new THREE.BoxGeometry(10,50,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;

  //Blades  创建螺旋桨的桨叶
  var geomBlade = new THREE.BoxGeometry(100,1,20,1,1,1);
  var matBlade = new THREE.MeshPhongMaterial({
    color:Colors.brownDark, shading:THREE.FlatShading});

  var geomBladee = new THREE.BoxGeometry(20,1,100,1,1,1);
  var matBladee = new THREE.MeshPhongMaterial({
    color:Colors.brownDark, shading:THREE.FlatShading});

  var bladee = new THREE.Mesh(geomBladee, matBladee);
  bladee.position.set(0,25,0);
  bladee.castShadow = true;
  bladee.receiveShadow = true;
  this.propeller.add(bladee);

  var blade = new THREE.Mesh(geomBlade, matBlade);
  blade.position.set(0,25,0);
  blade.castShadow = true;
  blade.receiveShadow = true;
    this.propeller.add(blade);
  this.propeller.position.set(0,15,0);
  this.mesh.add(this.propeller);
};

//定义天空对象
Sky = function(){

  //创建一个空的容器
  this.mesh = new THREE.Object3D();

  //设定散落在天空中云朵的数量
  this.nClouds = 20;

  //创建云朵对象
  this.clouds = [];

  //为了始终如一地散布云,我们需要把它们按统一的角度摆放。  
  var stepAngle = Math.PI*2 / this.nClouds;

  //创建云朵
  for(var i=0; i<this.nClouds; i++){
    var c = new Cloud();

    //放到对象数组中
    //this.clouds.push(c);

    //每朵云设置角度和位置
    var a = stepAngle*i;  //云最终的角度
    var h = 700 + Math.random()*200;  //轴中心到云的距离

    //将极坐标(角度、距离)转换成笛卡尔坐标(x,y)
    //运用三角函数知识(以轴心为原点r,边为h,y = sin(x轴夹角度)*h)
    c.mesh.position.y = Math.sin(a)*h;
    c.mesh.position.x = Math.cos(a)*h;

    //有远有近(增强真实感)
    c.mesh.position.z = -400-Math.random()*400;

    //根据云的位置做旋转
    c.mesh.rotation.z = a + Math.PI/2;

    //给每朵云设置比例
    var s = 1+Math.random()*2;
    c.mesh.scale.set(s,s,s);

    //将每朵云追加到场景中
    this.mesh.add(c.mesh);
  }
}

//用圆柱创建一个大海对象
//先定义一个大海对象
Sea = function(){

  //创建一个圆柱几何体Geometry
  //设置参数THREE.CylinderGeometry(上表面半径,下表面半径,高度,对象的半径方向的细分线段数,对象的高度细分线段数);
  var geom = new THREE.CylinderGeometry(600,600,800,40,10);

  //让他在x轴上旋转
  ///makeRotationX方法生成绕x轴转(-Math.PI/2)弧度的旋转矩阵
  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:.6,
    shading:THREE.FlatShading,
  });

  //在Three.js里创建一个物体Object,我们必须创建一个Mesh对象
  //mesh对象就是Geometry创建的框架贴上材质Material最后形成的总体
  this.mesh = new THREE.Mesh(geom, mat);

  //允许大海接收阴影
  this.mesh.receiveShadow = true;
}

//使得sea看上去更加有意思一点我们使它的的表面凹凸不平,所以我们为它增加一个方法moveWaves
//这里用到了原型继承的知识
//现在我们创建一个在每帧可以调用的函数,用于更新顶点的位置来模拟海浪。
//Prototype 是全局属性,适用于所有的Javascript对象。
//prototype 属性允许向对象添加属性和方法
//此处含义为向sea对象添加名为moveWaves的方法
Sea.prototype.moveWaves = function (){

  //获取顶点
  //vertices是一个数组
  //Geometry是场景中由顶点和三角面构成的几何体对象的基类,保存描述几何体所有必要的数据.
  //Geometry对象的功能函数采用定义构造的函数原型对象来实现.
  var verts = this.mesh.geometry.vertices;

  //共有l个顶点
  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 会缓存几何体和忽略一些修改 
    //除非加上这句 
    //物体每一帧都会改变自己的顶点
    //所以需要每一帧都需要将其verticesNeedUpdate属性设为true来告诉renderer我需要重新传输数据了
    //因为three.js对程序的优化,在renderer中第一次初始化geometry, material的时候,
    //如果判断为没有纹理,尽管内存中的数据中有每个顶点uv数据,但three.js还是不会将这些数据copy到显存中,
    //初衷应该还是为了节省点宝贵的显存空间,但是在添加纹理后geometry并不会很智能的重新去传输这些uv数据以供纹理使用,必须要我们手动的将设置uvsNeedsUpdate来告知它该更新uv了
    this.mesh.geometry.verticesNeedUpdate=true; 
    sea.mesh.rotation.z += .005;
  }

//定义云对象
Cloud = function(){
  //创建一个空的容器用来存放不同部分的云(顺便起个名儿)
  this.mesh = new THREE.Object3D();
  this.mesh.name = "cloud";

  //创建一个立方体;复制多个来创建云
  //THREE.CubeGeometry(width,height,depth,widthSegments,heightSegments, depthSegments)
  //width:x方向上的长度;height:y方向上的长度;depth:z方向上的长度;
  //widthSegments:x方向上的分段数(可选,缺省值1);heightSegments:y方向上的分段数(同上);depthSegments:z方向上的分段数(同上)
  //var geom = new THREE.CubeGeometry(20,20,20);
  var geom =new THREE.SphereGeometry(20,20,20);

  //创建云的材质,白色
  var mat = new THREE.MeshPhongMaterial({
    color:Colors.white,
  });

  //随机定义要复制的几何体数量
  //Math.random()*3会产生一个[0,3)的数
  //Math.floor()由上面的语句产生的数值进行向下取整
  //例如产生的数为5.5,则math.floor(5.5)=5
  var nBlocs = 3+Math.floor(Math.random()*3);
  for (var i=0; i<nBlocs; i++ ){

    //给复制的几何体创建Mesh对象
    //geom.clone()对geom对象的一个拷贝
    var m = new THREE.Mesh(geom.clone(), mat);

    //随机设置每个正方体的旋转角度以及每个正方体的位置
    m.position.x = i*15;  //固定x
    m.position.y = Math.random()*10;  //随机y轴位置 大于0
    m.position.z = Math.random()*10;  //随机z轴位置
    m.rotation.z = Math.random()*Math.PI*2;  //随机z轴旋转角度
    m.rotation.y = Math.random()*Math.PI*2;  //随机y轴旋转角度

    //随机的设置立方体的尺寸
    var s = .1 + Math.random()*.9;
    m.scale.set(s,s,s);

    //允许没多云生成投影和接收投影
    m.castShadow = true;
    m.receiveShadow = true;

    //把几何体追加到上面我们创建的容器中
    this.mesh.add(m);
  }
}

// 3D Models
var sea;
var airplane;

//实例化飞机
function createPlane(){
  airplane = new AirPlane();
  airplane.mesh.scale.set(.25,.25,.25);
  airplane.mesh.position.y = 100;
  scene.add(airplane.mesh);
}

//实例化大海对象,把他添加到场景scene中
function createSea(){
  sea = new Sea();

  //把它放到屏幕下方
  sea.mesh.position.set(0, -600, 0);

  //在场景中追加大海的mesh对象
  scene.add(sea.mesh);
}

//实例化天空对象,并把它的中心点放到屏幕下方
function createSky(){
  sky = new Sky();
  sky.mesh.position.y = -200;
  scene.add(sky.mesh);
}

function loop(){

  //更新每帧的飞机(更新位置)
  updatePlane();

  //更新海浪
  sea.moveWaves();

  // = =
  sea.mesh.rotation.z += .005;
  sky.mesh.rotation.z += .01;

  //渲染场景(渲染后才能看到)
  //渲染器的 render() 函数
  //每次修改物体的位置或颜色之类的属性就需要重新调用一次 render() 函数
  renderer.render(scene, camera);

  //重新调用render()函数
  requestAnimationFrame(loop);
}

function updatePlane(){

  //让我们在x轴上-100至100之间和y轴25至175之间移动飞机
  //根据鼠标的位置在-1与1之间的范围,使用的normalize函数实现
  //mousePos.x:获取鼠标的x坐标值
  //mousePos.y:获取鼠标的y坐标值
  var targetY = normalize(mousePos.y, -.75, .75, 25, 175);
  var targetX = normalize(mousePos.x, -.75, .75, -100, 100);

  //更新飞机的位置
  airplane.mesh.position.y = targetY;
  airplane.mesh.position.x = targetX;
  airplane.propeller.rotation.y += 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;
}

//初始化函数
function init(event){

  //事件监听机制
  //事件:是用户自身或者浏览器进行的特定行为(如:用户点击事件 click[click即为事件名称])
  //事件流:多个事件,按一定顺序触发,形成了事件流;事件流说明时间对象如何在显示列表中穿行。
  //显示列表以一种描述为树的层次结构形式进行组织。
  //位于显示列表层次结构顶部的是舞台,它是一种特殊的显示对象容器,用作显示列表的根。
  //事件流分为三部分,1.捕获阶段:根节点到子节点(目标节点);2.目标阶段:目标节点本身;3.冒泡阶段:目标本身到根节点
  //事件处理函数(监听函数):事件触发后的处理函数
  //监听鼠标的移动实现交互并处理相应的函数
  //三种时刻实现监听:mousedown,mousemove,mouseup
  //对应的动作执行相对应的事件:handleMouseMove
  //addEventListener(type:String, listener:Function, useCapture:Boolean = false)
  //type:String 事件的类型。
  //listener:Function 侦听到事件后处理事件的函数。
  //useCapture:Boolean (default = false) 此处的参数确定侦听器是运行于捕获阶段、 目标阶段还是冒泡阶段。 
  //如果将useCapture设置为true,则侦听器只在捕获阶段处理事件,而不在目标或冒泡阶段处理事件。
  //如果useCapture为false,则侦听器只在目标或冒泡阶段处理事件。 
  //要在所有三个阶段都侦听事件,请调用两次 addEventListener,一次将useCapture设置为true,第二次再将useCapture设置为false。
  document.addEventListener('mousemove', handleMouseMove, true);

  //设置场景scene,摄像机camera和渲染器renderer
  createScene();

  //添加光源lights
  //three.js有很多不同类型的可用光源。
  //此项目中,主要使用半球光(HemisphereLight)来设置
  createLights();

  //添加物体objects
  createPlane();
  createSea();
  createSky();

  //循环,更新物体的位置,并每一帧渲染场景
  loop();
}

// HANDLE MOUSE EVENTS
//获取鼠标的x,y坐标值,用鼠标移动飞机
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};
}

window.addEventListener('load', init, false);

噢噢!最后差点忘了……我们的世界

/*类选择器 class (id选择器#)*/
.world {
    position: absolute;
    width: 100%;
    height: 100%;
    overflow: hidden;
    /*添加渐变效果*/
    background: -webkit-linear-gradient(#e4e0ba, #f7d9aa);
    background: linear-gradient(#e4e0ba, #f7d9aa);
}

以及,我们的样式



@font-face {              
    font-family: 'iconfont';              
    src: url('//http://at.alicdn.com/t/font_1469410270_4028482.eot'); /* IE9*/            
    src: url('//http://at.alicdn.com/t/font_1469410270_4028482.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */            
    url('//http://at.alicdn.com/t/font_1469410270_4028482.woff') format('woff'), /* chrome、firefox */             
    url('//http://at.alicdn.com/t/font_1469410270_4028482.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/          
    url('//http://at.alicdn.com/t/font_1469410270_4028482.svg#iconfont') format('svg'); /* iOS 4.1- */          
    }
*,
*::after,
*::before {
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

body {
    font-family: 'Avenir Next', Avenir, 'Helvetica Neue', Helvetica, Arial, sans-serif;
    margin: 0;
    color: #444;
    background: #f6f6f6;
}

a {
    text-decoration: none;
    color: #d1b790;
    outline: none;
}

a:hover,
a:focus {
    color: #bba077;
    outline: none;
}



/* 导航 */

.meta {
    /*em相对于元素父元素的font-size来计算*/
    font-size: 0.75em;
    line-height: 1;
    position: absolute;
    top: 1em;
    left: 1em;
    /*display flex是将对象作为弹性伸缩盒显示*/
    display: -webkit-flex;
    display: flex;
    flex-direction: column;
    /*如果一行排不下换行*/
    -webkit-flex-wrap: wrap;
    flex-wrap: wrap;
    /*white-space : normal | pre | nowrap*/
    /*normal  :  默认值。默认处理方式。文本自动处理换行。假如抵达容器边界内容会转到下一行 */
    /*pre  :  换行和其他空白字符都将受到保护。这个值需要IE6+或者 !DOCTYPE 声明为 standards-compliant mode 支持。如果 !DOCTYPE 声明没有指定为 standards-compliant mode ,此属性可以使用,但是不会发生作用。结果等同于 normal 。参阅 pre 对象 */
    /*nowrap  :  强制在同一行内显示所有文本,直到文本结束或者遭遇 br 对象。*/
    white-space: nowrap;
    /*上边框1px厚,实线,#d1b790色的边框线*/
    border-top: 1px solid #d1b790;
}


/*右侧边框属性*/
.meta::after {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    width: 1px;
    height: 100%;
    background: #d1b790;

}

.meta a {
    /*a本身为行内元素(inline),强制改为块元素*/
    /*display:block;比较常用于<a><span>这两个标签,因为他们不是块级元素,定义display:block属性后,定义width、height等和长宽相关的css属性才会生效。*/
    display: block;
    padding: 1em 1.35em;
    border: 1px solid #d1b790;
    border-top: 0;
}

.meta a:not(:last-child) {
    border-right:0;
}

.codrops-icon {
    width: 4em;
}

.codrops-icon span {
    display: none;
}

.codrops-icon::before {
    font-family: 'codropsicons';
    font-size: 1.15em;
    font-weight: normal;
    font-style: normal;
    font-variant: normal;
    line-height: 1;
    text-transform: none;
    speak: none;
    -webkit-font-smoothing: antialiased;
}

.codrops-icon--drop::before {
    /*content: '\e001';*/
    color: #09c;
}

.codrops-icon--prev::before {
    content: '';
}


/* Demo links */

.demo-link {
    font-family: 'Playfair Display';
    font-weight: bold;
    text-align: center;
    letter-spacing: 0.2em;
    text-transform: uppercase;
}

.demo-link--current {
    color: #bba077;
}

/*利用@media screen实现网页布局的自适应*/
/*例如:1280分辨率以上(大于1200px)@media screen and (min-width:1200px);*/
/*1100分辨率(大于960px,小于1199px)@media screen and (min-width: 960px) and (max-width: 1199px) */
@media screen and (max-width:40em) {
    .meta {
        right: 1em;
    }
    .demo-link {
        -webkit-flex: 1;
        flex: 1;
    }
}


/* Styles for sponsor */
body #cdawrap { top: auto; right: auto; bottom: 12px; left: 12px; border: 1px solid #d1b790; background: none; }
.partisan { position: absolute; bottom: 0; left: 0; width: 38em; padding: 4.5em 7.5em 1.5em 5.5em; }
.partisan__bg { position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; pointer-events: none; opacity: 0.5; }
.partisan__link { position: relative; display: -webkit-flex; display: flex; -webkit-justify-content: center; justify-content: center; -webkit-align-items: center; align-items: center; }
.partisan__img, .partisan__title { opacity: 0.85; -webkit-transition: opacity 0.1s; transition: opacity 0.1s; }
.partisan__link:hover .partisan__img, .partisan__link:hover .partisan__title { opacity: 1; }
.partisan__img { max-width: 42%; }
.partisan__title { font-family: 'Playfair Display'; font-weight: bold; position: relative; margin: 0.5em 0 0 0.85em; color: #555d27; }
.partisan__title::before { content: 'Sponsored by:'; font-size: 0.5em; font-weight: bold; position: absolute; bottom: 100%; left: 0; padding: 0 0 1em 0; letter-spacing: 0.25em; text-transform: uppercase; color: #95a534; }
@media screen and (max-width:80em) {
    .partisan { font-size: 76%; }
    .partisan__title::before { font-size: 0.25em; }
}
@media screen and (max-width:60em) {
    .game-holder .message--instructions { bottom: 5.5em; }
    .partisan { width: 100%; height: auto; padding: 1.75em 0.5em 0.5em; text-align: center; background: rgba(190, 215, 48, 0.5); }
    .partisan__bg { display: none; }
    .partisan__img { display: none; }
    .partisan__title { margin: 0; }
    .partisan__title::before { width: 100%; padding: 0 0 0.25em; }
}

猜你喜欢

转载自blog.csdn.net/aqin1012/article/details/80535281