例子主要展示如何改變位置,法向量,顏色和紋理的數據類型。
在之前的指南中我們學習使用標准屬性,通過調用適合的請求方法。不像自定義屬性,用戶通過傳遞數據類型到句柄來指定數據類型(比如,MyMesh::FPropHandleT< int>),標准屬性的數據類型定義為網格特征。我們可以和特征一起定制和擴展網格數據結構。我們通過兩方面做到這一點。
- 改變位置(Position),法向量(Normal),顏色(Color),和紋理坐標(Texture coordinate不知道翻譯對了沒)的數據類型。
- 擴展網格實體,包括頂點,面,邊和Halfedge.
我們開始吧。每一個定制特性應該繼承自默認特性。
struct MyTraits : OpenMesh::DefaultTraits
之前提到的,我們可以為基本數據類型改變基礎數據結構 MyMesh::Point, MyMesh::Normal, MyMesh::Color, and MyMesh::TexCoord。我們可以使用提供的向量類或者我們使用其他類庫提供的類。這里我們簡單的替換Position和Normal的默認類型OpenMesh::Vec3f為OpenMesh::Vec3d
typedef OpenMesh::Vec3d Point; typedef OpenMesh::Vec3d Normal;
(通常,Point和Normal向量最好用相同的標量類型,比如在這里使用double型。不然我們將要必須考慮向量類的實現。)
注意,這些設置覆蓋父類的特征!正如我們通常繼承DefaultTraits,讓我們仔細看看。
實際上,OpenMesh::DefaultTraits僅僅是一個沒有內容的類。它只是為Point,Normal,TexCoord和Color以及一個屬性定義了類型,我們一直隱式地使用它們:
// HalfedgeAttributes( OpenMesh::Attributes::PrevHalfedge );
屬性PrevHalfedge是不同的,因為它沒有控制屬性。然而,它對網格類型的最終結果有個很大的影響,因為它在Halfedge結構中添加了額外的信息。影響有兩點:
快速地訪問前一個Halfedge
添加內存消耗(居然不是一個優點……)
使用這個特點取決於我們的需要。一種情況是我們需要訪問前一個Halfedge非常便利,這是網格的成員變量函數add_face().當前一個Halfedge可用的時候,成員函數的執行時間迅速下降。通常我們希望有這個信息。但是為了節約內存,我們可以輕松的移除這個特性
// HalfedgeAttributes( OpenMesh::Attributes::None );
然后我們需要少於8Byte的空間存儲一條邊,這就節省很多了,通過歐拉方程可以知道V-E+F=2(1-g),對於一個三角形網格,g=0,邊的數目幾乎是三倍的頂點數,既E=3V。
完整的代碼:
#include <iostream> #include <typeinfo> // -------------------- #include <OpenMesh/Core/IO/MeshIO.hh> #include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh> #include <OpenMesh/Core/Geometry/VectorT.hh> #ifndef DOXY_IGNORE_THIS // Define my personal traits struct MyTraits : OpenMesh::DefaultTraits { // Let Point and Normal be a vector of doubles typedef OpenMesh::Vec3d Point; typedef OpenMesh::Vec3d Normal; // Already defined in OpenMesh::DefaultTraits // HalfedgeAttributes( OpenMesh::Attributes::PrevHalfedge ); // Uncomment next line to disable attribute PrevHalfedge // HalfedgeAttributes( OpenMesh::Attributes::None ); // // or // // HalfedgeAttributes( 0 ); }; #endif // Define my mesh with the new traits! typedef OpenMesh::TriMesh_ArrayKernelT<MyTraits> MyMesh; // ------------------------------------------------------------------ main ---- int main(int argc, char **argv) { MyMesh mesh; if (argc!=2) { std::cerr << "Usage: " << argv[0] << " <input>\n"; return 1; } // Just make sure that point element type is double if ( typeid( OpenMesh::vector_traits<MyMesh::Point>::value_type ) != typeid(double) ) { std::cerr << "Ouch! ERROR! Data type is wrong!\n"; return 1; } // Make sure that normal element type is double if ( typeid( OpenMesh::vector_traits<MyMesh::Normal>::value_type ) != typeid(double) ) { std::cerr << "Ouch! ERROR! Data type is wrong!\n"; return 1; } // Add vertex normals as default property (ref. previous tutorial) mesh.request_vertex_normals(); // Add face normals as default property mesh.request_face_normals(); // load a mesh OpenMesh::IO::Options opt; if ( ! OpenMesh::IO::read_mesh(mesh,argv[1], opt)) { std::cerr << "Error loading mesh from file " << argv[1] << std::endl; return 1; } // If the file did not provide vertex normals, then calculate them if ( !opt.check( OpenMesh::IO::Options::VertexNormal ) && mesh.has_face_normals() && mesh.has_vertex_normals() ) { // let the mesh update the normals mesh.update_normals(); } // move all vertices one unit length along it's normal direction for (MyMesh::VertexIter v_it = mesh.vertices_begin(); v_it != mesh.vertices_end(); ++v_it) { std::cout << "Vertex #" << v_it << ": " << mesh.point( v_it ); mesh.set_point( v_it, mesh.point(v_it)+mesh.normal(v_it) ); std::cout << " moved to " << mesh.point( v_it ) << std::endl; } return 0; }