LOFTER for ipad —— 让兴趣,更有趣

点击下载 关闭
sgf-py开发指南(七)变换的实现和编写L-system生成树

sgf-py中的变换组件Transform

        变换是计算机描绘图形的基础。像html5中的Canvas,openGL,等图形库均有变换的功能,一般是translate 移动,rotate 旋转, scale 缩放。配合pushMatrix与popMatrix,使得计算机绘图更加的灵活,方便。

        但遗憾的是pygame.draw模块没有提供这种特性,这也使得绘图极为不方便。

        所以,我决定自己构建,嵌入Painter,使Painter的绘图更加灵活,方便。

        Transform组件可被多继承的方式继承,让继承它的类拥有变换的能力。同时为了避免多继承中的陷阱“菱形继承问题(diamond problem)”,component类型的类不能基础任何类。

        这里只实现2d的变换,3d与2d原理是一样的。

变换组件Transform的功能

        提供以下几个主要的API函数:

        1. translate(*args) -> None

        用于位移,*args可以是两个数,tuple,list与vec2,将下一次绘制的位置位移指定的距离。

        2. rotate(*args) -> None

        用于旋转,*args可以是三个数x, y, a表示绕(x, y)点旋转a度,也可以是一个数a, 默认绕(0, 0)点旋转,也可以是vec2和a,tuple和a, list和a。注意,这里a为弧度制。

        3. scale(*args) -> None

        用于缩放,*args同位移的一样。

        4. push() -> None

        将变换的矩阵压入矩阵栈,即保存之前位移,旋转,缩放变换。类似于openGL中的glPushMatrix()。

        5. pop() -> None

        将栈顶的变换矩阵弹出,移除之前保存的变换。类似于openGL中的glPopMatrix()

        6. resetCurrentMat() -> None

        将当前进行变换的矩阵置为单位矩阵。

        7. resetAll() -> None

        将矩阵栈中的所有矩阵置为单位矩阵

        其它还有popAll(),getMatStack(), getCurrentMat()等借助名称就可以知道功能的函数。

变换与矩阵的关系,变换矩阵的构造

        构造变换矩阵是计算机图形学的基础,它的理论基础来自于线性代数。

        首先是旋转变换:

向量(x1, y1),旋转θ度得到向量(x2, y2),(x1, y1)与x轴夹角为φ。

可知 x1 = rcosφ, y1 = rsinφ, x2 = rcos(θ + φ), y2 = rsin(θ + φ)

这里将x2, y2化简,再将x1,y1带入可等到

x2 = x1 * cosθ - y1 * sinθ

y2 = x1 * sinθ + y1 * cosθ

写成矩阵乘法的形式为:

记为1式

所以可以求得旋转矩阵

而这只是绕原点旋转,如果要绕任意点旋转呢?,先来看看位移,然后再回来讨论

位移变换:

向量从(x1, y1)移动至(x2, y2)

可知 x2 = x1+dx

y2 = y1 + dy

改写成矩阵时,需要一个齐次坐标w,这里w一般是1,即:

这个矩阵就是位移矩阵。

再来看看绕任意点的旋转矩阵怎么求?绕任意点旋转其实都可以引申为绕原点旋转。

具体做法是将任意点移动至原点处,旋转,然后再移动至该任意点处。

这里要重新拿回1式改写成含有齐次坐标的形式:

然后可以求出旋转矩阵绕(x, y)点旋转的矩阵M:

上式中因为原点距离(x, y) 为 dx, dy 就是x, y,所以直接写x,y

乘以M,即:

自此旋转矩阵才算真正构造完毕

缩放变换:

向量(x1,y1)x扩大a倍,y扩大b倍成为(x2,y2)

即 x2 = a * x1

y2 = b * y2

同样构造矩阵乘法为:

将这些变换结合起来,只需要让他们相乘即可。

其实,矩阵的乘法就是各种形变而已,不然它的乘法定义为什么那么奇怪。。。

知道了原理,编写代码就非常容易了。

编写L-system分形树

        工欲善其事,必先利其器,有了强大的Transform组件为工具,就不怕绘图难了。

        L-system(Lindenmayer System)是一位匈牙利裔(好多厉害的人都是匈牙利裔)荷兰Utrecht大学的生物学和植物学家,林登麦伊尔(Aristid Lindenmayer)于1968年提出的有关生长发展中的细胞交互作用的数学模型,尤其被广泛应用于植物生长过程的研究。

        主要是利用规则的迭代产生。

        一般的规则是:

        '+'代表向顺时针旋转一定的角度,'-'代表向逆时针方向旋转一定角度,'['代表存储形变的数据,']'代表删除形变的数据。还有,一般'F'代表画一条直线,或者也可以用其它字母代替。

        规则一般有一个初始值,称为axiom,是迭代的源头,然后是规定的角度,迭代的规则。

        例如 axiom: A    rule: A->AB, B->A

        即第一次结果:A

        第二次:AB

        第三次:ABA

        第四次:ABAAB。。。。。。

        比如 axiom: F    rule:F->F+[+F-F]    

        第一次结果:F

        第二次:F+[+F-F]    

        第三次:F+[+F-F]+[+F+[+F-F] -F+[+F-F]]。。。。。。

        了解了原理,编码就非常简单了,不一定要用sgf-py,同样的,也可以用python的turtle库来实现,原理都是一样的。

以下是用sgf-py实现的几个例子:

axiom:FX    rule:X->[-FX]+FX     angle:35

axiom:F    rule:F->FF+[+F-F-F]-[-F+F+F]    angle:35

axiom:F    rule:F->F[+FF][-FF]F[-F][+F]F    angle:35

axiom: X    rule:'X->F[+X]F[-X]+X','F->FF'    angle:35

代码:

class LSystemScene(Scene):
"""L-System 分形树,灵感来源于the Coding Train的视频"""

    def __init__(self, *args):
        super(LSystemScene, self).__init__(*args)

        rule_str1_1 = 'F->FF+[+F-F-F]-[-F+F+F]'
        rule_str1_2 = 'X->[-FX]+FX'
        rule_str1_3 = 'F->F[+FF][-FF]F[-F][+F]F'

        # rule_str2_1 = 'X->F+[[X]-X]-F[-FX]+X'
        rule_str2_1 = 'X->F[+X]F[-X]+X'
        rule_str2_2 = 'F->FF'

        self.__rule = self.rule(rule_str1_1)
        self.__rule2_1 = self.rule(rule_str2_1)
        self.__rule2_2 = self.rule(rule_str2_2)

        self.isFill = False
        self.caption = 'L-Systems生成树,鼠标点击,观看树的生长'

        self.l_system = LSystem([self.__rule], 'F', math.radians(35), self.screen)
        self.l_system.len = 10

    class rule:  # 内部类
        def __init__(self, _str):
            self.__str = _str
            self.a, self.b = _str.split('->')

        def __str__(self):
            return self.__str

    def setup(self):
        self.createTextElement('规则:' + str(self.__rule), color=(153, 217, 234))
        self.createTextElement('根:' + 'F', color=(153, 217, 234))

    def doMouseButtonDownEvent(self, Button):
        self.l_system.generate(self.width / 2, self.height)

class LSystem:
    def __init__(self, rules, axiom, angle, sur):
        self.__axiom = axiom
        self.__rules = rules
        self.__sentence = self.__axiom
        self.__angle = angle
        self.len = 10
        self.painter = Painter(sur)

    def generate(self, x, y):
                nextSentence = ''
        for i in range(len(self.__sentence)):
                        current = self.__sentence[i]
                        found = False
            for j in range(len(self.__rules)):
                if current == self.__rules[j].a:
                                        found = True
                    nextSentence += self.__rules[j].b
                if not found:
                                    nextSentence += current

            self.__sentence = nextSentence
            self.__show(x, y)

    def getSentence(self):
        return self.__sentence

    def __show(self, x=0, y=0):
        self.painter.resetCurrentMat()
        self.painter.translate(x, y)
        for i in range(len(self.__sentence)):
                        current = self.__sentence[i]
            if current == 'F':
                self.painter.Lines([point2(0, 0), point2(0, -self.len)], (255, 255, 255), 1, 0)
                self.painter.translate(0, -self.len)
            elif current == '+':
                self.painter.rotate(self.__angle)
            elif current == '-':
                self.painter.rotate(-self.__angle)
            elif current == '[':
                self.painter.push()
            elif current == ']':
                self.painter.pop()

推荐文章
评论(0)
分享到
转载我的主页