完成时间: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:{ F }
非终结符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;
}
}
}