今天我们继续来学习osg.js框架。上一篇我们介绍了DrawActor对象绘制操作类和Drawable可绘制对象类,我们大致知道了osg对Drawable可绘制对象的绘制流程管理。今天我们要继续介绍StateBin状态树节点类。我们来看一下StateBin,他管理的是StateSet状态,他将每个模型节点的StateSet状态信息(shaderLink,材质,depth等)包装成树节点,从而能够将状态节点递归组装成一棵状态树。我们来看看StateBin的构造函数。
1 /* 2 状态树结点 3 保存一个StateSet,每个StateSet都有一个唯一ID 4 */ 5 //let MemoryPool = require('../util/MemoryPool'); 6 7 let StateBin = function () { 8 this._stateset = undefined; 9 this._parent = undefined;//StateBin 10 this._children = {};//属性名为StateSet的ID,值为StateBin 11 //this._children._keys = [];//StateSet的ID 12 this._depth = 0;//树的深度值 13 };
首先我们可以看到StateBin的成员,this._stateset这就是模型节点的状态信息(shaderLink,材质,depth),this._parent该树节点的父节点,this._children该树节点的子节点,this._depth树的深度。这是一个典型的树节点类,熟悉数据结构的同学都明白如何递归构造一棵树,鲫鱼就不再啰嗦了。我们接下来看看StateBin的成员函数有哪些。
1 StateBin.prototype = { 2 getStateSet: function () { 3 return this._stateset; 4 }, 5 setStateSet: function (s) { 6 this._stateset = s; 7 }, 8 getParent: function () { 9 return this._parent; 10 }, 11 reset: function () {//重置数据,一般都是根节点调用 12 this._stateset = undefined; 13 this._parent = undefined; 14 15 //之所以遍历是为了StateGraph不被析构以内存复用,而不是每次都重新创建 16 //然后是StateGraph的数据必须被清空,重新使用时不会出错 17 // let keys = this._children.keys(); 18 // let l = keys.length; 19 // for (let i = 0; i < l; i++) { 20 // let key = keys[i]; 21 // let child = this._children[key]; 22 // child.reset(); 23 // //内存池 24 // //MemoryPool.StateBin.put(child); 25 // } 26 this._children = {}; 27 //this._children._keys.length = 0; 28 //this._children._keys = []; 29 this._depth = 0; 30 }, 31 addStateBinChild: function (bin) { 32 bin._parent = this; 33 bin._depth = this._depth + 1; 34 let id = bin._stateset.getID(); 35 this._children[id] = bin; 36 }, 37 addStateSetChild: function (stateset) {//添加子节点,以stateset的id为key,返回新创建或者已经存在的StateBin 38 let id = stateset.getID(); 39 let child = this._children[id]; 40 if (child) { 41 return child; 42 } else { 43 let sg = new StateBin(); 44 //let sg = MemoryPool.StateBin.get(); 45 sg._parent = this; 46 sg._depth = this._depth + 1; 47 sg._stateset = stateset; 48 this._children[id] = sg; 49 //children._keys.push(id); 50 return sg; 51 } 52 }, 53 removeStateBinChild: function (bin) { 54 let id = bin._stateset.getID(); 55 let cbin = this._children[id]; 56 if (cbin) { 57 cbin.parent = undefined; 58 delete this._children[id]; 59 } 60 }, 61 removeStateSetChild: function (stateset) { 62 let id = stateset.getID(); 63 let cbin = this._children[id]; 64 if (cbin) { 65 cbin.parent = undefined; 66 delete this._children[id]; 67 } 68 }, 69 removeChildren: function () { 70 this._children = {}; 71 }, 72 };
我们一个一个来看,getStateSet获取当前树节点的渲染状态信息this._stateset;setStateSet设置当前树节点的渲染状态信息即修改this._stateset;getParent获取当前树节点的父节点;reset初始化节点数据,将节点属性清空析构;addStateBinChild向当前树节点中加入一个子节点;addStateSetChild如果当前树节点存在id是stateset的id的子节点,就返回该子节点,如果不存在就创建一个stateset状态的子节点并返回;removeStateBinChild删除当前节点的确定id的某个子节点;removeStateSetChild删除当前节点某个状态是stateset的子节点;removeChildren删除该树节点的所有子节点。
我们可以清楚的看到,成员函数基本都是对树结构的操作,最后还有一个方法是对父状态的遍历继承,我们来看一下。
1 //父状态的匹配 2 StateBin.moveStateBin = function (glstate, preStateBin, curStateBin) { 3 if (curStateBin === preStateBin) {//两个相同什么都不做 4 return; 5 } 6 7 if (curStateBin === undefined) { 8 //curStateBin已经到顶,弹出preStateBin的所有状态 9 do { 10 if (preStateBin._stateset !== undefined) { 11 glstate.popStateSet(); 12 } 13 preStateBin = preStateBin._parent; 14 } while (preStateBin); 15 return; 16 } 17 18 if (preStateBin === undefined) { 19 //preStateBin已经到顶,压入curStateBin的所有状态 20 //从子节点往根节点遍历获取所有的状态,但是推给glstate必须从根节点往子节点遍历 21 //所以这里先塞到一个stack里面,然后再遍历stack推给glstate 22 let stack = []; 23 do { 24 if (curStateBin._stateset !== undefined) { 25 stack.push(curStateBin._stateset); 26 } 27 curStateBin = curStateBin._parent; 28 } while (curStateBin); 29 30 let size = stack.length - 1; 31 for (let i = size; i >= 0; --i) { 32 glstate.pushStateSet(stack[i]); 33 } 34 return; 35 } else if (preStateBin._parent === curStateBin._parent) { 36 // first handle the typical case which is two glstate groups 37 // are neighbours. 38 39 // glstate has changed so need to pop old glstate. 40 if (preStateBin._stateset !== undefined) { 41 glstate.popStateSet(); 42 } 43 // and push new glstate. 44 if (curStateBin._stateset !== undefined) { 45 glstate.pushStateSet(curStateBin._stateset); 46 } 47 return; 48 } 49 50 //先弹出状态,保证preStateBin和curStateBin达到树节点平级 51 //无法确定两个树节点谁的深度值更多,两个都做一次循环 52 while (preStateBin._depth > curStateBin._depth) { 53 if (preStateBin._stateset !== undefined) { 54 glstate.popStateSet(); 55 } 56 preStateBin = preStateBin._parent; 57 } 58 59 // use return path to trace back steps to curStateBin. 60 let stack = []; 61 // need to pop back up to the same depth as the curr glstate group. 62 while (curStateBin._depth > preStateBin._depth) { 63 if (curStateBin._stateset !== undefined) { 64 stack.push(curStateBin._stateset); 65 } 66 curStateBin = curStateBin._parent; 67 } 68 69 // now pop back up both parent paths until they agree. 70 // should be this to conform with above case where two StateBin 71 // nodes have the same parent 72 //继续遍历直到两个树节点相同 73 while (preStateBin !== curStateBin) { 74 if (preStateBin._stateset !== undefined) {//pre的从GLState中出栈 75 glstate.popStateSet(); 76 } 77 preStateBin = preStateBin._parent; 78 79 if (curStateBin._stateset !== undefined) {//当前的入栈,临时保存 80 stack.push(curStateBin._stateset); 81 } 82 curStateBin = curStateBin._parent; 83 } 84 85 //遍历结束后,从临时栈中推入GLState里 86 for (let i = stack.length - 1, l = 0; i >= l; --i) { 87 glstate.pushStateSet(stack[i]); 88 } 89 };
这段代码我们仔细来看一下。
第一件事比较当前节点状态和前一个节点状态,相同则直接返回。
1 if (curStateBin === preStateBin) {//两个相同什么都不做 2 return; 3 }
接下来如果前后节点状态不同,就继续下面的事情,我们来看下面接下来做了什么事。接下来是判断当前遍历到的状态节点是否已经是树的叶子节点,如果是叶子节点就向树根部遍历,依次弹出上一级父节点直到遍历到整棵树的根节点。弹出是靠glstate这个参数来操作实现的注意一下。遍历到根节点并弹出状态后就直接返回了。
1 if (curStateBin === undefined) { 2 //curStateBin已经到顶,弹出preStateBin的所有状态 3 do { 4 if (preStateBin._stateset !== undefined) { 5 glstate.popStateSet(); 6 } 7 preStateBin = preStateBin._parent; 8 } while (preStateBin); 9 return; 10 }
我们再看看接下来还做了什么操作,这个看注释就能理解他的操作。
1 if (preStateBin === undefined) { 2 //preStateBin已经到顶,压入curStateBin的所有状态 3 //从子节点往根节点遍历获取所有的状态,但是推给glstate必须从根节点往子节点遍历 4 //所以这里先塞到一个stack里面,然后再遍历stack推给glstate 5 let stack = []; 6 do { 7 if (curStateBin._stateset !== undefined) { 8 stack.push(curStateBin._stateset); 9 } 10 curStateBin = curStateBin._parent; 11 } while (curStateBin); 12 13 let size = stack.length - 1; 14 for (let i = size; i >= 0; --i) { 15 glstate.pushStateSet(stack[i]); 16 } 17 return; 18 } else if (preStateBin._parent === curStateBin._parent) { 19 // first handle the typical case which is two glstate groups 20 // are neighbours. 21 22 // glstate has changed so need to pop old glstate. 23 if (preStateBin._stateset !== undefined) { 24 glstate.popStateSet(); 25 } 26 // and push new glstate. 27 if (curStateBin._stateset !== undefined) { 28 glstate.pushStateSet(curStateBin._stateset); 29 } 30 return; 31 }
随后我们看看最后的操作。这波操作就是为了比较currStateBin和preStateBin这两个树节点的深度和对其向树根部的操作。
1 //先弹出状态,保证preStateBin和curStateBin达到树节点平级 2 //无法确定两个树节点谁的深度值更多,两个都做一次循环 3 while (preStateBin._depth > curStateBin._depth) { 4 if (preStateBin._stateset !== undefined) { 5 glstate.popStateSet(); 6 } 7 preStateBin = preStateBin._parent; 8 } 9 10 // use return path to trace back steps to curStateBin. 11 let stack = []; 12 // need to pop back up to the same depth as the curr glstate group. 13 while (curStateBin._depth > preStateBin._depth) { 14 if (curStateBin._stateset !== undefined) { 15 stack.push(curStateBin._stateset); 16 } 17 curStateBin = curStateBin._parent; 18 } 19 20 // now pop back up both parent paths until they agree. 21 // should be this to conform with above case where two StateBin 22 // nodes have the same parent 23 //继续遍历直到两个树节点相同 24 while (preStateBin !== curStateBin) { 25 if (preStateBin._stateset !== undefined) {//pre的从GLState中出栈 26 glstate.popStateSet(); 27 } 28 preStateBin = preStateBin._parent; 29 30 if (curStateBin._stateset !== undefined) {//当前的入栈,临时保存 31 stack.push(curStateBin._stateset); 32 } 33 curStateBin = curStateBin._parent; 34 } 35 36 //遍历结束后,从临时栈中推入GLState里 37 for (let i = stack.length - 1, l = 0; i >= l; --i) { 38 glstate.pushStateSet(stack[i]); 39 }
StateBin渲染状态树是对osg的StateSet单个模型渲染状态的管理数据结构,几乎在整个DrawActor的过程中都要大量应用,他的重要性不言而喻,鲫鱼也是一知半解的在学习这个数据结构,希望大家多提出宝贵的见解,多多斧正,谢谢同学们的支持关注。今天就到这里,下周再见。本文系原创,引用请注明出处:https://www.cnblogs.com/ccentry/p/10224312.html