[OpenGL] L系统 分形树的实现(L-System植物建模)

完成时间:2015/10/13(大二上学期)

耗时:4天

参考资料:

[1] Wiki - L system https://en.wikipedia.org/wiki/L-system

首先贴一下我的实现结果:(图一为橡树、图二为随便写的枯藤文法)

 

实现功能:

1. 程序可以读取用户定义的fct格式(fractal的缩写~)的文法,并根据载入的文法规律生成植物(附件提供了2组测试文法)。

2. 系统提供了一组预设的纹理。用户可以在程序中修改植物的纹理,实现不同的叶片、树干效果。

3. 除了我给出的一组fct格式的文法之外,用户可以自行编辑设计文法,实现更多的的植物生长效果。关于fct文件的语法格式,可以在程序菜单栏的“帮助”->“使用说明....”中查看。

4. 允许用户在程序中修改分形的参数、绕x,y,z的偏移、叶片大小等等。

 

这里,fct文件的语法格式为:

 

 

关于L系统

一、L系统简介

        (注:部分翻译自Wiki)

         L系统(L-System、Lindenmayer System)是一个有一系列文法的并行重写系统。(学过编译原理的同学,应当对文法Grammar较为熟悉吧,L系统的本质就是文法!),这里我用一个经典例子Koch雪花曲线来描述什么是L系统。

        假定有如下文法G[S]= { S, Vt, Vn , p}

               文法开始符S:{ }

              非终结符Vn:{ F }

               终结符Vt:{ + , - }

              产生式规则p: F -> F-F++F-F

        学过编译原理的同学应该对这个并不陌生,这就是一个普通文法,那么如何将上述文法G[S]和我们的L系统相联系起来呢?这就要求我们为上面的符号约定含义

              F:画一条直线。

             +:右转60°

               -:左转60°

       那么,假设从文法开始符开始:

       迭代0次:F

       迭代1次:F-F++F-F  (由产生式P得出)

       迭代2次:F-F++F-F-F-F++F-F++F-F++F-F-F-F++F-F(每一个下划线都由上一次迭代的F得到)

        ........

        如此这般,应用上面约定的含义,类似于小学时期玩过的小乌龟划线,就可以画出如下美丽的Koch雪花曲线(可以动手验证一下,食用效果更佳,值得注意的是:这里随着迭代次数n增加,每次由F进行划线的长度都缩短为最初长度的(1/(n+1))),这里给出的分别是迭代0次、迭代1次、迭代2次、迭代3次的图像。

二、随机L系统:

通过上面的例子我们可以建立起对L系统的初步认识。但是,显然这种L系统生成的图形非常规则。那么为了实现随机性,我们可以采用随机L系统对其进行改进。关于随机L系统:是对于同一个非终结符Vt有不同的产生式,每次应用迭代过程都要从中随机选取一个表达式生成。例如:

对于文法G[S] = {S, Vn, Vt , p}

              文法开始符S:{ F }  

              非终结符Vn:{ F }

              终结符Vt: { +、-、[、] }

              产生式规则p:

                                  F -> F[+F]F[-F]F        (1/3概率被使用)

                                  F-> F[+F]F[-F[+F]]    (1/3概率被使用)

                                  F-> FF+[+F+F]-[+F]  (1/3概率被使用)

    这里:F表示画一条直线,+/-表示向左/右旋转25°,“[”表示入栈保存当前状态(位置、角度),“]”表示出栈回到上一步状态。同时,每个F的产生式都被随机调用,这样每次得到的植株都将不同,并且充满随机性,就仿佛同种植株的不同个体一般。下图便是采用上述文法的到的一组二维植株。

 

三、扩展到三维状态

           对于三维状态下的L系统,我们可以采用如下规则如描述: 

           文法G[S] = {S, Vn, Vt, p }

            其中非终结符有F,功能是划线。

                    终结符+/-:    绕z轴旋转dz角度

                    终结符$/%:   绕y轴旋转dy角度

                    终结符^/&:  绕x轴旋转dx角度

                   终结符"[、]":进出栈,保存/退出当前状态。

          关于如何在三维状态下如何旋转角度,熟悉图形学的同学,应该对进行仿射变换的矩阵操作不陌生。这里简单的一个定义:

结语:

         通过上述方法和原理,便可以很轻松的实现一个L系统模拟三维植株的程序。另外,我想扩充几点在博客中没有提及的改进。首先,为了更进一步提高随机性,我们可以引入参数化的随机L系统。其次,对于植物枝干的模拟,为了实现弯曲效果,我们可以利用相邻枝干交点处的切向量来使用B样条曲线来实现。

 

贴出核心代码(为了方便阅读,给出的是简化版代码;仅包含如何利用文法产生分枝的部分,如需产生类似上面贴图中的较好的效果,可以自行修改下面的代码。)

Part one: 文法分析器Grammar类

 

// Grammar.h 文法分析器类头文件
class Grammar
{
private:
    String Gname;  // 文法名

    int level;        // 迭代层次
    String start;    // 文法开始符
    String result;   // 存储迭代level次后的结果

    Vector<Tuple<char,Vector<String> > > generations;  // 所有产生式,使用STL下的Tuple模板
public:

    Grammar(){}

    void clear();   // 清除所有数据

    int find(char ch);  // 查找ch为左部的产生式标号
    String search(const char& ch);  // 返回ch为左部的产生式右部(随机)

    void addGeneration(const char& ch,  const QString& ref); // 添加一个产生式
    void iterateFor(int num);  // 迭代num次

    void setGrammarName(const QString& ref);
    void setStart(const QString& ref);
    void setLevel(int num);

    QString getGrammarName();
    int getLevel();
    QString getResult();

};

 

// Grammar.cpp

#include "grammar.h"

void Grammar::clear()
{
    generations.clear();
}

int Grammar::find(char ch)
{
    for(int i=0; i< generations.size(); i++)
    {
        if( generations[i].getA() == ch)
            return i;   // Find it!
    }
    return -1;   // Can't Find, return -1
}


QString Grammar::search(const char& ch)    // 随机返回一个产生式右部
{
    //qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));

    int id = find(ch);

    if(id == -1)   // 不是产生式
        return QString(ch);

    int num = generations[id].getB().size();

    int index = qrand()%num; // 随机生成一个产生式index

    return generations[id].getB()[index];
}


void Grammar::addGeneration(const char& ch,  const String& ref)  // 添加一个产生式
{
    int id = find(ch);

    if(id == -1)    // 检查ch是否已经存在: 不存在,新建一个
    {
        Tuple<char, Vector<String> > temp;
        temp.setA(ch);
        temp.getB().push_back(ref);
        generations.push_back(temp);
        return;
    }
    generations[id].getB().push_back(ref); // 存在,直接后面补上一个
}

void Grammar::iterateFor(int num)
{
    setLevel(num);

    result = start;
    for(int i=0; i<num ;i++)
    {
        String tmp = "";
        for(int i=0; i<result.size();i++)
        {
            tmp+=search(result[i].toLatin1());
        }
        result = tmp;
    }
}

void Grammar::setGrammarName(const String& ref)
{
    Gname = ref;
}

void Grammar::setStart(const QString& ref)
{
    start = ref;
}

void Grammar::setLevel(int num)
{
    level = num;
}

QString Grammar::getGrammarName()
{
    return Gname;
}

int Grammar::getLevel()
{
    return level;
}

QString Grammar::getResult()
{
    return result;
}


Part Two:分形系统类FractalSystem

// 分形系统类FractalSystem.h

#include "grammar.h"

class State      // 当前探索状态
{
public:
    Vec3 pos;
    Vec3 dir;    // 当前乌龟方向,一个单位向量
};

class Trunk     // 枝干
{
public:
    Vec3 pos1;
    Vec3 pos2;
};

class FractalSystem
{
private:
    double dx,dy,dz;    // 围绕三个坐标轴的偏转角度

    double length;     // 初始步长
    double lengthFactor; // 步长比率
    double radius;   // 初始半径
    double radiusFactor;  // 半径比率

    State curState;  // 当前的位置和旋转信息

    double leafRadius;  // 叶片半径

    Grammar grammar;    // 一个文法分析器

public:

    Vector<Trunk> trunks; //  所有分支

    FractalSystem();

    void clearAll();
    void initGrammar();
    void generateFractal();
};

#endif // FRACTALSYSTEM_H

 

 

// FractalSystem.cpp
#include "fractalsystem.h"

FractalSystem::FractalSystem()
{
}


void FractalSystem::clearAll()
{
    grammar.clear();
    trunks.clear();
}

void FractalSystem::initGrammar()   // 加载一个文法
{
    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
    // 绕y轴旋转 $%
    // 绕x轴旋转 ^&
    // 绕z轴旋转 */
    grammar.setGrammarName("Test1");

//    // 2维
//    grammar.setStart("F");
//    grammar.addGeneration('F',"F[*F]F[/F]F");
//    grammar.addGeneration('F',"F[*F]F[/F[*F]]");
//    grammar.addGeneration('F',"FF*[*F*F]/[*F]");
//    grammar.iterateFor(6);

//    // 3维
//    grammar.setStart("F");
//    grammar.addGeneration('F',"F[*F]F[$F]");
//    grammar.addGeneration('F',"F[/F]F[$F[&F]]");
//    grammar.addGeneration('F',"FF*[^F^F]%[*F]");
//    grammar.iterateFor(6);

    grammar.addGeneration('F',"F");
    //grammar.addGeneration('X',"F[%[^$X]][$[&X]]^X");
    grammar.addGeneration('X',"F[$$$[*X][/X]]");
    grammar.addGeneration('X',"F[%%%[*X][/X]]");
    grammar.addGeneration('X',"F[$[*X][/X]]");
    grammar.addGeneration('X',"F[%%[*X][/X]]");
    grammar.setStart("X");
    grammar.iterateFor(8);

    qDebug()<<grammar.getResult();
}

void FractalSystem::generateFractal()   // 利用加载过的文法,创建分形树
{ 
    trunks.clear();

    // 乌龟初始状态: 坐标(0,0,0),方向y轴正方向
    curState.pos = Vec3(0,0,0);
    curState.dir = Vec3(0,1,0);

    Vector<State> stacks; // 状态栈
    stacks.push_back(curState);

    for(int i=0; i<grammar.getResult().size();i++)
    {
        char ch= grammar.getResult()[i].toLatin1();  // 当前字符

        Trunk tmp; // 临时树干

        switch(ch)
        {
        case 'F':
            // 画一条直线

            tmp.pos1 = curState.pos;

            //修改curState的pos;
            curState.pos.x += length*curState.dir.x;
            curState.pos.y += length*curState.dir.y;
            curState.pos.z += length*curState.dir.z;

            tmp.pos2 = curState.pos;

            trunks.push_back(tmp);
            break;
        case '$':
            // 增加curState的a;
            curState.dir = MyMath::RotateY(curState.dir,dy);
            break;
        case '%':
            // 减少curState的a;
            curState.dir = MyMath::RotateY(curState.dir,-dy);
            break;
        case '^':
            curState.dir = MyMath::RotateX(curState.dir,dx);
            break;
        case '&':
            curState.dir = MyMath::RotateX(curState.dir,-dx);
            break;
        case '*':
            curState.dir = MyMath::RotateZ(curState.dir,dz);
            break;
        case '/':
            curState.dir = MyMath::RotateZ(curState.dir,-dz);
            break;
        case '[':
            // 将curState进一次栈
            stacks.push_back(curState);
            break;
        case ']':
            // 将curState取栈顶,出栈
            curState = stacks.at(stacks.size()-1);
            stacks.removeLast();
            break;
        default:
            break;
        }
    }
}

 

 

 

 

 

  • 27
    点赞
  • 126
    收藏
    觉得还不错? 一键收藏
  • 94
    评论
本文将介绍如何利用三维热传导模型实现日光温室不通风情况下的湿度分布模拟,并提供MATLAB代码实现案例。 1. 建立三维温室模型 首先需要建立日光温室的三维模型。可以使用专业的3D建模软件,如SketchUp、AutoCAD等,也可以使用MATLAB自带的3D建模工具进行建模。具体建模方法与步骤在此不再赘述。 2. 确定边界条件 在建立好三维温室模型后,需要确定边界条件。边界条件包括温度、湿度、风速等。这些参数可以通过实际测量或者模拟计算得到。对于不同的边界条件,需要采用不同的数值方法进行求解。 3. 利用三维热传导方程求解 接下来利用三维热传导方程求解温室内的湿度分布。三维热传导方程为: $$\frac{\partial T}{\partial t} = \alpha (\frac{\partial^2 T}{\partial x^2} + \frac{\partial^2 T}{\partial y^2} + \frac{\partial^2 T}{\partial z^2}) + Q$$ 其中,T为温度,t为时间,$\alpha$为热传导系数,Q为热源项。在湿度分布模拟中,需要将上述方程扩展为含有湿度的方程。 4. 编写MATLAB代码实现 利用MATLAB编写程序实现湿度分布模拟。具体代码实现如下: ```matlab % 温室模型参数 L = 10; % 温室长度 W = 5; % 温室宽度 H = 3; % 温室高度 % 空气参数 rho = 1.2; % 空气密度 Cp = 1005; % 空气比热容 k = 0.026; % 空气热导率 % 边界条件 T0 = 20; % 温度初始值 T1 = 30; % 温度边界值 H0 = 0.01; % 湿度初始值 H1 = 0.02; % 湿度边界值 % 时间参数 dt = 0.01; % 时间步长 t = 0:dt:3600; % 时间范围 % 空间参数 dx = 0.1; % 空间步长 dy = 0.1; % 空间步长 dz = 0.1; % 空间步长 % 三维网格 [X,Y,Z] = meshgrid(0:dx:L,0:dy:W,0:dz:H); % 初始化温度和湿度 T = T0*ones(size(X)); H = H0*ones(size(X)); % 边界条件 T(:,1,:) = T1; T(:,end,:) = T1; T(1,:,:) = T1; T(end,:,:) = T1; H(:,1,:) = H1; H(:,end,:) = H1; H(1,:,:) = H1; H(end,:,:) = H1; % 计算热传导系数 alpha = k/(rho*Cp); % 计算热源项 Q = 0; % 三维热传导方程求解 for i = 2:length(t) % 计算空气流动速度 v = 0; % 计算风速 u = 0; % 计算大气辐射 R = 0; % 计算水蒸气含量 q = 0; % 计算植物蒸腾 E = 0; % 计算降水量 P = 0; % 计算热源项 Q = rho*Cp*(v.*diff(T,1,1)/dx + u.*diff(T,1,2)/dy + alpha*(diff(T,2,1)/dx^2 + diff(T,2,2)/dy^2 + diff(T,2,3)/dz^2) + R + Lv*q - E + P); % 更新温度和湿度 T = T + Q*dt; H = H + q*dt; end % 绘制湿度分布图 figure; slice(X,Y,Z,H,[L/2],[W/2],[H/2]); title('湿度分布图'); xlabel('x'); ylabel('y'); zlabel('z'); ``` 5. 结果分析 运行上述代码,可以得到日光温室不通风情况下的湿度分布图。可以根据该图像对温室内的湿度分布进行分析和预测,为农业生产提供重要的参考依据。 总结 本文介绍了利用三维热传导方程模拟日光温室不通风情况下的湿度分布模型,并提供了MATLAB代码实现案例。该模型可以为农业生产提供重要的参考依据,帮助农民掌握温室内的湿度分布情况,从而更好地管理和调节温室环境。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 94
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值