Tree generator [source code]


#1

I have made an example about tree .Share a source code below~

Program run in OF

after the lines finish growing.Press ‘R’ will save the 3d Model use format ‘.ply’ . Then you can render the model in other 3D software like KeyShot.

You can set different parameter to get different shape .Render by KeyShot below.


Here is the code.

ofApp.h -----

#include "ofMain.h"
#include "WenzyGrowingCurve.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();
		......
		
    ofPoint crossProduct(ofPoint a,ofPoint b);

    void generate();
    void drawMeshLine(ofPolyline myLine,int divideNum,int circleNum,float Rmin,float Rmax);

    ofMesh mesh;
    bool addingMesh;
    int startStepNum; // 起始步长
    int bloomNum; // 每个节点生长的枝干数量
    int maxRecursionNum;
    
    ofEasyCam cam;
    vector<WenzyGrowingCurve> curve; // 储存所有曲线
};

ofApp.cpp -----

void ofApp::setup(){
    cam.setDistance(2000);
    startStepNum = 200;
    maxRecursionNum = 3; // 递归次数,决定枝干复杂度
    bloomNum = 5;
    generate();
}

void ofApp::generate(){
    // 样式一:
    for(int i = 0;i < 1;i++){
        WenzyGrowingCurve temp;
        temp.setup(ofPoint(0,-300,0),ofPoint(0,1,0),0,startStepNum,0.01);
        curve.push_back(temp);
    }
    
    // 样式二
//    for(int i = 0;i < 6;i++){
//        WenzyGrowingCurve temp;
//        temp.setup(ofPoint(0,0,0),ofPoint(0,0,0),0,startStepNum,0.02);
//        curve.push_back(temp);
//    }
    
    mesh.setMode(OF_PRIMITIVE_TRIANGLES);
}

//--------------------------------------------------------------
void ofApp::update(){
    for(int i = 0;i < curve.size();i++){
        curve[i].update();
        // 长出新枝干
        if(curve[i].generation < maxRecursionNum && curve[i].moving == false && !curve[i].hasBloomed){
            curve[i].hasBloomed = true;
            
            for(int j = 0;j < bloomNum;j++){
                WenzyGrowingCurve temp;
                
                int nextStepNum;
                if(curve[i].generation < maxRecursionNum - 1){
                    float ratio = 0.72; // 决定每个枝干缩小的比例
                    nextStepNum = pow(ratio,curve[i].generation) * startStepNum * ofRandom(0.8,1.1);
                }else{
                    // 末端枝干更短
                    nextStepNum = startStepNum * 0.2 * ofRandom(0.5,1.5);
                }
                temp.setup(curve[i].endPos,curve[i].curveLineDir,curve[i].generation + 1,nextStepNum,0.06);
                curve.push_back(temp);
            }
        }
    }
    ofSetWindowTitle(ofToString(ofGetFrameRate()));
}

// 求叉积
ofPoint ofApp::crossProduct(ofPoint a, ofPoint b){
    ofPoint c;
    c.x = a.y*b.z - a.z*b.y;
    c.y = a.z*b.x - a.x*b.z;
    c.z = a.x*b.y - a.y*b.x;
    return c;
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofBackground(0);
    cam.begin();
    
    
    ofRotateY(ofGetFrameNum() * 0.5);

    ofSetLineWidth(2.5);
    ofSetColor(255,200);
    for(int i = 0;i < curve.size();i++){
        curve[i].draw();
    }
    
    if(addingMesh){
        for(int i = 0;i < curve.size();i++){
            float minR = 50 * pow(0.5,curve[i].generation + 1);
            float maxR = 50 * pow(0.5,curve[i].generation);

            if(curve[i].generation == 0){
                drawMeshLine(curve[i].curveLine,20,20,maxR,minR);
            }else if(curve[i].generation == maxRecursionNum){
                // 末端枝干用更少的面数可节省资源
                drawMeshLine(curve[i].curveLine,6,10,maxR,minR);
            }else{
                drawMeshLine(curve[i].curveLine,20,20,maxR,minR);
            }
        }
        addingMesh = false;
        // 保存文件
        mesh.save("1.ply");
    }
    
    if(!addingMesh){
        ofSetLineWidth(1);
        ofSetColor(255,100);
        mesh.drawWireframe();
    }
    cam.end();
}

void ofApp::drawMeshLine(ofPolyline myLine,int divideNum,int circleNum,float startR,float endR){
    
    vector<vector<ofPoint>> myPos;
    // 从 -1 开始是为了能绘制底面
    for(int i = -1;i < divideNum;i++){
        
        float ratio1,ratio2;
        ofPoint A,B; // 根据 A,B 求圆环
        if(i != -1){
            ratio1 = i/(float)divideNum;
            A = myLine.getPointAtPercent(ratio1);
            ratio2 = (i + 1)/(float)divideNum;
            B = myLine.getPointAtPercent(ratio2);
        }else{
            ratio1 = 0;
            A = myLine.getPointAtPercent(ratio1);
            ratio2 = 0.00001;
            B = myLine.getPointAtPercent(ratio2);
        }
        
        
        // dir 为基向量
        ofPoint ab;
        ab = B - A;
        ab.normalize();
        
        // ab 与 X 轴的叉积
        ofPoint M;
        M = crossProduct(ab,ofPoint(1,0,0));
        
        // ab 与 M 的叉积,N
        ofPoint N;
        N = crossProduct(ab,M);
        
        // 求基向量
        ofPoint n,m;
        n = N.normalize();
        m = M.normalize();
        
        // 设 theta
        float newRatio = (i + 1)/(float)(divideNum + 1);
        float R = ofLerp(startR,endR,newRatio);
        
        vector<ofPoint> tempPos;
        for(int i = 0;i < circleNum;i++){
            float theta = 2 * PI /circleNum * i;
            float ratio = 1;
            ofPoint C;
            C = B + ratio * R * (m * cos(theta) + n * sin(theta));
            ofSetColor(255,0,0);
            //ofDrawSphere(C,3);
            tempPos.push_back(C);
        }
        myPos.push_back(tempPos);
    }
    
    ofSetColor(255,150);
    for(int i = 0;i < myPos.size()-1;i++){
        int indexA = i;
        int indexB = i + 1;
        for(int j = 0;j < circleNum;j++){
            if(addingMesh){
                mesh.addVertex(myPos[indexA][j]);
                mesh.addVertex(myPos[indexA][(j + 1) % circleNum]);
                mesh.addVertex(myPos[indexB][j]);
                
                mesh.addVertex(myPos[indexA][(j + 1) % circleNum]);
                mesh.addVertex(myPos[indexB][(j + 1) % circleNum]);
                mesh.addVertex(myPos[indexB][j]);
                
                if(i == 0){
                    ofPoint center;
                    center.set(0,0,0);
                    for(int k = 0;k < circleNum;k++){
                        center += myPos[indexA][k];
                    }
                    center /= circleNum;
                    
                    for(int k = 0;k < circleNum;k++){
                        mesh.addVertex(center);
                        mesh.addVertex(myPos[indexA][(k+1) % circleNum]);
                        mesh.addVertex(myPos[indexA][k]);
                    }
                    
                }
                if(i == myPos.size() - 2){
                    ofPoint center;
                    center.set(0,0,0);
                    for(int k = 0;k < circleNum;k++){
                        center += myPos[indexB][k];
                    }
                    center /= circleNum;
                    
                    for(int k = 0;k < circleNum;k++){
                        mesh.addVertex(myPos[indexB][k]);
                        mesh.addVertex(myPos[indexB][(k+1) % circleNum]);
                        mesh.addVertex(center);
                    }
                }
            }
        }
    }
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){

    if(key == 'r'){
        addingMesh = !addingMesh;
    }
    //  重新生成
    if(key == 'c'){
        curve.clear();
        mesh.clear();
        generate();
    }
}

WenzyGrowingCurve.h -----

class WenzyGrowingCurve{
public:
    ofPolyline curveLine; // 表示每条基础曲线
    ofVec3f curveLineDir;  // 线条方向
    ofVec3f curvePos; // 用于加入的点,方便累加
    ofVec3f noiseSeed;
    ofVec3f startPos,endPos; // 每条曲线的起始结束点
    
    // 动画相关
    
    bool moving; // 是否开始运动(添加点)
    int curStep;  // 当前已运行步数
    int stepNum; // 运行总步数
    float stepLength; // 每次步进的长度
    
    int generation; // 代数,方便外部初始化
    float dirRange; // 扩散角度,数值越大扭曲程度越高
    bool hasBloomed; // 是否已经生长过
    
    void setup(ofVec3f startPos_,ofVec3f startDir_,int generation_,int stepNum_,float dirRange_){
        noiseSeed = ofVec3f(ofRandom(100),ofRandom(100),ofRandom(100));
        stepLength = 2;
        moving = true;
        curStep = 0;
        stepNum = stepNum_;
        generation = generation_;
        curveLineDir = startDir_; // 继承上条曲线的方向
        startPos = startPos_;
        curvePos = startPos;
        hasBloomed = false;
        dirRange = dirRange_;
        curveLine.curveTo(startPos);
        curveLine.curveTo(startPos);
    }
    
    void update(){
        if(moving && curStep < stepNum){
            float noiseRange = 0.01; // 控制 noise 的输入
            curveLineDir += ofVec3f(ofSignedNoise(noiseSeed.x + ofGetFrameNum() * noiseRange),
                                    ofSignedNoise(noiseSeed.y + ofGetFrameNum() * noiseRange),
                                    ofSignedNoise(noiseSeed.z + ofGetFrameNum() * noiseRange))
                                    * dirRange;
            curveLineDir.normalize();
            curvePos += curveLineDir * stepLength;
            if(curStep % 3 == 0){
                curveLine.curveTo(curvePos);
            }
            
            curStep++;
            if(curStep == stepNum){
                endPos = curvePos;
                curveLine.curveTo(curvePos);
                curveLine.curveTo(curvePos);
            }
        }else{
            moving = false;
        }
    }
    
    void draw(){
        curveLine.draw();
    }
};

any advice is appreciate :slight_smile: