山东大学 计算机图形学实验 二维网格剖分 Catmull-Clark算法

原理可以参考这里

想自己写的记得注意边界条件:度数<=3的顶点不做位置调整,只和一个面相邻的边点直接取边的中点

#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <algorithm>
#include <cmath>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

struct V{
    int v_id;
    double x, y;
};

struct E{
    int e_id, V1, V2;
    //vector<int> nf;
};

class F{
public:
    int f_id;
    vector<int> es; // 属于该面的所有边的索引
    vector<int> vs; // 属于该面的所有点的索引

    F(){}

    F(const F& f){
        f_id = f.f_id;
        es.clear();
        vs.clear();
        for (auto &e: f.es)
            es.push_back(e);
        for (auto &v: f.vs)
            vs.push_back(v);
    }

    void operator = (const F& f){
        f_id = f.f_id;
        es.clear();
        vs.clear();
        for (auto &e: f.es)
            es.push_back(e);
        for (auto &v: f.vs)
            vs.push_back(v);
    }
};
 
void readFile(const string &filename, vector<V> &vs, vector<E> &es, vector<F> &fs){
    ifstream file(filename);
    string line;
    int i = 0, j = 0, k = 0;
    while (getline(file, line)){
        istringstream iss(line);
        string type;
        iss >> type;
        if (type == "v"){ // 顶点信息
            V v;
            i++;
            v.v_id = i;
            iss >> v.x >> v.y;
            vs.push_back(v);
        }
        else if (type == "e"){ // 边信息
            E e;
            j++;
            e.e_id = j;
            iss >> e.V1 >> e.V2;
            es.push_back(e);
        }
        else if (type == "f"){ // 面信息
            F f;
            k++;
            f.f_id = k;
            string vertexIndex;
            while (iss >> vertexIndex){
                istringstream vertexIss(vertexIndex);
                int vIndex;
                vertexIss >> vIndex;
                f.vs.push_back(vIndex);
            }
            for (auto &e : es){
                int count = 0;
                for (auto v : f.vs){
                    if (e.V1 == v || e.V2 == v){
                        count++;
                        if (count == 2){
                            f.es.push_back(e.e_id);
                            //e.nf.push_back(k);
                            break;
                        }
                    }

                }
            }
            fs.push_back(f);
        }
    }
}
void writeFile(const string &filename, const vector<V> &vs, const vector<E> &es, const vector<F> &fs){
    ofstream file(filename);
 
    // 写入顶点信息
    for (const auto &v : vs){
        file << "v " << v.x << " " << v.y << endl;
    }
 
    // 写入边信息
    for (const auto &e : es){
        file << "e " << e.V1 << " " << e.V2 << endl;
    }
 
    // 写入面信息
    for (const auto &f : fs){
        file << "f";
        for (int v : f.vs){
            file << " " << v; // 从1开始的索引转换为从0开始
        }
        file << endl;
    }
}

void draw(const vector<V> &vs, const vector<E> &es){
    cv::Mat image(600, 800, CV_8UC3, cv::Scalar(255, 255, 255));
    for (const auto &v : vs){
        cv::Point p(v.x * 200, v.y * 200);
        circle(image, p, 3, Scalar(0, 0, 0), -1);
    }
    for (const auto &e : es){
        cv::Point p1(vs[e.V1 - 1].x * 200, vs[e.V1 - 1].y * 200);
        cv::Point p2(vs[e.V2 - 1].x * 200, vs[e.V2 - 1].y * 200);
        cv::line(image, p1, p2, cv::Scalar(0, 0, 255), 1);
    }
    cv::imshow("画板", image);
    cv::waitKey(0);
}

void catmullClark(vector<V> &vs, vector<E> &es, vector<F> &fs){
    vector<E> new_es;
    vector<F> new_fs;
    int v_cnt = vs.size();
    int n = v_cnt;
    int e_cnt = es.size();
    int f_cnt = fs.size();
    int m = 0;
    int k = 0;
    // 面点
    for (auto &f : fs){
        double x = 0, y = 0;
        for (auto& v_index : f.vs){
            x += vs[v_index - 1].x; 
            y += vs[v_index - 1].y;
        }
        x /= 1.0 * f.vs.size();
        y /= 1.0 * f.vs.size();

        //circle(image, Point(x * 200, y * 200), 6, Scalar(0, 0, 0), -1);

        vs.push_back(V{v_cnt + f.f_id, x, y});
    }
 
    // 边点
    for (auto &e : es){
        double i = 2;
        double x = vs[e.V1 - 1].x + vs[e.V2 - 1].x; 
        double y = vs[e.V1 - 1].y + vs[e.V2 - 1].y;
        double x0 = x, y0 = y;
        for (auto &f : fs){
            for (auto& e_index : f.es){//查找包含e的面
                if (e_index == e.e_id){ 
// v_cnt + f.f_id – 1为对应面点的下标
                    x += vs[v_cnt + f.f_id - 1].x; 
                    y += vs[v_cnt + f.f_id - 1].y;
                    ++m;
                    new_es.push_back(E{m, v_cnt + f_cnt + e.e_id, v_cnt + f.f_id});
                    i += 1.0;
                }
            }
        }
        if (i < 3.5) //面数<=1,取中点
            x = x0, y = y0, i = 2.0;
        x = x / i;
        y = y / i;
        V edgeCenter = V{v_cnt + f_cnt + e.e_id, x, y};
        //边点和两端点连边
        ++m;
        new_es.push_back(E{m, v_cnt + f_cnt + e.e_id, e.V1});
        ++m;
        new_es.push_back(E{m, v_cnt + f_cnt + e.e_id, e.V2});

        vs.push_back(edgeCenter);//边点加入点集
    }
    for (auto& f : fs){//连接剩余的边,得到新面
        for (auto& v_index : f.vs){
            k++;
            F new_f1;
            new_f1.f_id = k;
            //面点-边点-原顶点-边点构成一个新面
            //面和原顶点加入f的点集
            new_f1.vs.push_back(v_index);
            new_f1.vs.push_back(v_cnt + f.f_id);
            for (auto& e_index : f.es) {
                //查找得到对应的边点
                if (es[e_index - 1].V1 == v_index || es[e_index - 1].V2 == v_index){
                    new_f1.vs.push_back(v_cnt + f_cnt + e_index);
                }
            }
            for (auto &e:new_es){
                //在新边集中查找,得到f的边集
                int c = 0;
                for (auto &v:new_f1.vs){
                    if (e.V1 == v || e.V2 == v){
                        ++c;
                    }
                    if (c == 2){
                        new_f1.es.push_back(e.e_id);
                        break;
                    }
                }
            }
            // cout<<endl;
            new_fs.push_back(new_f1);
        }
    }
    vector<V> modiv;
    for (auto &v : vs){
        n--;
        if (n < 0)
            break;
        //更新原顶点
        double x = 0, y = 0;
        double count = 0, c2 = 0;
        for (auto& f : fs){
            for (auto& v_index : f.vs){//计算面点部分
                if (v_index == v.v_id){
                    x += vs[v_cnt + f.f_id - 1].x;
                    y += vs[v_cnt + f.f_id - 1].y;
                    count += 1.0;
                }
            }
        }
        for (auto& e : es) {//计算边点部分
            if (e.V1 == v.v_id || e.V2 == v.v_id){
                x += 2.0 * vs[v_cnt + f_cnt + e.e_id - 1].x; 
                y += 2.0 * vs[v_cnt + f_cnt + e.e_id - 1].y;
            }
        }
        if (count < 4) continue;//度数小于4,跳过
        x /= 4.0 * count;
        y /= 4.0 * count;
        x += v.x / 4.0, y += v.y / 4.0;

        modiv.push_back(V{v.v_id, x, y});
    }
    for (auto &v:modiv){
        vs[v.v_id - 1].x = v.x;
        vs[v.v_id - 1].y = v.y;
    }
    es.clear();
    for (auto& e:new_es)
        es.push_back(e);
    fs.clear();
    for (auto& f:new_fs)
        fs.push_back(f);
//cv::imshow("画板", image);
 //cv::waitKey(0);
}
 
int main(){
    vector<V> vs;
    vector<E> es;
    vector<F> fs;
    int op;
    cin >> op;
    readFile("dat", vs, es, fs);
    for (int i = 0; i < op; i++){
        catmullClark(vs, es, fs);
       
    }
    writeFile("output", vs, es, fs);
    draw(vs, es);
    vs.clear();
    fs.clear();
    es.clear();
    return 0;
}

posted @ 2025-11-16 21:23  <blank>  阅读(10)  评论(0)    收藏  举报