Three.js的轻量级封装框架Sim.js解析(1)

这段时间在看《WebGL入门指南》,其中使用的Three.js的轻量级封装框架,本来使用Three.js框架就有点困难,还要使用Three.js的框架,OMG~~~

发牢骚归发牢骚,不看是不会懂的。初看书本上的Sim.js写出的代码,第一感觉,这都写的是啥啊。的确,要看懂这些,要求对js的面向对象的思想有一定的了解。下面是本人现学现卖总结的有关js面向对象的知识,仅供参考:
http://blog.csdn.net/birdflyto206/article/category/6121645

下面是我阅读Sim.js源码所做的笔记,由于在初步涉猎webgl,js学得也不是很好,难免会有错误的地方,请各位看官不吝指教。

如果直接读Sim.js的源码,肯定云里雾里的,所以我们对照书上第三章的第一个例子,来看Sim.js框架到底是如何运行的。

//-------------earth-basic.js---------------------
//该例子是画一个地球。首先是抽象出一个EarthApp类,该类继承Sim.App类
EarthApp = function()
{
    Sim.App.call(this);//调用父类的构造函数
}
// Subclass Sim.App
EarthApp.prototype = new Sim.App();//继承Sim.App的函数原型

//---------------------------Sim.js-----------------------------------
//现在我们看看Sim.App的定义
// Sim.App - application class (singleton)
Sim.App = function()
{
    //Sim.App也是继承Sim.Publisher类的,该类负责管理框架中的事件订阅/发布。该例子中没有用到事件,这个后面再讲。
    Sim.Publisher.call(this);

    //初始化一个Three.js场景中的基本对象
    this.renderer = null;
    this.scene = null;
    this.camera = null;
    //全局app中要渲染的物体的集合
    this.objects = [];
}
//让Sim.App.prototype指向一个Sim.Publisher对象,也就达到继承Sim.Publisher原型中所有方法和属性的目。这时js中实现继承的一种方式。
Sim.App.prototype = new Sim.Publisher;

//小结:声明一个EarthApp实例,实际主要完成的操作就是声明一些Three.js场景中render、scene、camera等变量。

//-------------earth-basic.js---------------
//自定义EarthApp的初始化方法
EarthApp.prototype.init = function(param)
{
    //调用父类的init方法完成scene、render、camera的初始化
    //注意:这里的this传递进去很重要,这是js中实现继承的关键。
    Sim.App.prototype.init.call(this, param);

    // 创建一个Earth对象,由于我们是画一个地球,根据OOP的思想,将其抽象为一个Earth对象,该对象继承Sim.js框架中的Sim.Object对象
    var earth = new Earth();
    //调用Earth对象的init方法
    earth.init();
    //将earth对象的实例添加上到EarthApp中
    this.addObject(earth);
}

//---------------------------Sim.js-----------------------------------
//Sim.App的init方法
//因为自定义的EarthApp调用父类的init方法传递的是其代表其本身的this,所以,父类init方法中的所有this相关的操作都被映射为EarthApp的操作。说得简单点,现在Sim.App中的this代表的是EarthApp。这样EarthApp就可以不会吹灰之力继承父类init方法中的所有操作,而不必自己再实现一遍,这就是Sim.js框架的目的所在。
//现在我们看看,Sim.App的init方法中干了些什么。
Sim.App.prototype.init = function(param)
{
    //传递的param是一个json对象
    param = param || {};    
    var container = param.container;
    var canvas = param.canvas;

    // 初始化在Sim.App构造函数中定义的一些场景元素
    var renderer = new THREE.WebGLRenderer( { antialias: true, canvas: canvas } );
    renderer.setSize(container.offsetWidth, container.offsetHeight);
    container.appendChild( renderer.domElement );

    // Create a new Three.js scene
    var scene = new THREE.Scene();
    scene.add( new THREE.AmbientLight( 0x505050 ) );
    //为scene动态添加一个属性data,指向EarthApp(子类对象),这个还不知道是干什么的,后面再说。
    scene.data = this;

    // 初始化相机
    camera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 1, 10000 );
    camera.position.set( 0, 0, 3.3333 );

    scene.add(camera);

    // 创建一个Object3D对象,Object3D对象是Sim.Object对象的一个属性,这个类会在后面讲到。该属性一般存储的是一个将要绘制的对象,一般是一个Mesh。例如本例中,Object3D对象就是一个球体和材质组合的Mesh对象。该属性是通过setObject3D函数完成的。
    var root = new THREE.Object3D();
    scene.add(root);

    // 创建一个工具类,用于完成在Three.js鼠标拾取对象。
    var projector = new THREE.Projector();

    // 保存一些变量到全局对象的属性中,为了方便在任何地方能够访问到。
    this.container = container;
    this.renderer = renderer;
    this.scene = scene;
    this.camera = camera;
    this.projector = projector;
    this.root = root;

    // 设置事件相关的句柄。这里先跳过。
    this.initMouse();
    this.initKeyboard();
    this.addDomHandlers();
}

//小结:earth-basic.js中这段代码主要完成的任务就是初始化一些Three.js场景中的基本元素,如scene、camera、render等。

//下面看看自定义的Earth对象
//---------earth-basic.js------------------
// Custom Earth class
Earth = function()
{
    Sim.Object.call(this);//继承Sim.Object
}
Earth.prototype = new Sim.Object();

//这里Earth的init方法没有调用父类的init方法,为什么呢?
//我们看下Sim.js的源码,发现Sim.Object的init方法为空
Earth.prototype.init = function()
{
    //下面是Three.js中的操作
    // Create our Earth with nice texture
    var earthmap = "../images/earth_surface_2048.jpg";
    var geometry = new THREE.SphereGeometry(1, 32, 32);
    var texture = THREE.ImageUtils.loadTexture(earthmap);
    var material = new THREE.MeshBasicMaterial( { map: texture } );
    var mesh = new THREE.Mesh( geometry, material ); 

    // Let's work in the tilt
    mesh.rotation.x = Earth.TILT;

    // 将Mesh对象作为Object3D传递给Sim.js框架
    // 这里的this指的是什么?
    // 对,就是Earth类,而Earth对象我们并没有定义setObject3D方法啊?对,这个方法是继承父类Sim.Object的,我们看看父类中的setObject3D方法,其实就是mesh对象赋值给Sim.Object类的Object3D属性。
    this.setObject3D(mesh);    
}

Earth.prototype.update = function()
{
    // "I feel the Earth move..."
    this.object3D.rotation.y += Earth.ROTATION_Y;//这里的Object3D就是Mesh对象,因为前面已经 this.setObject3D(mesh); 结合Sim.js源码
}

Earth.ROTATION_Y = 0.0025;
Earth.TILT = 0.41;

//--------Sim.js---------
//此案例中Object3D对象传递进来的就是一个包含了纹理的地球Mesh对象
Sim.Object.prototype.setObject3D = function(object3D)
{
    object3D.data = this;
    this.object3D = object3D;
}

//然后在主页面中是这么调用框架的
$(document).ready(
            function() {
                var container = document.getElementById("container");
                var app = new EarthApp();//自定义的EarthApp对象,表示整个应用程序
                app.init({ container: container });
                app.run();
            }
    );
//看看app.run()方法
Sim.App.prototype.run = function()
{
    this.update();//首先调用刷新界面的方法
    this.renderer.render( this.scene, this.camera );//Three.js中的render方法
    var that = this;//保存Sim.App的引用。
    requestAnimationFrame(function() { that.run(); });  
}

Sim.App.prototype.update = function()
{
    var i, len;
    len = this.objects.length;
    //循环当前场景中的所有的物体,让其全部重绘。重绘的方法是子类自己实现的。
    //该类中Earth对象的update方法就是不断的更改其绕y轴旋转的角度,以达到旋转动画的目的。
    for (i = 0; i < len; i++)
    {
        this.objects[i].update();//每个自定义的Object对象必须有update方法,可以在该方法中完成旋转动画等。
    }
}

ok,至此为止,我们分析了第一个例子的运行原理。总结如下:
1.首先抽象出整个应用程序类EarthApp,继承自Sim.App,主要完成Three.js中一些变量的初始化操作,像声明render、scene、camera等,还有一些事件的订阅和发布,本例中没有用到这些 。还有就是初始化当前场景中需要渲染的物体,根据面向对象编程的思想,把这些要渲染的物体都封装在一个对象中,本例中的对象就是Earth类,该类的初始化就是在EarthApp的init方法中完成的。
2.然后抽象出自己要渲染的对象类Earth,这个对象类是一个容器,其中可以包含很多要渲染的物体,当然本例中只包含了一个贴了纹理的地球。该类的init方法的主要是定义要画的物体,本例中画的地球的几何和纹理以及包装成要画的Mesh等操作都是在该方法中完成的。
3.然后是关键的一步,就是循环重绘。Sim.js框架是通过调用Sim.App的run方法开启的,首先调用自身的update方法,当前这步是为了让物体进行简单的动画,比如绕坐标轴进行旋转。真正的循环重绘操作是通过requestAnimationFrame完成的,不断的调用run方法,run方法中调用Three.js中的render(scene,camera)方法,这样整个程序就运行起来了。
4.面向对象思想是门艺术!!!

你可能感兴趣的:(Three.js)