Biboff est une suite d'utilitaires pour gérer des modèles 3d

Description

Biboff est une suite de cinq programmes pour lire, contrôler, vérifier, corriger et convertir des modèles 3d au format off. Il existe de nombreux formats pour modéliser des objets 3d : obj, ply, 3ds, vrml, stl, etc. Le format off dans sa version initiale comporte uniquement une simple liste de sommets : x, y et z et une liste de faces polygonales avec seulement pour chacune d'elles les numéros de ses sommets. L'inconvénient de ce format off est que, contrairement à d'autres, il ne comporte pas de renseignements tels que les normales aux sommets ou dans les faces, les coordonnées 2d de paramétrisation pour plaquer des textures, des données de couleur ou diverses données de matériau, etc. Par contre l'avantage principal de ce format off est qu'il est toujours possible et facile d'obtenir ses données à partir de n'importe quel autre format plus évolué. C'est donc pour cela qu'il est intéressant de montrer que l'on peut traiter aussi bien l'objet 3d défini en format off que par un autre format plus riche, en particulier en recalculant, si nécessaire, la topologie que ce format ne fournit pas.
- Parmi les utilitaires ici présents c'est justement ce que fait readoff : il lit en format off le fichier modèle d'un objet 3d défini avec seulement des faces triangulaires et installe en mémoire centrale la très célèbre structure HalfEdge pouvant permettre de faire, si on le souhaite, toutes les opérations classiques sur les modèles "2d-manifold". Ce programme est ici présent en 2 versions : readoff-1 plus simple à comprendre mais plus lent et readoff-2 plus complexe mais beaucoup plus rapide.
- L'objectif de trianoff est de convertir un modèle off quelconque en modèle ayant uniquement des faces triangulaires.
- L'utilitaire normoff traite un modèle off en faces triangulaires pour orienter toutes les faces dans le même sens vers l'extérieur du modèle.
- Et l'utilitaire swapoff permet d'inverser le sens d'orientation de toutes les faces.
- Enfin l'utilitaire checkoff effectue automatiquement l'enchainement de trianoff, de normoff et de l'affichage du modèle.
Il y a aussi de nombreux exemples tout prêts pour faire directement divers essais. Biboff est donc une base, en mode console pour Windows, pouvant assez facilement être complétée pour d'autres traitements ou bien pour d'autres formats d'objets 3d.

Source / Exemple :


 
// ----  fichier mesh.hpp  --  @author pgl10  ----
// Pour un objet 2d-manifold modélisé en HalfEdge

#ifndef MESH_HPP
#define MESH_HPP

#include <iostream>
#include <vector>
#include <cmath>

class Vertex;
class Face;

class HalfEdge {

public:
    ~HalfEdge();
    HalfEdge();
    HalfEdge(Vertex v);
    HalfEdge(Vertex v, Face f);
    void setnext(HalfEdge* h);
    void setpair(HalfEdge* h);
    void setprev(HalfEdge* h);
    void setorig(Vertex* v);
    void setface(Face* f);
    HalfEdge* getpair();
    HalfEdge Lnext() const;
    HalfEdge Rpair() const;
    HalfEdge Lprev() const;
    Vertex&  Orign();
    Vertex   Orign() const;
    Vertex&  Extrm();
    Vertex   Extrm() const;
    Vertex&  operator[](int i);
    Vertex   operator[](int i) const;
    HalfEdge Rnext() const;
    HalfEdge Rprev() const;
    Face&    Lface();
    Face     Lface() const;

private:
    HalfEdge* _next;  // le HalfEdge suivant dans la même Face à gauche
    HalfEdge* _pair;  // le HalfEdge dans la Face à droite du Vertex de l'extrémité
    HalfEdge* _prev;  // le HalfEdge précédent dans la même Face à gauche
    Vertex*   _orig;  // le Vertex origine de ce HalfEdge
    Face*     _face;  // la Face à gauche de ce HalfEdge
};

class Vertex {

public:
    ~Vertex();
    Vertex();
    Vertex(double x, double y, double z);
    void sethe(HalfEdge* he);
    void getxyz(double xyz[3]);
    HalfEdge* gethe();

private:
    double  _x,  _y,  _z;
    HalfEdge* _he;  // pointeur vers l'un des HalfEdge où il est l'origine
// on peut ajouter ici d'autres données, par exemple la normale à ce sommet
};

class Face {

public:
    ~Face();
    Face();
    void sethe(HalfEdge* he);
    HalfEdge* gethe();

private:
    HalfEdge* _he;  // pointeur vers l'un de ses HalfEdge pour ses côtés
// on peut ajouter ici d'autres données, par exemple la normale à cette face
};

bool paste(HalfEdge& he1, HalfEdge& he2);
bool cut(HalfEdge& he);

#endif  // MESH_HPP

// ----  fichier mesh.cpp  --  @author pgl10  ---- 

#include "mesh.hpp"

// destructeur par défaut
HalfEdge::~HalfEdge() {}

// constructeur par défaut
HalfEdge::HalfEdge() : _next(NULL), _pair(NULL), _prev(NULL), _orig(NULL), _face(NULL)  {}

// constructeur incomplet pour un HalfEdge isolé défini uniquement par son origine
HalfEdge::HalfEdge(Vertex v) : _next(NULL), _pair(NULL), _prev(NULL), _orig(&v), _face(NULL) {}

// constructeur incomplet pour un HalfEdge isolé défini par son origine et sa face à gauche
HalfEdge::HalfEdge(Vertex v, Face f) : _next(NULL), _pair(NULL), _prev(NULL), _orig(&v), _face(&f) {}

void HalfEdge::setnext(HalfEdge* h) {_next = h;}

void HalfEdge::setpair(HalfEdge* h) {_pair = h;}

void HalfEdge::setprev(HalfEdge* h) {_prev = h;}

void HalfEdge::setorig(Vertex* v) {_orig = v;}

void HalfEdge::setface(Face* f) {_face = f;}

HalfEdge* HalfEdge::getpair() {return _pair;}

// Son HalfEdge suivant dans sa Face à gauche ( left = gauche )
HalfEdge HalfEdge::Lnext() const {return *_next;}

// Son HalfEdge pair et opposé dans sa Face à droite ( right = droite )
HalfEdge HalfEdge::Rpair() const {return *_pair;}

// Son HalfEdge précédent dans sa Face à gauche ( previous = précédent )
HalfEdge HalfEdge::Lprev() const {return *_prev;}

// Son Vertex origine
Vertex& HalfEdge::Orign() {return *_orig;}

Vertex  HalfEdge::Orign() const {return *_orig;}

// Son Vertex extrémité
Vertex& HalfEdge::Extrm() {return Lnext().Orign();}

Vertex  HalfEdge::Extrm() const {return Lnext().Orign();}

// e[0] : Vertex origine  et  e[1] : Vertex extrémité
Vertex& HalfEdge::operator[](int i) {return i ? Extrm() : Orign();}

Vertex  HalfEdge::operator[](int i) const {return i ? Extrm() : Orign();}

// Son HalfEdge suivant dans sa Face à droite ( right = droite )
HalfEdge HalfEdge::Rnext() const {return Rpair().Lnext();}

// Son HalfEdge précédent dans sa Face à droite
HalfEdge HalfEdge::Rprev() const {return Rpair().Lprev();}

// Sa Face à gauche
Face& HalfEdge::Lface() {return *_face;}

Face  HalfEdge::Lface() const {return *_face;}

Vertex::~Vertex() {}

Vertex::Vertex() : _x(0.0), _y(0.0), _z(0.0), _he(NULL) {}

Vertex::Vertex(double x, double y, double z) {_x = x; _y = y; _z = z; _he=NULL;}

void Vertex::sethe(HalfEdge* he) {_he = he;}

void Vertex::getxyz(double xyz[3]) {xyz[0]=_x; xyz[1]=_y; xyz[2]=_z;}

HalfEdge* Vertex::gethe() {return _he;}

Face::~Face() {}

Face::Face() :  _he(NULL) {}

void Face::sethe(HalfEdge* he) {_he = he;}

HalfEdge* Face::gethe() {return _he;}

// Pour coupler deux HalfEdge pairs
bool paste(HalfEdge& he1, HalfEdge& he2) {
    if(he1.getpair() != NULL) return false;
    if(he2.getpair() != NULL) return false;
    if(&he1.Orign() != &he2.Extrm()) return false;
    if(&he2.Orign() != &he1.Extrm()) return false;
    he1.setpair(&he2);
    he2.setpair(&he1);
    return true;
}

// Pour découpler deux HalfEdge pairs dont l'un est he
bool cut(HalfEdge& he) {
    if(he.getpair() != NULL) {
        (he.Rpair()).setpair(NULL);
        he.setpair(NULL);
        return true;
    }
    return false;
}

// ----  fichier readoff.cpp  --  @author pgl10  ---- 
// La commande : readoff fichier.off 
// lit le fichier.off d'un modèle en faces triangulaires
// et installe en mémoire une modélisation à base de HalfEdge

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <cmath>
#include <list>
#include "mesh.hpp"
#include "table.hpp"

void erroff(int n) {
    std::cout << "\nErreur " << n << " pour le fichier .off\n\n";
    exit(EXIT_FAILURE);
}
void help() {
    std::cout << "\nIl faut faire : readoff fichier.off\n\n";
    exit(EXIT_FAILURE);
}

bool newline(std::ifstream* fichier, std::string* ligne) {
// Lire une nouvelle ligne qui n'est ni vide, ni blanche et ni un commentaire
    for(;;) {
        std::getline(*fichier, *ligne);
        if((*fichier).fail()) return false;
        int n = strlen((*ligne).c_str());
        if(n == 0) continue;
        if((*ligne).c_str()[0] == '#') continue; 
        for(int i=0; i<n; i++) if((*ligne).c_str()[i] != ' ') return true;
    }
    return false;
}

int main(int argc, char *argv[])
{
    std::string ligne;
    int i, j, k, l, n, m, ns, nf, no, ne, h, s, t, nz, iz;
    double x, y, z;
    if ( argc != 2 ) help();
    char input[64], type[4];
    for(i=0; i<strlen(argv[1])-4; i++) input[i]=argv[1][i];
    input[strlen(argv[1])-4]='\0';
    for(i=0; i<4; i++) type[i]=argv[1][i+strlen(argv[1])-3];
    type[3]='\0';
    if(!(strcmp(type,"off")!=0 || strcmp(type,"OFF")!=0)) help();
// Lecture de l'entête du fichier d'entrée 
    std::ifstream fichier(argv[1], std::ios::in);
    if(fichier.fail()) erroff(1);
    if(!newline(&fichier, &ligne)) erroff(9);
    if(!strcmp(ligne.substr(0,2).c_str(), "OFF")) erroff(2);
    if(!newline(&fichier, &ligne)) erroff(9);
    std::istringstream iss1(ligne);
    if(!(iss1 >> ns >> nf)) erroff(3);
// Définition des 3 vecteurs : halfedges, vertices et faces
    std::vector<HalfEdge> halfedges;
    std::vector<Vertex> vertices;
    std::vector<Face> faces;
// Lecture des sommets du fichier .off et mise en place du vecteur des Vertex
    n=0;
    do {
        if(!newline(&fichier, &ligne)) erroff(9);
        std::istringstream istr(ligne);
        if(!(istr >> x >> y >> z)) erroff(4);
        Vertex v(x, y, z);
        vertices.push_back(v);
        n=n+1;
    }while(n<ns);
// Mise en place du vecteur des HalfEdge
    HalfEdge he;
    for(i=0; i<nf; i++) {
        halfedges.push_back(he);
        halfedges.push_back(he);
        halfedges.push_back(he);
    }
// Mise en place du vecteur des Face
    Face f;
    for(i=0; i<nf; i++) faces.push_back(f);
// Lecture des faces du fichier .off
// On termine de renseigner les vertices 
// Et on renseigne partiellement les halfedges et les faces
    n=0;
    do {
        if(!newline(&fichier, &ligne)) erroff(9);
        l=0;
        sscanf(ligne.c_str(), "%d %d %d %d %d", &t, &i, &j, &k, &l);
        if(t != 3) erroff(5); // les traitements effectués dans cette version
        if(l != 0) erroff(5); // concernent uniquement les faces triangulaires
        s = 3*n;
        halfedges[s].setnext(&halfedges[s+1]);
        halfedges[s].setprev(&halfedges[s+2]);
        halfedges[s].setorig(&vertices[i]);
        halfedges[s].setface(&faces[n]);        
        halfedges[s+1].setnext(&halfedges[s+2]);
        halfedges[s+1].setprev(&halfedges[s]);
        halfedges[s+1].setorig(&vertices[j]);
        halfedges[s+1].setface(&faces[n]); 
        halfedges[s+2].setnext(&halfedges[s]);
        halfedges[s+2].setprev(&halfedges[s+1]);
        halfedges[s+2].setorig(&vertices[k]);
        halfedges[s+2].setface(&faces[n]); 
        if(vertices[i].gethe()==NULL) vertices[i].sethe(&halfedges[s]);
        if(vertices[j].gethe()==NULL) vertices[j].sethe(&halfedges[s+1]);
        if(vertices[k].gethe()==NULL) vertices[k].sethe(&halfedges[s+2]);
        if(faces[n].gethe() == NULL) faces[n].sethe(&halfedges[s]);
        n=n+1;
    }while(n<nf);
    fichier.close();
    std::cout << "\nOn a ici : " << ns << " sommets et " << nf << " faces\n\n";
    std::cout << "\nEt : " << halfedges.size() << " HalfEdges\n\n";
// Il faut maintenant terminer de renseigner les halfedges
// en calculant pour chacun le HalfEdge pair s'il existe.
// Le modèle lu comporte peut-être des faces voisines ayant une
// arête commune mais des orientations différentes, même si ce 
// n'est pas souhaitable. Dans ce cas il y aura plusieurs zones
// comportant chacune toutes ses faces avec la même orientation.
// Les HalfEdge des faces d'une même zone pouront être couplés
// 2 à 2 quand ils partagent la même arête entre 2 faces voisines.
// Si le modèle lu comporte plusieurs parties disjointes chaque
// partie aura une ou plusieurs zones, mais chaque zone sera
// entièrement comprise dans une seule partie.
// 
// Dans un premier temps on calcule pour chaque 
// sommet le nombre de faces où il intervient.
    int* ms;
    ms = new int[ns];
    for(n=0; n<ns; n++) ms[n]=0;
    for(n=0; n<nf; n++) {
        he = *(faces[n].gethe());
        i = &he.Orign()-&vertices[0];
        he = he.Lnext();
        j = &he.Orign()-&vertices[0];
        he = he.Lnext();
        k = &he.Orign()-&vertices[0];        
        ms[i]=ms[i]+1;
        ms[j]=ms[j]+1;
        ms[k]=ms[k]+1;
    }
    m = 0;
    for(n=0; n<ns; n++) if(ms[n] > m) m = ms[n];
    delete [] ms;
// Le sommet qui a le plus de faces en a donc m
// On crée une Table à ns lignes dans laquelle 
// les 3 HalfEdge de chaque face seront renseignés
// dans la ligne du sommet d'origine avec la colonne 
// du sommet extrémité et sa face.
    Table fs;
    fs.settab(ns, m);
    for(n=0; n<nf; n++) {
// On récupère les numéros i, j et k des sommets de la face n
        he = *(faces[n].gethe());
        i = &he.Orign()-&vertices[0];
        he = he.Lnext();
        j = &he.Orign()-&vertices[0];
        he = he.Lnext();
        k = &he.Orign()-&vertices[0];
// On enregistre les 3 arêtes orientées i->j, j->k et k->i
        fs.set(i, j, n);
        fs.set(j, k, n);
        fs.set(k, i, n);
    }
    nz=1;
// nz : nombre de zones homogènes dans ce modèle
    iz=0;
// iz est le numéro de la première face de la zone nz
    int* zn;
    zn = new int[nf];
    for(n=0; n<nf; n++) zn[n]=0;
// zn[n] sera le numéro de zone de la n-ième face ( faces[n] )
    int* ee;
    ee = new int[m];
    int* fe;
    fe = new int[m];
// ee et fe serviront pour l'extrémité et la face associées
// à un sommet dans les diverses faces où il intervient.
    int* nk;
    nk = new int[nf];
    for(n=0; n<nf; n++) nk[n]=0;
// nk servira à compter le nombre de couplages de chaque face.
    int nbc = 0;
// nbc sera le nombre total de couplages effectués.
    nzone: zn[iz]=nz;
    do {
        t = 0;
        for(n=0; n<nf; n++) if(zn[n]==nz) if(nk[n]<3) {
// Les faces ayant déjà eu 3 couplages ne peuvent plus être couplées
// Cette face n a 3 HalfEdge : halfedges[3*n], halfedges[3*n+1] et halfedges[3*n+2]
            for(s=0; s<3; s++) {
                h = 3*n+s;
                if(halfedges[h].getpair() != NULL) continue;
// Ce halfedges[h] n'est pas couplé => on cherche un HalfEdge compatible
                no = &halfedges[h].Orign()-&vertices[0];
                ne = &halfedges[h].Extrm()-&vertices[0];
// Quelles sont les faces où le sommet ne est à l'origine d'un HalfEdge ?
                fs.getcv(ee, fe, ne);
                for(i=0; i<m; i++) {
                    int fei = fe[i];
                    if(fei == -1) break;
                    if(ee[i] == no) {
//   
// Dans la face fe[i] lequel des 3 HalfEdge va de ne à no ?
                        for(j=0; j<3; j++) {
                            k = 3*fei+j;
                            if(&halfedges[k].Orign()-&vertices[0] == ne) break;
                        }
// Et voici enfin le couplage recherché et son enregistrement
                        halfedges[h].setpair(&halfedges[k]);
                        halfedges[k].setpair(&halfedges[h]);
//   
// Voici une variante des instructions précédentes qui ne tient pas compte du fait
// que les 3 HalfEdge de la face fe[i] ont pour numéros 3*fei, 3*fei+1 et 3*fei+2
//                      he = *(faces[fei].gethe());
//                      if(&he.Orign()-&vertices[0] != ne) he = he.Lnext();
//                      if(&he.Orign()-&vertices[0] != ne) he = he.Lnext();
//                      he = he.Lnext();
//                      halfedges[h].setpair(&he.Lprev());
//                      he.Lprev().setpair(&halfedges[h]); 
//  
                        nk[fei] = nk[fei]+1;
                        nk[n] = nk[n]+1;
                        nbc = nbc + 1;
                        zn[fei] = nz;
                        t = 1;
                        break;
                    }
                }
            }
        }
    }while(t==1);
// Si le modèle a plusieurs zones homogènes il faut 
// relancer le traitement précédent pour chaque zone
    for(i=0; i<nf; i++) if(zn[i]==0) {iz=i; nz=nz+1; goto nzone;}
    delete [] ee;
    delete [] fe;
    delete [] nk;
    delete [] zn;
    std::cout << "\nOn a fait : " << nbc << " couple(s) de HalfEdge\n\n";
    std::cout << "\nIl y a donc : " << 3*nf-2*nbc << " ar\210te(s) non coupl\202e(s)\n\n";
    std::cout << "\nIl y a " << nz << " zone(s) homog\212ne(s) o\227 les faces ont la m\210me orientation\n\n";
    return 0;
}

Conclusion :


Voir mon envoi Affimoff du 24-09-2011 pour afficher un modèle off et http://pgl10.chez.com/format_off.html pour une petite documentation sur le format off. En complément, il y a aussi un petit offviewer venant d'Internet. Toutes remarques, améliorations ou corrections sont les bienvenues.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.