今天我们继续来学习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                       

01-05 20:52