%------------------------------------------------------------------------------- % Document: OpenCTM Developers Manual % Author: Marcus Geelnard % Compile: pdflatex DevelopersManual.tex %------------------------------------------------------------------------------- % Note: You need a LaTeX environment to build this document as PDF. The % recommended way is to install TeX Live (http://www.tug.org/texlive/) and % a decent LaTeX editor (e.g. texmaker, LEd, etc). % % Ubuntu: sudo apt-get install texlive-full % Mac OS X: http://www.tug.org/mactex/ (MacTeX.mpkg.zip) % % To build the PDF document, run pdflatex twice on this .tex file (in order to % correctly build the TOC). %------------------------------------------------------------------------------- % Use the OpenCTM TeX style \input{openctm-tex.sty} % Document properties \author{Marcus Geelnard} \title{OpenCTM Developers Manual} % PDF specific document properties \hypersetup{pdftitle={OpenCTM Developers Manual}} \hypersetup{pdfauthor={Marcus Geelnard}} \hypersetup{pdfkeywords={OpenCTM,manual}} % Document contents \begin{document} %--[ Title page ]--------------------------------------------------------------- \begin{titlepage} \begin{center} ~ \vspace{5cm} \includegraphics[width=10.0cm]{logo.pdf} \vspace{0.4cm} {\large Software Library version 1.0.3} \vspace{1.0cm} {\Large Developers Manual} \vspace{1.5cm} Copyright \copyright \ 2009-2010 Marcus Geelnard \end{center} \end{titlepage} %--[ Table of contents ]-------------------------------------------------------- \tableofcontents %------------------------------------------------------------------------------- \chapter{Introduction} The OpenCTM file format is an open format for storing 3D triangle meshes. One of the main advantages over other similar file formats is its ability to losslessly compress the triangle geometry to a fraction of the corresponding raw data size. This document describes how to use the OpenCTM API to load and save OpenCTM format files. It is mostly written for C/C++ users, but should be useful for other programming languages too, since the concepts and function calls are virtually identical regardless of programming language. For a complete reference to the OpenCTM API, please use the Doxygen generated OpenCTM API Reference, which describes all API functions, types, constants etc. %------------------------------------------------------------------------------- \chapter{Concepts} \section{The OpenCTM API} The OpenCTM API makes it easy to read and write OpenCTM format files. The API is implemented in the form of a software library that an application can be linked to in order to access the OpenCTM API. The software library itself is written in standard, portable C language, but can be used from many other programming languages (writing language bindings for new languages should be fairly straight forward, since the API was written with cross-language portability in mind). \section{The triangle mesh} The triangle mesh, in OpenCTM terms, is managed in a format that is well suited for a modern 3D rendering pipeline, such as OpenGL. At a glance, the OpenCTM mesh has the following properties: \begin{itemize} \item A vertex is a set of attributes that uniquely identify the vertex. This includes: vertex coordinate, normal, UV coordinate(s) and custom vertex attribute(s) (such as color, weight, etc). \item A triangle is described by three vertex indices. \item In the OpenCTM API, these mesh data are treated as arrays (an integer array for the triangle indices, and floating point arrays for the vertex data). \item All vertex data arrays in a mesh must have the same number of elements (for instance, there is exactly one normal associated with each vertex coordinate). \item All mesh data are optional, except for the triangle indices and the vertex coordinates. For instance, it is possible to leave out the normal information. \end{itemize} For an example of the mesh data structure see table \ref{tab:MeshVert} (vertex data) and table \ref{tab:MeshTri} (triangle data). \begin{table}[p] \centering \begin{tabular}{|l|l|l|l|l|l|l|l|}\hline \textbf{Index} & 0 & 1 & 2 & 3 & 4 & \textellipsis & N\\ \hline \textbf{Vertex} & $v_0$ & $v_1$ & $v_2$ & $v_3$ & $v_4$ & \textellipsis & $v_N$\\ \hline \textbf{Normal} & $n_0$ & $n_1$ & $n_2$ & $n_3$ & $n_4$ & \textellipsis & $n_N$\\ \hline \textbf{UVCoord1} & $t1_0$ & $t1_1$ & $t1_2$ & $t1_3$ & $t1_4$ & \textellipsis & $t1_N$\\ \hline \textbf{UVCoord2} & $t2_0$ & $t2_1$ & $t2_2$ & $t2_3$ & $t2_4$ & \textellipsis & $t2_N$\\ \hline \textbf{Attrib1} & $a1_0$ & $a1_1$ & $a1_2$ & $a1_3$ & $a1_4$ & \textellipsis & $a1_N$\\ \hline \textbf{Attrib2} & $a2_0$ & $a2_1$ & $a2_2$ & $a2_3$ & $a2_4$ & \textellipsis & $a2_N$\\ \hline \end{tabular} \caption{Mesh vertex data structure in OpenCTM, for a mesh with normals, two UV coordinates per vertex, and two custom attributes per vertex.} \label{tab:MeshVert} \end{table} \begin{table}[p] \centering \begin{tabular}{|l|l|l|l|l|l|l|l|}\hline \textbf{Triangle} & $tri_0$ & $tri_1$ & $tri_2$ & $tri_3$ & $tri_4$ & \textellipsis & $tri_M$\\ \hline \end{tabular} \caption{Mesh triangle data structure in OpenCTM, where $tri_k$ is a tuple of three vertex indices. For instance, $tri_0=(0, 1, 2)$, $tri_1=(0, 2, 3)$, $tri_2=(3, 5, 4)$, \textellipsis} \label{tab:MeshTri} \end{table} \subsection{Triangle indices} \label{sec:MeshIndices} Each triangle is described by three integers: one vertex index for each corner of the triangle). The triangle index array looks like this: \begin{tabular}{|l|l|l|l|l|l|l|l|l|l|}\hline $tri^0_0$ & $tri^1_0$ & $tri^2_0$ & $tri^0_1$ & $tri^1_1$ & $tri^2_1$ & \textellipsis & $tri^0_M$ & $tri^1_M$ & $tri^2_M$\\ \hline \end{tabular} \textellipsis where $tri^j_k$ is the vertex index for the $j$:th corner of the $k$:th triangle. \subsection{Vertex coordinates} Each vertex coordinate is described by three floating point values: $x$, $y$ and $z$. The vertex coordinate array looks like this: \begin{tabular}{|l|l|l|l|l|l|l|l|l|l|l|}\hline $x_0$ & $y_0$ & $z_0$ & $x_1$ & $y_1$ & $z_1$ & \textellipsis & $x_N$ & $y_N$ & $z_N$\\ \hline \end{tabular} \textellipsis where $x_k$, $y_k$ and $z_k$ are the $x$, $y$ and $z$ coordinates of the $k$:th vertex. \subsection{Normals} Each normal is described by three floating point values: $x$, $y$ and $z$. The normal array looks like this: \begin{tabular}{|l|l|l|l|l|l|l|l|l|l|l|}\hline $x_0$ & $y_0$ & $z_0$ & $x_1$ & $y_1$ & $z_1$ & \textellipsis & $x_N$ & $y_N$ & $z_N$\\ \hline \end{tabular} \textellipsis where $x_k$, $y_k$ and $z_k$ are the $x$, $y$ and $z$ components of the $k$:th normal. \subsection{UV coordinates} A mesh may have several UV maps, where each UV map is described by: \begin{itemize} \item A UV coordinate array. \item A unique UV map name. \item A file name reference (optional). \end{itemize} Each UV coordinate is described by two floating point values: $u$ and $v$. A UV coordinate array looks like this: \begin{tabular}{|l|l|l|l|l|l|l|l|l|l|}\hline $u_0$ & $v_0$ & $u_1$ & $v_1$ & $u_2$ & $v_2$ & \textellipsis & $u_N$ & $v_N$\\ \hline \end{tabular} \textellipsis where $u_k$ and $v_k$ are the $u$ and $v$ components of the $k$:th UV coordinate. \subsection{Custom vertex attributes} A mesh may have several custom vertex attribute maps, where each attribute map is described by: \begin{itemize} \item A vertex attribute array. \item A unique attribute map name. \end{itemize} Each vertex attribute is described by four floating point values: $a$, $b$, $c$ and $d$. An attribute array looks like this: \begin{tabular}{|l|l|l|l|l|l|l|l|l|l|l|l|l|}\hline $a_0$ & $b_0$ & $c_0$ & $d_0$ & $a_1$ & $b_1$ & $c_1$ & $d_1$ & \textellipsis & $a_N$ & $b_N$ & $c_N$ & $d_N$\\ \hline \end{tabular} \textellipsis where $a_k$, $b_k$, $c_k$ and $d_k$ are the four attribute values of the $k$:th attribute. \section{The OpenCTM context} The OpenCTM API uses a \emph{context} for almost all operations (function calls). The context is created and destroyed with the functions ctmNewContext() and ctmFreeContext(), respectively. A program may instantiate any number of contexts, and all OpenCTM function calls are completely thread safe (multiple threads can use the OpenCTM API at the same time), as long as each context instance is handled by a single thread. Each context is fully self contained and independent of other contexts. There are two types of OpenCTM context: \emph{import contexts} and \emph{export contexts}. Import contexts are used for importing OpenCTM files, and export contexts are used for exporting OpenCTM files. The context type is selected when creating the context. %------------------------------------------------------------------------------- \chapter{Compression Methods} The OpenCTM file format supports a few different compression methods, each with its own advantages and disadvantages. The API makes it possible to select which method to use when creating OpenCTM files (the default method is MG1). \section{RAW} The RAW compression method is not really a compression method, since it only stores the data in a raw, uncompressed form. The result is a file with the same size and data format as the in-memory mesh data structure. The RAW method is mostly useful for testing purposes, but can be preferred in certain situations, for instance when file writing speeds and a small memory footprint is more important than minimizing file sizes. Another situation where the RAW method can be useful is when you need an easily parsable binary file format. Usually the OpenCTM API can be used in almost any application, but in some environments, such as certain script languages or data inspecion tools, it can be handy to have access to the raw data. \section{MG1} The MG1 compression method effectively reduces the size of the mesh data by re-coding the connectivity information of the mesh into an easily compressible format. The data is then compressed using LZMA. The floating point data, such as vertex coordinates and normals, is fully preserved in the MG1 method, by simply applying lossless LZMA compression to it. Under typical condititions, the connectivity information is compressed to about two bytes per triangle (17\% of the original size), and vertex data is compressed to about 75\% of the original size. While creating MG1 files can be a relatively slow process (compared to the RAW method, for instance) the reading speed is usually very high, thanks to the fast LZMA decoder and the uncomplicated data format. \section{MG2} The MG2 compression method offers the highest level of compression among the different OpenCTM methods. It uses the same method for compressing connectivity information as the MG1 method, but does a better job at compressing vertex data. Vertex data is converted to a fixed point representation, which allows for efficient, lossless, prediction based data compression algorithms. In short, the MG2 method divides the mesh into small sub-spaces, sorts the data geometrically, and applies delta-prediction to the data, which effectively lowers the data entropy. The re-coded vertex data is then compressed with LZMA. When using the OpenCTM API for creating MG2 files you can trade mesh resolution for compression ratio, and the API provides several functions for controlling the resolution of different vertex attributes independently. Therefor it is usually important to know the resolution requirements for your specific application when using the MG2 method. In some applications, such as games, movies and art, it is important that the 3D model is not visually degraded by compression. In such applications you will typically tune your resolution settings using trial and error, until you find a setting that does not alter the model visually. In other applications, such as CAD/CAM, 3D scanning, calibration, etc, reasonable resolution settings can usually be derived from the limitations of the process in which the model is used. For instance, there is usually no need for nanometer precision in the design of an airplane wing, and there is little use of micrometer resolution in a manufacturing process that can not reproduce features smaller than 0.15 mm. As a side effect of the fact that MG2 produces smaller files than the MG1 method does, loading files is usually faster with the MG2 method than with the MG1 method. Saving files with the MG2 method is about as fast as with the MG1 method. %------------------------------------------------------------------------------- \chapter{Basic Usage} \section{Prerequisites} To use the OpenCTM API, you need to include the OpenCTM include file, like this: \begin{lstlisting} #include \end{lstlisting} You also need to link with the OpenCTM import library. For instance, in MS Visual Studio you can add "openctm.lib" to your Additional Dependencies field in the Linker section. For gcc/g++ or similar compilers, you will typically add -lopenctm to the list of compiler options, for instance: \begin{lstlisting} > g++ -o foo foo.cpp -lopenctm \end{lstlisting} \section{Loading OpenCTM files} Below is a minimal example of how to load an OpenCTM file with the OpenCTM API, in just a few lines of code: \begin{lstlisting} CTMcontext context; CTMuint vertCount, triCount, * indices; CTMfloat * vertices; // Create a new importer context context = ctmNewContext(CTM_IMPORT); // Load the OpenCTM file ctmLoad(context, "mymesh.ctm"); if(ctmGetError(context) == CTM_NONE) { // Access the mesh data vertCount = ctmGetInteger(context, CTM_VERTEX_COUNT); vertices = ctmGetFloatArray(context, CTM_VERTICES); triCount = ctmGetInteger(context, CTM_TRIANGLE_COUNT); indices = ctmGetIntegerArray(context, CTM_INDICES); // Deal with the mesh (e.g. transcode it to our // internal representation) // ... } // Free the context ctmFreeContext(context); \end{lstlisting} \section{Creating OpenCTM files} Below is a minimal example of how to save an OpenCTM file with the OpenCTM API, in just a few lines of code: \begin{lstlisting} void MySaveFile(CTMuint aVertCount, CTMuint aTriCount, CTMfloat * aVertices, CTMuint * aIndices, const char * aFileName) { CTMcontext context; // Create a new exporter context context = ctmNewContext(CTM_EXPORT); // Define our mesh representation to OpenCTM ctmDefineMesh(context, aVertices, aVertCount, aIndices, aTriCount, NULL); // Save the OpenCTM file ctmSave(context, aFileName); // Free the context ctmFreeContext(context); } \end{lstlisting} %------------------------------------------------------------------------------- \chapter{Controlling Compression} When creating OpenCTM files, one of the most important things to control with the API is the compression method. \section{Selecting the compression method} You can select which compression method to use with the ctmCompressionMethod() function. The different options are: \begin{tabular}{|l|l|}\hline \textbf{Name} & \textbf{Description}\\ \hline CTM\_METHOD\_RAW & Use the RAW compression method.\\ \hline CTM\_METHOD\_MG1 & Use the MG1 compression method (default).\\ \hline CTM\_METHOD\_MG2 & Use the MG2 compression method.\\ \hline \end{tabular} For instance, to select the MG2 compression method for a given OpenCTM context, use: \begin{lstlisting} ctmCompressionMethod(context, CTM_METHOD_MG2); \end{lstlisting} \section{Selecting the compression level} You can select which LZMA compression level to use with the ctmCompressionLevel() function. The compression level can be in the range 0-9, where 0 is the fastest compression, and 9 is the best compression. The compression level also affects the amount of memory that is used during compression (anywhere from a few megabytes to several hundred megabytes). \begin{lstlisting} ctmCompressionMethod(context, 4); \end{lstlisting} The default compression level is 1. \section{Selecting fixed point precision} When the MG2 compression method is used, further compression control is provided through the API that deals with the fixed point precision for different vertex attributes. The different attribute precisions that can be controlled are: \begin{tabular}{|l|l|}\hline \textbf{Attribute} & \textbf{API function}\\ \hline Vertex coordinate & ctmVertexPrecision() / ctmVertexPrecisionRel()\\ \hline Normal & ctmNormalPrecision()\\ \hline UV coordinates & ctmUVCoordPrecision()\\ \hline Custom attributes & ctmAttribPrecision()\\ \hline \end{tabular} Reasonable default values for the fixed point precisions are selected by the API unless the corresponding API functions are called. However, the API does not know the requirements for the mesh, which is why it is always a good idea to specify the fixed point precision that is most relevant for your specific mesh. \subsection{Vertex coordinate precision} The vertex coordinate precision can be controlled in two ways: \begin{itemize} \item Absolute precision - ctmVertexPrecision(). \item Relative precision - ctmVertexPrecisionRel(). \end{itemize} You typically specify the absolute precision when you know the properties of the mesh and what is going to be used for (for instance, if it is a product of a measurment procecss, or if it will be used in a manufacturing process). For example, if the vertex coordinate unit is meters, and the precision is specified as $0.001$, the fixed point precision will be 1 mm: \begin{lstlisting} ctmVertexPrecision(context, 0.001); \end{lstlisting} When you do not know much about the mesh, it can be useful to specify the relative precision. The ctmVertexPrecisionRel() function will analyze the mesh to find a useful base measure, which is multiplied by a scaling factor that is given as an argument to the function. The relative precision function uses the average triangle edge length as the base measure. So for example, if you specify $0.01$ as the relative precision, the precision will be 1\% of the average triangle edge length, which is usually a good figure for meshes that will be used in visualization applications: \begin{lstlisting} ctmVertexPrecisionRel(context, 0.01); \end{lstlisting} It should be noted that unlike the ctmVertexPrecision() function, the ctmVertexPrecisionRel() function requires that the mesh has been specified before calling the function. The default vertex coordinate precision is $2^{-10} \approx 0.00098$. \subsection{Normal precision} In the MG2 compression method, each vertex normal is represented in spherical coordinates (the coordinate system is aligned to the average normal of all triangles that connect to the vertex). The precision controls both the angular resolution and the radial resolution (magnitude). For instance, $0.01$ means that the circle is divided into 100 steps, and the normal magnitude is rounded to 2 decimals: \begin{lstlisting} ctmNormalPrecision(context, 0.01); \end{lstlisting} The default normal precision is $2^{-8} \approx 0.0039$. \subsection{UV coordinate precision} UV coordinate precision is specified on a per UV map basis, and gives the absolute precision in UV coordinate space. The effects of different precisions depend on many different things. For instance if the UV map is used for mapping a 2D texture onto the triangle mesh, the resolution of the texture can influence the required UV coordinate precision (e.g. a 4096x4096 texture may require better precision than a 256x256 texture). The resolution of the mesh may also affect the required UV coordinate precision. To specify a resolution of $0.001$ for the UV map $uvMap$, use: \begin{lstlisting} ctmUVCoordPrecision(context, uvMap, 0.001); \end{lstlisting} The default UV coordinate precision is $2^{-12} \approx 0.00024$. \subsection{Custom attribute precision} As with UV coordinates, the precision for custom vertex attributes are specified on a per attribute basis. The precision of a custom attribute depends entirely on the type of attribute. For instance, standard color attributes typically do not require more then eigh bits per component, which means that $1/256$ is a good precision setting (if the value range is $[0,1]$): \begin{lstlisting} ctmAttribPrecision(context, attribMap, 1.0/256.0); \end{lstlisting} For integer values, the precision $1.0$ is a good choice. The default vertex attribute precision is $2^{-8} \approx 0.0039$. %------------------------------------------------------------------------------- \chapter{Error Handling} An error can occur when calling any of the OpenCTM API functions. To check for errors, call the ctmGetError() function, which returns a positive error code if something went wrong, or zero (CTM\_NONE) if no error has occured. See \ref{tab:ErrorCodes} for a list of possible error codes. \begin{table}[p] \centering \begin{tabular}{|l|p{7cm}|}\hline \textbf{Code} & \textbf{Description}\\ \hline CTM\_NONE (zero) & No error has occured (everything is OK).\\ \hline CTM\_INVALID\_CONTEXT & The OpenCTM context was invalid (e.g. NULL).\\ \hline CTM\_INVALID\_ARGUMENT & A function argument was invalid.\\ \hline CTM\_INVALID\_OPERATION & The operation is not allowed.\\ \hline CTM\_INVALID\_MESH & The mesh was invalid (e.g. no vertices).\\ \hline CTM\_OUT\_OF\_MEMORY & Not enough memory to proceed.\\ \hline CTM\_FILE\_ERROR & File I/O error.\\ \hline CTM\_BAD\_FORMAT & File format error (e.g. unrecognized format or corrupted file).\\ \hline CTM\_LZMA\_ERROR & An error occured within the LZMA library.\\ \hline CTM\_INTERNAL\_ERROR & An internal error occured (indicates a bug).\\ \hline CTM\_UNSUPPORTED\_FORMAT\_VERSION & Unsupported file format version.\\ \hline \end{tabular} \caption{OpenCTM error codes.} \label{tab:ErrorCodes} \end{table} The last error code that indicates a failure is stored per OpenCTM context until the ctmGetError() function is called. Calling the function will reset the error state. It is also possible to convert an error code to an error string, using the ctmErrorString() function, which takes an error code as its argument, and returns a constant C string (pointer to a null terminated UTF-8 format character string). %------------------------------------------------------------------------------- \chapter{C++ Extensions} To take better advantage of some of the C++ language features, such as exception handling, a few C++ wrapper classes are availbale through the standard API when compiling a C++ program. As usual, just include "openctm.h", and you will have access to two C++ classes: CTMimporer and CTMexporter. The main differences between the C++ classes and the standard API are: \begin{itemize} \item The C++ classes call ctmNewContext() and ctmFreeContext() in their constructors and destructors respectively, which makes it easier to use the C++ dynamic scope mechanisms (such as exception handling). \item Whenever an OpenCTM error occurs, an exception is thrown. Hence, there is no method corresponding to the ctmGetError() function. \end{itemize} \section{The CTMimporter class} Here is an example of how to use the CTMimporter class in C++: \begin{lstlisting} try { // Create a new OpenCTM importer object CTMimporter ctm; // Load the OpenCTM file ctm.Load("mymesh.ctm"); // Access the mesh data CTMuint vertCount = ctm.GetInteger(CTM_VERTEX_COUNT); CTMfloat * vertices = ctm.GetFloatArray(CTM_VERTICES); CTMuint triCount = ctm.GetInteger(CTM_TRIANGLE_COUNT); CTMuint * indices = ctm.GetIntegerArray(CTM_INDICES); // Deal with the mesh (e.g. transcode it to our // internal representation) // ... } catch(exception &e) { cout << "Error: " << e.what() << endl; } \end{lstlisting} \section{The CTMexporter class} Here is an example of how to use the CTMexporter class in C++: \begin{lstlisting} void MySaveFile(CTMuint aVertCount, CTMuint aTriCount, CTMfloat * aVertices, CTMuint * aIndices, const char * aFileName) { try { // Create a new OpenCTM exporter object CTMexporter ctm; // Define our mesh representation to OpenCTM ctm.DefineMesh(aVertices, aVertCount, aIndices, aTriCount, NULL); // Save the OpenCTM file ctm.Save(aFileName); } catch(exception &e) { cout << "Error: " << e.what() << endl; } } \end{lstlisting} \end{document}