rf-geom / ridgeforge Public

questions

Questions that arrive more than once

Answers are short on purpose. Where a question has a real answer elsewhere in the documentation, this page points at it rather than paraphrasing it badly.

Design decisions

Why write another mesh library?

Because the middle of the pipeline kept being the part we vendored from four places at once: one package for loading, another for welding, a third for decimation with a different vertex layout, and glue code converting between them that was slower than any of the three. RidgeForge is those steps against one data structure. It does not try to be a geometry framework, and the scope section says so in writing.

Is it header-only?

No. It compiles to a static library, which keeps build times sane for the common case. If you need a single file - for a build system that is not CMake, or for a project that vendors everything as source - the rf_amalgamate target produces one, at the cost of the optional parallel path. See amalgamation.

Why no exceptions by default?

Two reasons that have nothing to do with taste. Several callers embed this in code built with exceptions disabled entirely, and a geometry failure is usually an expected outcome rather than an exceptional one: a file that will not parse, a mesh that is not manifold. Result<T> makes those paths visible at every call site. Allocation failure is genuinely exceptional and is the one case RF_USE_EXCEPTIONS restores.

Why 32-bit indices?

Halving the triangle array is worth a great deal when the array is the hot structure in every loop, and the cap of about 2.1 billion vertices has not yet constrained anyone we know of. A mesh at that size needs 25 GB for the arrays alone, which is a different kind of problem from the one this library solves. The change and its reasoning are in 0.4.0.

Why not GPU?

The collapse loop is sequential and latency bound, which is the opposite of what a GPU rewards. The parts that would map well - quadric accumulation, the intersection descent - are already not the bottleneck. A GPU version would be a different library with different output, not a faster flag on this one.

Using it

May I use it in a commercial product?

Yes. The licence is MIT: use, modify and ship it, including in closed-source software, keeping the copyright notice with the source or in your documentation. There is no attribution requirement in a user interface and no obligation to publish changes.

Does decimation preserve my texture coordinates?

Partly, since 0.9.4. Normals and one UV set are interpolated at merged vertices. A vertex on a texture seam legitimately has two different UV values and will end up with one of them, which shows as a thin smear across the seam. If that matters, collect the seam vertices and pass them in locked_vertices. The general caveat is under what is not promised.

Why is my decimated mesh bigger than the target I asked for?

Almost always locks. With preserve_ridges on, a tessellated CAD part can have nearly every vertex on a sharp edge, and once the unlocked ones are gone decimation stops. The report tells you: target_reached is false while the status is still Ok. Raise ridge_angle_deg for coarse levels rather than switching preservation off, and see the LOD example.

The same mesh gives different results after upgrading. Is that a bug?

Not by itself. Determinism is promised within a version, not across versions: any change to the collapse metric or the guards reorders the collapses. The changelog marks the releases where that happened. If the output changed between two runs of the same build, that is a real bug and we want to hear about it.

How do I get a watertight mesh out of a scan?

There is no single call, and any library that offers one is hiding a decision from you. The sequence that usually works is in the cleaning example: weld, drop degenerates, remove speckle, orient, fill holes, then check with Stats::closed and a self-intersection search. If holes remain, they are larger than your max_loop_edges, and raising it blindly will fill the mouth of a cup.

What it will not do

Can it handle quad or polygon meshes?

No. Everything assumes triangles, including the quadric accumulation and every adjacency structure. Triangulate before loading; the OBJ reader rejects faces with more than three vertices rather than silently fanning them, because a naive fan on a non-convex quad is wrong often enough to matter.

Booleans, offset surfaces, voxel remeshing?

None of them, and none are planned. Each is a project of its own with its own robustness problem. The exact predicates here would help with booleans, which is why the question comes up, but sharing a predicate is not the hard part of that work.

Is there a Python binding?

Not an official one. The API is small enough that a binding is an afternoon, and we would rather not maintain a second surface whose release cadence must track this one. A stable C ABI is the more likely step, because every binding generator can consume it; there is no date for it.

Can it read glTF, FBX or a CAD exchange format?

No. Three formats are enough to get triangles in and out, and every additional one brings a scene graph, a material model or a parser with a long history of security issues. Convert outside the library.

The project itself

How is it tested?

411 cases, run on three compilers before each tag, plus a sanitizer configuration for address and undefined behaviour. Four fixtures are deliberately pathological: a zero-area fan, two shells meeting at a single vertex, a strip with inconsistent winding and a cube with one face reversed. One test runs every operation twice and compares the outputs byte for byte, which is how the determinism claim stays honest. Details in tests and sanitizers.

How often are releases cut?

When there is something worth shipping. The gaps in the changelog run from six weeks to five months, and that is unlikely to change. Nothing is released without the full test matrix passing and the benchmark table being re-measured.

Can I send a patch?

Yes, by mail to dev@ridgeforge.dev, as a diff against the current release. Small and focused gets read quickly; a rewrite of the decimator will get a slow and probably disappointing answer. A failing test that demonstrates the problem is worth more than the fix, because it survives the next refactor.

What happens if the project stops?

The licence lets anyone continue it, the code has no dependencies to rot, and the test suite is in the same tarball. That is the whole succession plan, and for a library this size it is an honest one.