《webgl入门指南》学习笔记一three.js与sim.js

时间:2021-09-03 04:08:14

最近又一次很久木有更新博客了,但是不是停了,而是一次性更新了太多,想清楚了自己水平有限,不想误导大家,所以就把这里单纯的当做自己的学习笔记。

上次让立方体转起来了,这次我们就直接介绍一个框架《webgl入门指南》的sim.js 这个东西是什么玩意呢,就是一个模拟器(官方说法),实际上就是简化了three.js里面的各种初始化,比如设置渲染器,循环重绘,处理DOM事件,基本的层级(层次模型)操作,作者没有对这个进行详解,只是说了,一句代码很易读,的确很易读,唯一的缺点就是,真有人不懂,比如我,现在看了《webgl编程指南》还好点,以后也会把《webgl编程指南的读书笔记贴出来,和大家交流下,这次贴的只是部分注释,关于dom操作的没有写,因为大家应该都比我懂,《webgl入门指南》学习笔记一three.js与sim.js


// Sim.js - A Simple Simulator for WebGL (based on Three.js)

Sim = {};//空对象,用来构建后续的框架

// Sim.Publisher - base class for event publishers
// 以下为一个订阅者模式的基类,也称为观察者模式,是几乎sim.js对象的基类
Sim.Publisher = function() {//Publisher的构造函数
    this.messageTypes = {};//构造函数的属性
}

Sim.Publisher.prototype.subscribe = function(message, subscriber, callback) {//Publisher的原型方法
    var subscribers = this.messageTypes[message];
    if (subscribers)
    {
        if (this.findSubscriber(subscribers, subscriber) != -1)//若是在数组里面找到了,说明之前注册了,直接退出
        {
            return;
        }
    }
    else
    {
        subscribers = [];
        this.messageTypes[message] = subscribers;
    }

    subscribers.push({ subscriber : subscriber, callback : callback });//这句才是这个函数的关键,将订阅的函数
                                                                       //以及回调函数压入subscribers这个栈
}

Sim.Publisher.prototype.unsubscribe =  function(message, subscriber, callback) {
    if (subscriber)
    {
        var subscribers = this.messageTypes[message];

        if (subscribers)
        {
            var i = this.findSubscriber(subscribers, subscriber, callback);//为何这里是三个参数 
                                                                           //下面定义的明明是两个参数
                                                                           //js对传入的参数无所谓?
                                                                           //解决:的确无所谓,js都传入的参数个数无所谓
                                                                           //对于多了的参数自动忽略,但不能使未定义的参数
                                                                           //对于少传了参数的函数也能运行,只是这个参数默认为未定义
            if (i != -1)
            {
                this.messageTypes[message].splice(i, 1);//从数组中删除该
            }
        }
    }
    else
    {
        delete this.messageTypes[message];
    }
}

Sim.Publisher.prototype.publish = function(message) {
    var subscribers = this.messageTypes[message];

    if (subscribers)
    {
        for (var i = 0; i < subscribers.length; i++)
        {
            var args = []; 
            for (var j = 0; j < arguments.length - 1; j++)//这里的arguments到底是从哪里来的argument属于谁?
            	                                          //为什么不直接传入到下面的apply中?作用域问题?
            {
                args.push(arguments[j + 1]);//这里为什么要+1??
            }
            //
            subscribers[i].callback.apply(subscribers[i].subscriber, args);//什么意思?
             //subcribers里面的回调函数被执行同时传入参数  ,apply是调用函数 第一个参数为callback的作用域
             //第二个是传入的参数数组(js的参数列表本身就是一个“类似数组”)
        }
    }
}

Sim.Publisher.prototype.findSubscriber = function (subscribers, subscriber) {//找到指定索引的位置
    for (var i = 0; i < subscribers.length; i++)
    {
        if (subscribers[i] == subscriber)
        {
            return i;
        }
    }
    
    return -1;
}








// Sim.App - application class (singleton)这里是个单体模式???????不懂
//这里不是标准的单体设计模式 
Sim.App = function()
{
	Sim.Publisher.call(this);
	
	this.renderer = null;   //入口在Sim.App.prototype.init
	this.scene = null;      //入口在Sim.App.prototype.init
	this.camera = null;     //入口在Sim.App.prototype.init
	this.objects = [];      //入口在Sim.App.prototype.addObject
}

Sim.App.prototype = new Sim.Publisher;//这里和前面的call一起,采用的是组合继承模式,(还有一种是采用three.js
                                      //的方法,Object.create();)但是这里的new会出现两个问题,
                                      //1.call已经继承了实例对象的属性,但是这里Sim.App.prototype不但继承了原型对象对象
                                      //还再次继承了实例对象
                                      //2.这里的Sim.App的construtor直接指向了Sim.Publisher 会造成原型链紊乱
                                      //解决方案:使用寄生组合继承. 详见javascript高级程序设计E3 6.3.6 以及阮一峰博客http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html



Sim.App.prototype.init = function(param)//初始化,this会被绑定
{
	param = param || {};	
	var container = param.container;
	var canvas = param.canvas;
	
    // Create the Three.js renderer, add it to our div
    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 = this;

    // Put in a camera at a good default location
    camera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 1, 10000 );
    camera.position.set( 0, 0, 3.3333 );

    scene.add(camera);
    
    // Create a root object to contain all other scene objects
    var root = new THREE.Object3D();
    scene.add(root);
    
    // Create a projector to handle picking
    var projector = new THREE.Projector();
    



    // Save away a few things那么问题来了,这里的this是指的哪里的this????作用域是整个Sim.App
    //还是Sim.App下面的 ???
    //问题解决:首先是寻找自己作用域(原型函数)下面的,然后是自己所在构造函数下面的
    //然后再是父类,依次寻找。
    //对于其他原型函数里面的,只有绑定到了实例对象 里面以后才行也就是说那个原型函数必须调用后 
    //里面的this.XX 属性才能被其他prototype 调用
/*
如下图所示 ** 所在位置 若出现this.**  其依次搜寻位置优先级依次为为12345 所用继承方式为 为寄生组合继承

		   |super|
		    / \
		   / | \
	|5构造|  |  |3原型|
             |
           |sub|
          /  |  \
         /   |   \
  |4构造|    |    |1 原型a 属性**|
             |
           |2原型b|
*/
//由于原型函数下的init()会被调用用来初始化,所以以下属性在APP全局可被引用,无论,构造对象下,还是原型对象
//那么问题来了????   既然绝对会用为何不直接声明在sim.APP下面??
    this.container = container;
    this.renderer = renderer;
    this.scene = scene;
    this.camera = camera;
    this.projector = projector;
    this.root = root;               //可被渲染的object3D存在这里
    
    // Set up event handlers
    this.initMouse();
    this.initKeyboard();
    this.addDomHandlers();
}





//Core run loop循环
Sim.App.prototype.run = function()//this会被绑定
{
	this.update();                    //Sim.App下的原型对象,可以调用其他原型对象? 调用自己所在的构造函数下的原型函数,??????
	                                  //问题解决:这个run()会在后期调用,自己会被绑定到this上面,所以可以 

	this.renderer.render( this.scene, this.camera );//这里的两个this和上面一个道理
	var that = this;
	requestAnimationFrame(function() { that.run(); });	
}

// Update method - called once per tick
Sim.App.prototype.update = function()//这个在App原型对象里面的run()里面调用  ??问题:同一个对象下面的原型函数可以互相调用??
                                     //问题解决:同一个对象下面的原型函数的确可以互相调用,就像这里的run()可以调用update()
{
	var i, len;
	len = this.objects.length;
	for (i = 0; i < len; i++)
	{
		this.objects[i].update();//这里的update什么意思?从哪里来的?
		                         //问题解决:这里的update是object自带的;object来自继承Sim.Object
	}
}


// Add/remove objects
//以下是this.object的入口

Sim.App.prototype.addObject = function(obj)//因为前面的scene camera renderer全部在原型对象的init里面初始化了,
                                           //所以这里主要是mesh 
{
	this.objects.push(obj);

	// If this is a renderable object, add it to the root scene
	//若从obj里面检查到object3D说明其可被渲染
	if (obj.object3D)
	{
		this.root.add(obj.object3D);
	}
}

Sim.App.prototype.removeObject = function(obj)
{
	var index = this.objects.indexOf(obj);
	if (index != -1)
	{
		this.objects.splice(index, 1);//删除object[]中的元素
		// If this is a renderable object, remove it from the root scene
		if (obj.object3D)
		{
			this.root.remove(obj.object3D);//这里的this属于原型调用原型
		}
	}
}

// Event handling
Sim.App.prototype.initMouse = function()
{
	var dom = this.renderer.domElement;
	
	var that = this;
	//添加鼠标移动的事件
	dom.addEventListener( 'mousemove', 
			function(e) { that.onDocumentMouseMove(e); }, false );

    //添加鼠标按下的事件
	dom.addEventListener( 'mousedown', 
			function(e) { that.onDocumentMouseDown(e); }, false );
	//添加鼠标弹起的事件
	dom.addEventListener( 'mouseup', 
			function(e) { that.onDocumentMouseUp(e); }, false );

	//中键滑轮??
	$(dom).mousewheel(
	        function(e, delta) {
	            that.onDocumentMouseScroll(e, delta);
	        }
	    );
	
	this.overObject = null;
	this.clickedObject = null;
}

Sim.App.prototype.initKeyboard = function()
{
	var dom = this.renderer.domElement;
	
	var that = this;
	dom.addEventListener( 'keydown', 
			function(e) { that.onKeyDown(e); }, false );
	dom.addEventListener( 'keyup', 
			function(e) { that.onKeyUp(e); }, false );
	dom.addEventListener( 'keypress', 
			function(e) { that.onKeyPress(e); }, false );

	// so it can take focus
	dom.setAttribute("tabindex", 1);
    dom.style.outline='none';
}

Sim.App.prototype.addDomHandlers = function()
{
	var that = this;
	window.addEventListener( 'resize', function(event) { that.onWindowResize(event); }, false );
}

Sim.App.prototype.onDocumentMouseMove = function(event)
{
    event.preventDefault();
    
    if (this.clickedObject && this.clickedObject.handleMouseMove)
    {
	    var hitpoint = null, hitnormal = null;
	    var intersected = this.objectFromMouse(event.pageX, event.pageY);
	    if (intersected.object == this.clickedObject)
	    {
	    	hitpoint = intersected.point;
	    	hitnormal = intersected.normal;
	    }
		this.clickedObject.handleMouseMove(event.pageX, event.pageY, hitpoint, hitnormal);
    }
    else
    {
	    var handled = false;
	    
	    var oldObj = this.overObject;
	    var intersected = this.objectFromMouse(event.pageX, event.pageY);
	    this.overObject = intersected.object;
	
	    if (this.overObject != oldObj)
	    {
	        if (oldObj)
	        {
        		this.container.style.cursor = 'auto';
        		
        		if (oldObj.handleMouseOut)
        		{
        			oldObj.handleMouseOut(event.pageX, event.pageY);
        		}
	        }
	
	        if (this.overObject)
	        {
	        	if (this.overObject.overCursor)
	        	{
	        		this.container.style.cursor = this.overObject.overCursor;
	        	}
	        	
	        	if (this.overObject.handleMouseOver)
	        	{
	        		this.overObject.handleMouseOver(event.pageX, event.pageY);
	        	}
	        }
	        
	        handled = true;
	    }
	
	    if (!handled && this.handleMouseMove)
	    {
	    	this.handleMouseMove(event.pageX, event.pageY);
	    }
    }
}

Sim.App.prototype.onDocumentMouseDown = function(event)
{
    event.preventDefault();//阻止元素默认动作,好用来执行以下代码
        
    var handled = false;

    var intersected = this.objectFromMouse(event.pageX, event.pageY);
    if (intersected.object)
    {
    	if (intersected.object.handleMouseDown)
    	{
    		intersected.object.handleMouseDown(event.pageX, event.pageY, intersected.point, intersected.normal);
    		this.clickedObject = intersected.object;
    		handled = true;
    	}
    }
    
    if (!handled && this.handleMouseDown)
    {
    	this.handleMouseDown(event.pageX, event.pageY);
    }
}

Sim.App.prototype.onDocumentMouseUp = function(event)
{
    event.preventDefault();
    
    var handled = false;
    
    var intersected = this.objectFromMouse(event.pageX, event.pageY);
    if (intersected.object)
    {
    	if (intersected.object.handleMouseUp)
    	{
    		intersected.object.handleMouseUp(event.pageX, event.pageY, intersected.point, intersected.normal);
    		handled = true;
    	}
    }
    
    if (!handled && this.handleMouseUp)
    {
    	this.handleMouseUp(event.pageX, event.pageY);
    }
    
    this.clickedObject = null;
}

Sim.App.prototype.onDocumentMouseScroll = function(event, delta)
{
    event.preventDefault();

    if (this.handleMouseScroll)
    {
    	this.handleMouseScroll(delta);
    }
}

Sim.App.prototype.objectFromMouse = function(pagex, pagey)
{
	// Translate page coords to element coords
	var offset = $(this.renderer.domElement).offset();	
	var eltx = pagex - offset.left;
	var elty = pagey - offset.top;
	
	// Translate client coords into viewport x,y
    var vpx = ( eltx / this.container.offsetWidth ) * 2 - 1;
    var vpy = - ( elty / this.container.offsetHeight ) * 2 + 1;
    
    var vector = new THREE.Vector3( vpx, vpy, 0.5 );

    this.projector.unprojectVector( vector, this.camera );
	
    var ray = new THREE.Ray( this.camera.position, vector.subSelf( this.camera.position ).normalize() );

    var intersects = ray.intersectScene( this.scene );
	
    if ( intersects.length > 0 ) {    	
    	
    	var i = 0;
    	while(!intersects[i].object.visible)
    	{
    		i++;
    	}
    	
    	var intersected = intersects[i];
		var mat = new THREE.Matrix4().getInverse(intersected.object.matrixWorld);
    	var point = mat.multiplyVector3(intersected.point);
    	
		return (this.findObjectFromIntersected(intersected.object, intersected.point, intersected.face.normal));        	    	                             
    }
    else
    {
    	return { object : null, point : null, normal : null };
    }
}

Sim.App.prototype.findObjectFromIntersected = function(object, point, normal)
{
	if (object.data)
	{
		return { object: object.data, point: point, normal: normal };
	}
	else if (object.parent)
	{
		return this.findObjectFromIntersected(object.parent, point, normal);
	}
	else
	{
		return { object : null, point : null, normal : null };
	}
}


Sim.App.prototype.onKeyDown = function(event)
{
	// N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
	event.preventDefault();

    if (this.handleKeyDown)
    {
    	this.handleKeyDown(event.keyCode, event.charCode);
    }
}

Sim.App.prototype.onKeyUp = function(event)
{
	// N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
	event.preventDefault();

	if (this.handleKeyUp)
	{
		this.handleKeyUp(event.keyCode, event.charCode);
	}
}
	        
Sim.App.prototype.onKeyPress = function(event)
{
	// N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
	event.preventDefault();

	if (this.handleKeyPress)
	{
		this.handleKeyPress(event.keyCode, event.charCode);
	}
}

Sim.App.prototype.onWindowResize = function(event) {

	this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight);

	this.camera.aspect = this.container.offsetWidth / this.container.offsetHeight;
	this.camera.updateProjectionMatrix();

}

Sim.App.prototype.focus = function()
{
	if (this.renderer && this.renderer.domElement)
	{
		this.renderer.domElement.focus();
	}
}








// Sim.Object - base class for all objects in our simulation
Sim.Object = function()
{
	Sim.Publisher.call(this);
	
	this.object3D = null;      //入口在Sim.Object.prototype.setObject3D
	this.children = [];        //入口在Sim.Object.prototype.addChild
}

Sim.Object.prototype = new Sim.Publisher;

Sim.Object.prototype.init = function()
{
}

Sim.Object.prototype.update = function()							//那么现在大问题来了有两个Sim.Object.prototype.update
{
	this.updateChildren();
}

// setPosition - move the object to a new position
Sim.Object.prototype.setPosition = function(x, y, z)
{
	if (this.object3D)
	{
		this.object3D.position.set(x, y, z);
	}
}

//setScale - scale the object
Sim.Object.prototype.setScale = function(x, y, z)
{
	if (this.object3D)
	{
		this.object3D.scale.set(x, y, z);
	}
}

//setScale - scale the object
Sim.Object.prototype.setVisible = function(visible)
{
	function setVisible(obj, visible)
	{
		obj.visible = visible;
		var i, len = obj.children.length;
		for (i = 0; i < len; i++)
		{
			setVisible(obj.children[i], visible);
		}
	}
	
	if (this.object3D)
	{
		setVisible(this.object3D, visible);
	}
}

// updateChildren - update all child objects
Sim.Object.prototype.update = function()                             //那么现在大问题来了有两个Sim.Object.prototype.update
{
	var i, len;
	len = this.children.length;
	for (i = 0; i < len; i++)
	{
		this.children[i].update();
	}
}

Sim.Object.prototype.setObject3D = function(object3D)
{
	object3D.data = this;      //原型对象中的this,到底指的是那个this?到底是Sim.Object还是Sim.Object.prototype.setObject3D
	                           //解决:经试验是Sim.Object的this

	this.object3D = object3D;
}

//Add/remove children
Sim.Object.prototype.addChild = function(child)
{
	this.children.push(child);
	
	// If this is a renderable object, add its object3D as a child of mine
	if (child.object3D)
	{
		this.object3D.add(child.object3D);
	}
}

Sim.Object.prototype.removeChild = function(child)
{
	var index = this.children.indexOf(child);
	if (index != -1)
	{
		this.children.splice(index, 1);
		// If this is a renderable object, remove its object3D as a child of mine
		if (child.object3D)
		{
			this.object3D.remove(child.object3D);
		}
	}
}

// Some utility methods
Sim.Object.prototype.getScene = function()
{
	var scene = null;
	if (this.object3D)
	{
		var obj = this.object3D;
		while (obj.parent)
		{
			obj = obj.parent;//若obj一直存在parent则,一直把obj.parent赋值给obj,直到obj不存在parent,
			                 //说明找到了最上面的那个obj
			                 //那么问题来了:所有的object的父节点就是scene???
		}
		
		scene = obj;
	}
	
	return scene;
}

Sim.Object.prototype.getApp = function()
{
	var scene = this.getScene();
	return scene ? scene.data : null;
}

// Some constants

/* key codes
37: left
38: up
39: right
40: down
*/

/**
 * [KeyCodes description]以下为sim.js的键盘keycode编号,直接定义为全局变量方便使用,也便于理解
 * @type {Object}
 */
Sim.KeyCodes = {};
Sim.KeyCodes.KEY_LEFT  = 37;//左键
Sim.KeyCodes.KEY_UP  = 38;//上键
Sim.KeyCodes.KEY_RIGHT  = 39;//右键
Sim.KeyCodes.KEY_DOWN  = 40;//下键