HOOPS3D可视化入门教程三大体概念和数据结构.docx
《HOOPS3D可视化入门教程三大体概念和数据结构.docx》由会员分享,可在线阅读,更多相关《HOOPS3D可视化入门教程三大体概念和数据结构.docx(9页珍藏版)》请在冰豆网上搜索。
HOOPS3D可视化入门教程三大体概念和数据结构
HOOPS3D可视化入门教程三:
大体概念和数据结构
上篇文章介绍了HOOPS的要紧模块,这篇文章将要向大伙儿介绍HOOPS的数据结构和穿插其中的一些大体概念。
这些内容要紧包括在3dGS模块内。
一、保留模式
HOOPS采纳保留画图模式(retained mode)。
所谓保留模式是相关于传统的非保留模式而言的。
做过OpenGL编程的人都知道,OpenGL的绘制都是通过挪用一系列画图命令来实现的,一般是在一个叫updateGL的函数里。
除非你自己把相关画图信息保留起来,不然出了那个函数OpenGL就不认帐了,也确实是说你无法从OpenGL里面再获取你曾经绘制的一些图元信息。
而保留模式那么不如此,它把绘制过的命令和图形会保留起来,放在特定的数据结构中,从而使得咱们能够事后随时读取这些数据。
相较于非保留模式,保留模式能够提供更高的效率(因为数据都在内部,下次绘制时不需要再读取),更快的交互(通过特定的基于数据结构的算法,能够加速选取、高亮等等交互操作),还有更方便的编程接口。
固然凡事都有两面性,保留模式也有它的缺点,其中之一确实是它增加了程序的内存消耗(用于存储那个数据结构)。
但咱们以为如此的代价是完全值得的。
二、基于段的数据结构
HOOPS的数据结构简单讲是基于段(segment)的树状结构。
最上层是根段,为“/”。
该数据结构和Linux文件系统有着一曲同工之妙,有Linux利用体会的同窗将会很容易明白得。
Linux的根目录的符号也是“/”,所有文件系统中的文件或文件夹的途径都以该符号开头。
文件夹出名字,段也出名字。
犹如文件夹内能够有文件和子文件夹,segment下能够有sub segment。
如此的层次结构能够专门好地构建咱们想要的图形。
打开一个段的HOOPS函数是HC_Open_Segment,它有一个参数,确实是那个段的名字。
咱们能够传一个空字符串给它,从而创建一个匿名段。
若是已经存在那个名字的段,那么该函数会打开那个段,不然就自动创建一个新段。
打开后,咱们就能够够在该段内做任何咱们想要做的操作。
操作终止,记得用HC_Close_Segment来关闭那个段。
HOOPS采纳和OpenGL一样的上下文机制,那确实是“状态机(State Machine)”。
所谓状态机,形象地讲确实是一旦改变了状态,那么接下去不论程序运行到哪里,该状态将一直保留,直到下次改变状态。
在HOOPS中,打开一个段事实上就意味着进入了一个状态机,直到你关闭那个段,你所有的操作都将在如此一个上下文中进行。
具体来讲确实是,打开一个段,然后你能够跳转到任意的程序位置完成具体的绘制任务,然后关闭段,这一系列操作没有必要在一个函数中完成。
这无疑大大增加了咱们编程的灵活性。
举个例子,咱们想要绘制一所屋子,屋子有房顶、窗户还有门,咱们能够用如下代码:
HC_Open_Segment (“/”);
HC_Open_Segment (“house”);
HC_Open_Segment (“roof”);
HC_Close_Segment ();
HC_Open_Segment (“door”);
HC_Open_Segment (“windows”);
HC_Open_Segment (“window1”);
HC_Close_Segment ();
HC_Open_Segment (“window2”);
HC_Close_Segment ();
HC_Open_Segment (“window3”);
HC_Close_Segment ();
HC_Close_Segment ();
HC_Close_Segment ();
HC_Close_Segment ();
事实上咱们创建了如下的树状结构:
创建了段以后,咱们需要有相应的方式能够找到那个段,这时就会用到段的途径。
和Linux上的文件途径类似,段的途径也分为两种:
相对途径和绝对路径。
咱们打开一个段,进入该段的状态机,若是要打开它下面的子段,就能够够用相对途径。
HOOPS会自动地在该段下面找给出的段名,若是找不到,那么会报错。
绝对途径那么是从根段名“/”开始,慢慢地把段名添加上去,直到咱们想要找的段为止,完整的途径确实是绝对途径。
例如咱们要找第三扇窗户,相关于house的相对途径是:
”windows/window3”,而绝对途径是:
”/house/windows/window3。
咱们能够用“.”和“..”来别离指代当前目录和父目录,这又跟Linux上的途径利用适应是一致的。
这种简称只能用于相对途径中。
为了能够更方便地提供段的途径,HOOPS中还有一套特有的符号,叫做“wildcards”,能够同时指代多个不同的途径,有以下几种:
1. 逗号wildcard。
那个是最简单的一种。
有时候咱们需要同时对多个可列举的段进行统一处置,例如咱们想用同一种颜色来装饰roof和door(尽管这种做法很少见……),咱们就能够够用如此的一个途径来同时指代这两个段:
/house/(door,roof)。
2. 通配符。
能够用“*”来匹配0个或多个字符,“%”来匹配单个字符,那个跟咱们用Windows系统搜索功能是一样的,也和正那么表达式相一致。
3. 递归wildcards。
其实上面两个并非是HOOPS所特有的,在其他也有见到。
可是HOOPS还有一个它特有的符号,那确实是“…”,该符号能够指代一个段名或一串途径上的段名。
例如咱们能够用/house/…/window1来指代第一扇窗户,而不用去管当中到底隔了多少个段。
该种方式超级长适合于咱们不清楚house到window1之间到底存在着如何的父子结构。
尽管方面,可是若是咱们确切地明白window1的完整途径,那就不要这么写了,因为HOOPS是通过自顶向下的方式搜索取得window1,因此需要消耗必然的计算量。
另外,该符号还能够递归地表示一个段的所有子段和子段的子段。
若是咱们要对一个段内的所有子段进行某项修改,那么那个wildcards真是再适合只是了。
三、几何信息(Geometry)
segment像文件夹一样,它本身并无实质的东西,而只是一个容器。
真正绘制出车的形状,还需要具体的几何信息。
因此,段内部除能够存储子段外,还能够存储Geometry。
HOOPS中的Geometry丰硕多样,囊括了点、边、面、壳(shell)、网络(mesh)等等大体上大伙儿能够想到的图元。
这些大体几何通过彼此组合,能够组成加倍复杂的图像信息,这是一个自底向上的组建进程。
例如咱们能够通过下面的方式插入一个点和一条直线:
HC_Open_Segment (“myseg”);
HC_Insert_Marker (0, 1, 1);
HC_Insert_Line (-1, -1, -1, 2, 2, 2);
HC_Close_Segment ();
HC_Insert_Marker需要传入三个浮点参数,也确实是一个点的三维坐标。
HC_Insert_Line需要传入六个参数,为一个线段的起始点和终止点的三维坐标。
咱们能够用下面的代码插入一个多边形的面:
HC_POINT pts[4] =
{HC_POINT(0, 0, 0), HC_POINT(1, 0, 0), HC_POINT(1, 1, 0), HC_POINT(0, 1, 0)};
HC_Open_Segment (“mypolygon”);
HC_Insert_Polygon (4, pts);
HC_Close_Segment ();
HC_Insert_Polygon需要传入两个参数,别离是多边形极点个数和寄存极点三维坐标的数组。
该函数代表了HOOPS中一类参数,确实是对一群点进行操作。
需要注意的是,这种函数在内部会对传入的三维坐标数组进行拷贝,因此若是你传入的坐标数组是动态申请出来的,在挪用完该类函数以后,必须手动地将其释放掉。
除大体的点、线、多边形等,HOOPS还提供了两个相对高级的图元,别离是Shell和Mesh。
在进行大型场景构建时,这两个图元是超级经常使用的,例如咱们用三角网格构建一个人的模型,那么那个三角模型确实是一个shell。
shell有三个层次的图元组成,别离是node(点)、edge(边)和face(面),这三部份彼此连接形成一个整体。
mesh和shell超级类似,一样由点边面三部份组成,唯一的区别是mesh它不能形成一个封锁的类似于人如此的模型,它只能是一张面,而且只能是一张四边形面,例如一张四边形纸。
如此的区别使得在处置特定的模型时,若是mesh能够知足应用需要,那么mesh将会比shell表现得高效得多。
下面举例创建一个立方体,并在它的一个面上接一个金字塔体:
HC_POINT pts[] = {
HC_POINT (0, 0, 1), HC_POINT (1, 0, 1),
HC_POINT (1, 1, 1), HC_POINT (0, 1, 1),
HC_POINT (0, 0, 2), HC_POINT (1, 0, 2),
HC_POINT (1, 1, 2), HC_POINT (0, 1, 2),
HC_POINT (0.5, 0.5, 2.5)
};
int flist[] = {
4, 0, 3, 2, 1,
4, 0, 1, 5, 4,
4, 1, 2, 6, 5,
4, 2, 3, 7, 6,
4, 3, 0, 4, 7,
3, 4, 5, 8,
3, 5, 6, 8,
3, 6, 7, 8,
3, 7, 4, 8
};
HC_Open_Segment ("mymodel");
HC_Insert_Shell (9, pts, 41, flist);
HC_Close_Segment ();
HC_Insert_Shell需要四个参数,别离是shell的极点个数,极点数组,面列表数组的长度,面列表数组指针。
极点个数和数组专门好理解,确实是具体的各个极点的三维坐标。
面列表是如此的格式:
面极点个数n, 第一个极点序号,第二个极点序号,…,第n个极点序号。
例如flist第一行,4表示该面由四个极点组成,也确实是一个四边形。
然后,0,3,2,1表示由pts那个数组中的第0、3、二、1号点组成那个面。
需要注意的是HC_Insert_Shell的第三个参数实质flist那个数组本身的长度,而不是将要构建的shell上面的个数。
例如那个例子中面的个数为9,但flist的长度为41。
成效如以下图所示:
四、段的属性
上文中,咱们在HOOPS中创建了一个屋子,假设咱们此刻已经用几何图元将屋子给绘制出来了,可是光有结构还不行,至少咱们还需要给它上色,或许我们还会通过贴上不同的纹理来表示不同的材料。
HOOPS的段结构中除能够寄存Geometry,还能够寄存属性Attribute。
咱们经常使用的属性包括:
可见性(Visibility),颜色(Color),可选择性(Selectability),点、边、字体的大小,光照(light),渲染属性(rendition)等等。
乃至能够添加咱们自概念的属性(User defined attribution)。
能够说,HOOPS的属性功能是超级全面而壮大的。
和插入几何一样,要修改一个segment的属性,咱们需要进入该segment的状态机,亦即要第一打开那个段。
下面以house模型为例:
HC_Open_Segment (“house”);
HC_Open_Segment (“roof”);
//add roof geometry here...
HC_Set_Color (“geometry=red”);
HC_Close_Segment ();
HC_Open_Segment (“door”);
//add door geometry here...
HC_Set_Color (“geometry=grey”);
HC_Close_Segment ();
HC_Close_Segment ();
如此,咱们将屋顶和门别离设置成了红色和灰色。
又比如适才咱们自创的那个集合模型,这回,咱们要让它再也不空白一片了,咱们给它点颜色看看(J):
HC_Open_Segment ("mymodel");
HC_Set_Color ("faces=grey,edges=green");
HC_Set_Visibility ("edges=on");
HC_Insert_Shell (9, pts, 41, flist);
HC_Close_Segment ();
咱们设置了mymodel那个段的两个属性,颜色和可见性。
在设置颜色中,咱们设置面为灰色,而设置边为绿色;在设置可见性上,咱们设置边为可见。
什么缘故不设置面为可见呢?
因为在HOOPS中,有些是默许可见的,而有些是默许不可兼得;而shell的面是默许可见的,edges那么恰好是默许不可见的。
下面是新的成效图,怎么样,和之前不一样了吧?
记住那个模型,往后的教程中咱们还会多次用到,比如给它贴上漂亮的纹理、光照等等,还有动画。
上面在设置颜色时,咱们用一个字符串命令同时设置了面和边的颜色。
这种格式化的字符串在HOOPS中被大量应用,几乎同意字符串作为参数的HOOPS函数中都有如此的格式化命令。
faces和edges关于HC_Set_Color函数来讲,是能够设置颜色的对象,而等号后面是具体的值,中间用逗号分隔。
若是没有显式地说明设置对象,那么确实是everything,也确实是所有对象。
该格式化字符串有很多相关利用技术,具体能够参看HOOPS的帮忙文档,下面仅举几个例子来讲明格式化字符串的大体用法:
1. “red,faces=green”,设置所有几何图元为红色,只有面为绿色;
2. “markers=edges=black”,点和边为黑色;
3. “!
edges=(r=0.5 g=0.5 b=0.5)”,非边的图元颜色都设置为灰色。
至于设置对象是复数仍是单数是无所谓的,即edges和edge的作用成效完全一样。
五、属性的继承
属性(Attribute)是能够被继承的,就像面向对象的编程语言里面类的继承一样。
关于绝大多数属性来讲,继承的方向是子段从父段中继承属性。
这种特性有时候对咱们来讲能够提供极大的方便。
回忆咱们之前创建的house,它有三扇窗户,一样来讲,一座屋子的窗户颜色都是一样的,若是没有属性的继承,那么咱们可能就需要针对每一个窗户段设置它的颜色属性。
关于咱们这座小屋子来讲,这还能够同意,可是某天你发达了,让你构建一桩拥有成千上万扇窗户的摩天大楼,那恐怕确实是场灾难了。
有了属性的继承,世界仍是美好的。
咱们能够在windows那个段设置颜色,那么所有该段下面的子段都自动继承了该颜色属性,再不用咱们单独去设置了。
但是,问题也随之显现。
整幢大楼里毕竟有些窗户所在的房间住着不寻常的人,而这些窗户咱们希望显示出不一样的颜色,以彰显这些人的显赫身份。
那如何幸免这些窗户继承父段的颜色呢?
咱们能够单独设置这些窗户的颜色,HOOPS在绘制这些窗时,会优先利用单独设置在这些段上的颜色;若是没有单独设置(如同绝大多数窗户),那么HOOPS才会自动地去读取父段的该属性,直到最上层的根节点“/”。
若是根节点也没有设置该属性,HOOPS就会报错。
关于绝大多数的属性来讲,HOOPS正是遵循这种“追根溯源”的方式来确信一个属性的值的。
尽管这种直接覆盖的属性占大多数,可是有些属性不是直接覆盖取得的,例如旋转矩阵。
要计算一个图元最终活着界坐标上的位置,咱们需要从根节点开始,慢慢地累加旋转矩阵,一直到该段,如此计算所得的旋转矩阵才是最后真正的旋转矩阵。
尽管咱们能够操纵一个特定的段的属性,可是有时候咱们仍是想要强制整个段表现为同一种属性,而不管底下各个子段是不是单独设置了该属性。
有些属性就提供了如此的功能,其中之一确实是颜色属性。
当咱们用鼠标选中了某一个segment以后,咱们希望整个段都显示一种高亮色,而不管该段内部子段的单独颜色。
这时,咱们需要用到颜色的属性锁。
能够通过挪用下面的代码来对颜色加锁:
HC_Open_Segment(“myseg”);
HC_Set_Color("red");
HC_Set_Rendering_Options("attribute lock = color");
HC_Close_Segment();
如此,myseg那个段的颜色就被锁定为红色。
若是后续操作中咱们再也不需要对颜色进行锁定,那么能够利用HC_UnSet_Rendering_Option (“attribute lock”)。
六、特殊的段——包括段和样式段
上面介绍的段都是HOOPS中的一般类型的段。
另外,HOOPS还有包括段(included segment)和样式段(style segment)。
这些段的功能事实上都能够用一般段来实现,可是正因为引入了这些特殊类型的段,咱们能够将HOOPS的数据结构设计得更为精致和高效,咱们的程序结构性也更好。
再回忆咱们之前给的house模型。
咱们在屋子上添加了三扇窗户。
一样来讲,一幢屋子上的窗户长得都是差不多的,因此咱们想到是不是能够只设计窗户一次,而三次重复利用呢?
能够的,HOOPS里面利用的确实是包括段(included segment)。
包括段事实上确实是一次概念,多次重复利用,它提高了代码的利用率,也提高了内存利用率。
事实上包括段和C/C++语言中的头文件是很像的,咱们编写一次头文件,然后在需要用到的地址通过#include就能够够将其包括进来,而不需要另外再写。
包括机制除提高效率之外,还能够方便后续的保护,例如当咱们想要更新窗户的样式时,只需要在概念处修改一次,由于三处窗户都是包括该窗户的,因此这三处就自动加载了新的样式。
咱们再也不需要一个个地别离去修改,既提高了效率,又减少了犯错的可能。
包括段一般是针对含有几何信息的段(固然,由于包括段本质上仍是一般的段,因此它能够包括属性),而样式段那么仅包括属性。
有些时候,咱们需要重用的可能仅仅是一套样式,例如颜色、大小、光照等,关于具体的几何图元咱们却爱好不大,那个时候就能够够用到HOOPS的样式段。
下面的代码演示了如何利用Style segment:
HC_Open_Segment (“mystyle”);
HC_Set_Color (“edges=red,faces=(diffuse=(r=0.5 g=0.2 b=0.3))”);
HC_Close_Segment ();
HC_Open_Segment (“myseg1”);
HC_Style_Segment (“mystyle”);
//Insert my geometry...
HC_Close_Segment ();
HC_Open_Segment (“myseg2”);
HC_Style_Segment (“mystyle”);
//Insert my another geometry...
HC_Close_Segment ();
这段创建了一个样式段,两个一般段,这两个一般段插入了不同的几何图元,可是利用了一样的样式段,因此它们显示出来后都是红色的边,紫色的面。
七、键值
键值(一般是HC_KEY类型)是HOOPS中一个超级重要的概念。
HC_KEY本质上是一个32位带符号的整型。
前文中咱们说,能够通过段的名字和途径(相对途径或绝对途径)来索引一个段,于此同时咱们也能够用键值来索引段。
HC_Open_Segment会返回一个long型的整数,确实是打开的那个段的键值。
注意,新版本的HOOPS取消了在API谓词前的K变形,而因此之前这些K变形函数都返回键值了。
19版本之前的HOOPS,HC_Open_Segment返回是void类型的,而要返回段的键值,那么必需显示地挪用HC_KOpen_Segment。
在新版本中这样的函数已经去掉了,HC_Open_Segment直接返回键值。
除段能够有键值,几何图元也能够有键值。
HC_Insert_Line、HC_Insert_Polygon等插入图元的函数都会返回一个键值,该键值唯一的指代新插入的几何图元。
HOOPS中大部份的API函数都有By_Key结尾的变形,这一类的变形函数实现和它们原型函数一样的功能,唯一的区别是它们的入口参数是要操作的段的键值,而不是字符形式的名字了。
既然此刻咱们有两种方式来找到一个段,那么咱们就需要详细地比较一下这两种方式各自的好坏。
1. 存储键值只需要一个32位整数,存储段名那么需要一个字符数组,而且长度不定;
2. 用键值来找到一个段速度要比用字符途径快;
3. 段名比较直观,便于调试的时候肉眼判定正误,键值那么比较抽象,一眼看上去不太容易分辨对错;
4. 段名还有途径支持之前提到的wildcards,因此能够同时指代多个不同的段,可是键值是唯一的,它只能指代一个段或几何图元;
5. 关于几何图元来讲,咱们只能够用键值去找到它们,因为它们是没有字符形式的名字的;
6. 关于匿名段来讲,由于咱们没有赋给它任何段名(应该说是空的段名),因此也就无法用段名来索引它,而只能用键值。
以上只是我目前发觉并整理的不同的地方,如后续有新发觉,那么会继续补充。
一样来讲,系统返回的键值是负数。
咱们能够通过HC_Renumber_Key来修改系统给咱们的键值。
若是咱们调试的时候发觉一个键值为0或正数,那么要么是咱们修改了,要么是程序在哪个地址犯错了。
那个概念虽小,可是在实际操作中却是超级有效的。
另外,为了确保某些HOOPS API操作成功,咱们能够在操作终止后将取得的键值跟INVALID_KEY进行比较。
INVALID_KEY是HOOPS预概念的一个值,它表示若是API执行失败返回的错误键值。