[ 源码分享 ] Test251- 有关树的生成

2018-10-13 23:06:46 织梦安装使用
  • 文章介绍
Wenzy InsLab

分享一个先前练习的源码,使用的框架为 Openframeworks。下图是用该程序生成 3d 模型文件,再放到 KeyShot 中渲染的效果

程序中的生成过程

(打开程序后,枝干会自动开始生成,如希望产生其他形态,可按快捷键 ‘ C ’ 重置)

(待曲线生成完毕,按快捷键‘ R ’可将曲线转换为 3d 模型文件,并储存到 data 文件夹中)


具体代码

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();
    }
};

简要说明:

  • 枝干的生成主要通过 WenzyGrowingCurve.h 类实现。代码不足 100 行,尽管结构简单,但生成的效果还是比较贴近植物的自然形态。通过该类生成的对象对应每根独立的枝干。调整相关参数,可以产生截然不同的形态。

  • 从曲线转化为立体,主要通过自定义函数 drawMeshLine 实现。之前某篇文章有介绍过具体原理。

    • Openframeworks 三维模型导出技巧 ]

  • 程序导出的 3d 文件格式为 ply,可以使用 Meshlab 等软件转换为常用的 obj 格式放到 keyshot 中渲染

End

以上代码仅仅是一个基础版本,还有很多内容可以继续深挖。例如使用 shader 渲染,添加树叶,让它产生更丰富自然的光影效果。

(使用 Shader 实时渲染)

完整工程文件下载:

(链接: https://pan.baidu.com/s/1sldpISP 密码: xs2v



    上一篇: 手机归属地查询源码分享 ..

    下一篇: 捕鱼达人游戏源码分享以及运行教程..

    相关文档推荐

    精品模板推荐

     2020-07-29   18166  0金币下载

     2020-07-27   65338  0金币下载

     2020-07-27   65333  0金币下载

     2020-06-22   57995  0金币下载

     2020-06-13   62585  0金币下载

     2020-06-13   62587  0金币下载

    专业的织梦模板定制下载站,在线购买后即可下载!

    商业源码

    跟版网模板,累计帮助5000+客户企业成功建站,为草根创业提供助力!

    立刻开启你的建站之旅
    
    QQ在线客服

    服务热线

    织梦建站咨询