OpenCTM is a file format and a C library for compressing 3D triangle meshes, written by Marcus Geelnard and released under the zlib/libpng license. This repository is the fork that MeshLib uses for reading and writing .ctm files.
It started from upstream v1.0.3 (2010-01-15), the last release on sourceforge.net/projects/openctm. The original README with the upstream change log and credits is kept verbatim as README.txt; the file format specification is doc/FormatSpecification.pdf.
- Library only. Only
lib/(the OpenCTM sources plus the bundled LZMA SDK) is kept. Thectmconvandctmviewertools, the language bindings, the Maya/Blender plugins and the upstream Makefiles are removed. - CMake build.
CMakeLists.txtbuilds a sharedOpenCTMtarget and installs headers, the library and anOpenCTMConfig.cmakepackage, so consumers usefind_package(OpenCTM CONFIG)and linkOpenCTM::OpenCTM. Installation rules can be disabled with-DOPENCTM_INSTALL=OFF. - Export macro. Symbol export is driven by CMake's
OpenCTM_EXPORTSdefine instead of upstream'sOPENCTM_BUILD;OPENCTM_STATICstill selects static linkage. On non-Windows builds every API function carries default visibility.
ctmRearrangeTriangles(context, on)(new). Upstream always sorts triangles before MG1/MG2 compression to improve the ratio, which changes their order in the file. The fork keeps that as the default, but turning it off preserves the caller's triangle order. In both modes each triangle is still rotated so that its smallest vertex index comes first, which does not change the mesh.- Compression progress and cancellation.
ctmSaveCustomgained a third parameter, aCTMcompressProgresscallback(size_t pos, size_t total, void * userData)that is called during LZMA compression with the number of bytes processed so far. Returning a non-zero value aborts the save, which then fails withCTM_LZMA_ERROR. PassNULLfor the upstream behaviour.ctmSaveis unchanged. ExistingctmSaveCustomcall sites need the extraNULLargument. - Empty meshes. A mesh with zero vertices and/or zero triangles can be defined, saved and loaded. Upstream rejects such data with
CTM_INVALID_MESHon save andCTM_BAD_FORMATon load, so files of empty meshes written by this fork are not readable by the original library. - C++ wrapper.
openctmpp.husesnoexceptinstead of the removedthrow()specification.
Fixes for two heap-buffer overflows reachable from ctmLoad on a crafted file, reported by Kamal Sentassi (S9S Security Research) under coordinated disclosure in 2026:
- String lengths (#2). A file-declared string length of
0xFFFFFFFFmademalloc(len + 1)wrap to a zero-size buffer that was then overrun. Such lengths and short reads are rejected, and the loader stops parsing after the first failed read instead of continuing with an error already set.ctmLoadCustomalso clears a stale error from a previous load on the same context. - Vertex and triangle counts (#3). File-declared counts drove
malloc(count * stride)directly, which wraps on a 32-bitsize_t(wasm32) and in the 32-bit intermediates of the temporary buffers. Counts aboveUINT_MAX / 16(268,435,455) are rejected withCTM_BAD_FORMAT.
Other small fixes: compiler warnings, line endings, typos, and a Windows install rule for the DLL.
The format itself is unchanged (format version 5). Files written by this fork load in upstream OpenCTM and vice versa, with the two exceptions noted above: empty meshes, and meshes above the count limit.
zlib/libpng, unchanged from upstream. See LICENSE.txt.