Add configurable normal generation with compile-time dispatch
Summary
This PR adds compile-time configuration for normal generation behavior in the surface nets algorithm, allowing users to choose between no normals, raw (unnormalized) normals, or normalized normals based on their specific use case.
Changes
-
New trait-based configuration system: Introduces
NormalModetrait with three implementations:-
NoNormals: Skips normal calculation entirely for maximum performance -
RawNormals: Generates unnormalized normals (maintains existing behavior) -
NormalizedNormals: Generates unit-length normals ready for rendering
-
-
New function:
surface_nets_with_config<T, S, N>()accepts a normal mode type parameter -
Backward compatibility: Existing
surface_nets()function unchanged, calls the new function withRawNormals -
Zero runtime cost: Normal mode is resolved at compile time, no performance impact
Usage
use fast_surface_nets::{surface_nets_with_config, NoNormals, RawNormals, NormalizedNormals};
// Skip normals entirely - fastest option
surface_nets_with_config::<_, _, NoNormals>(&sdf, &shape, min, max, &mut buffer);
// Generate raw normals - existing behavior, backward compatible
surface_nets_with_config::<_, _, RawNormals>(&sdf, &shape, min, max, &mut buffer);
// Generate normalized normals - ready for direct use in rendering
surface_nets_with_config::<_, _, NormalizedNormals>(&sdf, &shape, min, max, &mut buffer);
// while still 100% compatible with the old method (that is using RawNormals)
surface_nets(&sdf, &shape, min, max, &mut buffer);
Design Decisions
Trait-based approach vs const generics
Initially considered using const generics (const NORMAL_MODE: u8), but this results in awkward call-site syntax:
// Ugly syntax required with const generics
surface_nets_with_config::<_, _, {RawNormals}>(...);
The trait approach provides much cleaner ergonomics while maintaining compile-time dispatch.
Compile-time vs feature flags
A feature-flag approach was considered but rejected because:
- Inflexibility: Users couldn't mix different normal modes in the same binary
- Ham-fisted control: The "winning" normalization behavior would be determined globally at compile time
- Library constraints: Downstream libraries couldn't choose their own normal mode if their dependencies had different requirements
The trait-based approach allows fine-grained, per-call control while maintaining zero runtime overhead.
Performance Impact
-
NoNormals: Eliminates all normal calculation overhead -
RawNormals: Identical performance to existing implementation -
NormalizedNormals: Adds CPU/simd normalization cost but eliminates need for post-processing
All modes benefit from compile-time dispatch - no runtime branching or vtable lookups.
Breaking Changes
None. All existing code continues to work unchanged. The original surface_nets() function maintains its exact behavior and signature.
Notes
Possibly the "eval-max-plane" feature flag could be implemented in a similar way.