Posted by: billconan | October 9, 2006

Pillow简介


一直缺少一篇关于Pillow的介绍,现在补上。Pillow是一个我编的轻量级多边形3D建模程序,目前的目标是在模仿silo(www.nevercenter.com)的功能。这个程序还处于初级阶段,实现了一些基本的多边形建模操作,比如对点、边、面、体的挪动旋转以及缩放操作、删除、焊接、切割等等,另外还有catmull-clark细分,出于速度的考虑,目前人为地限定在最大5次细分,实际上细分的次数可以不限。因为这个程序还在起步的阶段,还存在很多需要解决的问题,但是不管怎么说是个不错的开始。据我所知很多小规模的建模工具都要开发很多年,而我这个才2个多月,何况我这个项目只有我一个人在搞。急不得。

下面介绍一下历史:
大概是在2004年10月的时候我开始有自己编一个建模工具的想法。当时是要学DirectX,我觉得学编程必须要动手才行,所以想到写个像silo的程序。之所以要模仿silo是因为我当时觉得silo是一个面向使用的程序,它能够合理的把操作分配到左右手,感觉工作的效率提高了。另外一个原因就是silo是所有3D建模工具里规模比较小的。有了这个想法之后很长时间都没有什么进展,光磨了嘴皮子。不过在这段时间里我思考了很多东西。直到今年7月分毕业,我才重新开始写这个程序。我给这个程序起过好多的名字,最开始叫cedar,然后叫polygon studio,再然后叫clayshop,直到现在的pillow。这些都可以在我旧的博客上看到。

下面介绍一下程序:
因为最开始是为了练习DirectX,所以最早是想用c#和managed directx来写。当时还用过一个truevision3d的引擎。原因很明显,引擎里屏幕拾取等操作已经都给你做好了。但是因为引擎的数据结构特别大,效率很低,所以后来我就直接用directx来编了。而我现在的这个程序则是重新用c++和opengl写的。最开始有人问我为什么不选择opengl,我的回答是每一个图形库都很有学问,一个尚且学不好,怎么能顾得上学另一个。不过自从接触过opengl之后,我觉得opengl没有directx封装的利害,感觉用起来很直接。我真的很喜欢opengl。用c++的目的首先是因为要用opengl,当然tao framework我也试过,不爽。第二个原因是因为效率,交互性强的程序太需要效率了。第三个原因是因为我一直对c++不太开窍,有点不甘心。c++应该是编图形程序的首选语言。至于程序的界面,我选择了wxWidgets。我编程的时候特别会做表面文章,非常在意界面。我认为外观设计不是什么无足轻重的东西,如果苹果公司没有它的工业设计,这样一个不兼容的怪胎早就被淘汰了。不过很遗憾,我找不到满足我要求的c++界面库。我希望能够有一个跨平台,最好开源,可以换肤,控件比较丰富的界面库,但是没有。编这个程序的时候我先后用了cegui,wxWidget,qt4,但是哪个都不满意。最后选择wxWidget也是因为silo也用的是它。

关于程序的结构:
我的程序大体分成了三个层次,界面层负责和使用者交互,并且把操作的结果显示出来。中间的控制层负责管理整个场景,负责将图形的操作转换成对于数据的操作。这部分实际上是整个程序的核心。最里面的是数据层,存储的是场景的图形数据。场景管理器和数据层之间的交互被严格定义为一组命令,并且每一个命令都会被一个叫做历史管理器的东西记录下来,便于随时将操作进行回退。在图中,蓝颜色的箭头表示的是命令的走向,紫颜色表示的是记录命令,红颜色是显示数据的走向。
目前我的程序还不具备undo和redo的功能,但是实际上我在程序中已经实现了。但是和界面结合的时候我没有把这个添上。因为在和界面结合的时候,程序写的有点乱。之前对于如何实现程序的核心部分,我考虑了很长时间,但是和界面结合的东西我考虑的不多。另外也是因为没有找到一个我觉得很得心应手的界面包,而我又尝试了很多的界面方案,几乎每一种界面都有自己的一套编程风格。因为历史记录这个部分是比较容易出问题的部分,尤其是容易内存泄露,我就暂时没有把它包含进来。
当初要实现undo和redo的时候我想到要用命令的方式,每个命令都有个逆命令,操作的时候记录每条命令,需要回退的时候再调用相应的逆命令。但是很多操作,比如说求平均,是没有逆命令的。把求平均之前的情况都记录下来显然也是个比较笨的办法。后来我想到把操作分层,因为不管一个图形操作的过程有多么复杂,归根结底都是要改变数据,改变数据的方式无非就是添加、删除和改动这几种,所以我只要记录这些简单的操作,就可以应对复杂的图形操作了。实际上在我的程序中定义的简单操作要有几十种,并不只是添加、删除和修改这几种。
什么是历史管理器呢,实际上是一个队列和栈的结构,命令被压入一个叫undo的队列头部,如果超过了队列的长度,则从队列的尾部直接抛弃。如果用户想要回退一步操作,就直接弹出在队列头部的一个命令进行回退,同时把这个命令翻译成逆命令放到redo中去,这个redo是一个栈的结构,如果要重做这步操作就弹出redo栈,执行,然后把命令翻译成逆命令再次放到undo队列中。
要如何防止内存泄露呢,我的设计是这样的:把所有在堆中的数据的指针进行统一的管理,这个结构我叫做data pool,实际上是个动态数组。任何其它的操作只能够引用这些数据,引用是通过数组的下标,而不是指针。释放指针由这个data pool统一进行,有两种方式,一种是直接释放,另一种是当需要记录删除操作的时候,把这个指针交给历史管理器,然后当历史管理器也不需要这个指针的时候,再释放掉它的空间。这样就可以防止指针已经丢失但是空间没有释放的情况,因为任何时候被分配的空间都至少在data pool中保留了一个指向它的指针。然后通过data pool释放这个指针,就可以释放掉这个空间。基本上就是这样。

总结:
最开始我把我编这个程序的想法告诉别人,有人就问我,这个程序到底有什么创新的地方,既然天下多边形建模工具已经那么多了。应该说目前还没有,但是以后会有的。现在这个程序虽然很普通,但是这是今后创新的一个平台,未来有可能在这个基础上做一些真正自己的东西。通过编这个程序我学到了不少东西,也多了不少认识。我感觉c++是一个陷阱,确切地说指针是陷阱,用的时候觉得很方便,肆无忌惮。但是当程序大到一定程度,就会突然发现已经是千疮百孔了。处处都有可能造成泄露。但是一个图形的程序又怎么能不用指针呢。我也想过把它都换成smart pointer,但是不知道效率上会打多大的折扣。
我在我的网盘里面放了一个pillow的程序,但是有些bug还没有修复,我已经编不动了,要换换脑子。我很想自己写个界面,sdl和opengl的界面,但是无疑又是个巨大的工程,不知道能不能实现。

一些资源:
之前写的一个pillow的教程:
http://billconan.blogspot.com/2006/10/pillow-01a.html
以及一小段演示动画:
http://billconan.blogspot.com/2006/10/wink.html

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: