You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Breaking changes
Remove the deprecated float_traits type: use related_types
Remove the deprecated implicit coercion to scalarf, scalard, vector4f, and vector4d: use the functions with the as_scalar and as_vector suffixes
Code that forward declares struct qvf, qvd, qvsf, qvsd, qvvf, or qvvd does not compile: include rtm/fwd.h instead
Changes in behavior
Symmetric and banker's rounding keep the sign of the input as std::round and std::nearbyint do, for example round(-0.0) = -0.0
Use a float64 default threshold in quat_near_identity (double): 2.82729655E-7 rad (an error of 1.0E-14 on [w]) replaces the float32 value of 0.00284714461 rad. To keep the previous behavior, give 0.00284714461 as the threshold
vqv_slerp and the deprecated qvv_slerp interpolate the scale with eerp instead of lerp. If the two scales of a component have a different sign or if one scale is zero, they use lerp for that component. vqv_lerp does not change
Use FMA on ARM64 for float64 multiply-add and quaternion multiplication, as float32 does: the results can differ in the last bits
Deprecations
Rename qvf and qvd to vqf and vqd, and the qv functions to vq: the name follows the order of operations (rotation, then translation). The new headers are rtm/vqf.h and rtm/vqd.h
Rename qvsf and qvsd to vqsf and vqsd, and the qvs functions to vqs. The new headers are rtm/vqsf.h and rtm/vqsd.h
Rename qvvf and qvvd to vqvf and vqvd, and the qvv functions to vqv. The new headers are rtm/vqvf.h and rtm/vqvd.h
Deprecate the qv, qvs, and qvv names: the types, the functions, matrix_from_qv, matrix_from_qvs, matrix_from_qvv, the _arg types, related_types::qv, related_types::qvs, related_types::qvv, and the headers rtm/qvf.h, rtm/qvd.h, rtm/qvsf.h, rtm/qvsd.h, rtm/qvvf.h, and rtm/qvvd.h. Use the vq, vqs, and vqv names. To be removed in 2.5
New features
Add the VQM transform type, vqmf and vqmd: a rotation quaternion, a translation, and a 3x3 scale/shear matrix. Unlike VQV, VQM can hold shear and forms a group. Include rtm/vqmf.h or rtm/vqmd.h. rtm/types.h declares vqmf and vqmd, rtm/type_traits.h adds related_types::vqm, and the functions take the vqmf_arg and vqmd_arg types, with the same choices as the 3x4 matrices
vqm_from_matrix finds the rotation with the Gram-Schmidt process, and the scale/shear part contains the reflection, if any. vqm_slerp interpolates the scale (the diagonal of the scale/shear part) with eerp and the remaining shear with lerp. vqm_lerp interpolates the scale/shear part with lerp. A vqm_inverse overload takes a fallback scale
A VQM uses 4 registers (64 bytes for vqmf): the [w] lanes of the translation and of the first two rows hold the third row of the scale/shear part. Use the vqm accessors, not the members
A VQM stores a flag in the m22 slot when the scale/shear part is exactly the identity: a quiet NaN with a fixed payload. vqm_identity, vqm_set, vqm_set_scale, and vqm_set_scale_shear set the flag. vqm_mul, vqm_mul_point3, vqm_mul_vector3, vqm_inverse, vqm_lerp, and vqm_slerp then skip the scale/shear part, and keep the flag when the result has the identity scale/shear part. When the scale/shear part of the second input of vqm_mul is the identity, the result has the exact scale/shear part of the first input
Add scalar_exp, scalar_log, vector_exp, and vector_log for float32 and float64. All the code paths use the same approximations (Cephes): for the same input, the scalar and the vector functions give the same result, and the result does not depend on the C runtime library. The results can differ in the last bit on ARM because the code uses FMA there. The largest error that we measured is 1 ulp for float32 and 2 ulp for float64
Add scalar_eerp and vector_eerp for float32 and float64: the exponential interpolation start * (end / start)^alpha
Add quat_add, quat_select, and the quat_mul overloads that multiply a quaternion by a scalar
Add mask_true, mask_false, and mask_not
Add vector_not_equal, vector_any_not_equal, and vector_all_not_equal, with their 2 and 3 component variants
Add vector_distance_squared3
Add the transpose macros RTM_MATRIXF_TRANSPOSE_2X2, RTM_MATRIXD_TRANSPOSE_2X2, RTM_MATRIXD_TRANSPOSE_3X4, RTM_MATRIXD_TRANSPOSE_4X3, and RTM_MATRIXD_TRANSPOSE_4X4
Add rtm/config.h: the header lists and documents the macros that configure RTM
Add RTM_NO_BIT_CAST to not use std::bit_cast with C++20 and later. RTM also defines it when the <bit> header is not available
Add an API reference site: Doxygen reads the /// comments, and MkDocs makes the site for develop and for each release
Platforms and tools
Add support for WebAssembly SIMD128: compile with -msimd128 to enable it, RTM then defines RTM_WASM128_INTRINSICS
Add support for ARM64EC with MSVC: RTM defines RTM_ARCH_ARM64 and uses the ARM64 NEON code path, and make.py adds -cpu arm64ec
Add support for VS2026: add vs2026 and vs2026-clang to make.py and add RTM_COMPILER_MSVC_2026
Use __vectorcall with Clang on Windows, as with MSVC
Add GCC 14, GCC 15, and Clang 19 to 23 to make.py
Add support for native ARMv7 builds on Linux to make.py
Add support for Android builds on OS X and Linux to make.py: the build uses the CMake toolchain of the NDK and -unit_test runs the unit tests on the device with adb
Compile the Emscripten unit tests with WebAssembly SIMD by default in make.py, use -nosimd for the scalar code path
Add the -analyze action to make.py to run the clang static analyzer on the public headers
Update Google Benchmark to 1.9.5
Fixes
Disable fast math with float_control pragma under Clang where the target supports it
Fix symmetric rounding for values near 0.5 and for odd values above 2^23 (float32) or 2^52 (float64) on all code paths, for example round(0.49999997) = 0.0
Fix float64 floor, ceil, and symmetric rounding for values between 2^31 and 2^52 with SSE2 without SSE4
Fix floor and ceil with SSE2 without SSE4 to keep the sign of a zero result, as the other code paths do
Fix float32 banker's rounding for values of 2^31 and larger without SIMD
Fix compilation errors with GCC on ARMv7 in vector floor, vector ceil, and the 2x2 transpose macro
Fix vector floor and ceil on ARMv7 to keep the sign of a zero result, as the other code paths do
Fix quat_slerp when the two quaternions are equal or nearly equal: the result was NaN. When the angle is near zero, the function uses the weights (1.0 - alpha) and alpha
Fix quat_mul (quatd, scalard) with SSE2: the function used lane 1 of the scalar to multiply [y] and [w]
Fix the argument type of the second input of vq_slerp, vqs_lerp, vqs_lerp_no_scale, vqs_slerp, quat_slerp (double), and vector_neg_mul_sub (vector4d, scalard)
Fix vector_cast and quat_cast (float64 to float32) with SSE2 and MSVC 19.34 to 19.44: when [xy] and [zw] are the same constant, the compiler sets [zw] to 0
Fix vector_atan2 (vector4d) with SSE2 and MSVC 19.51 for x64: the compiler uses the [xy] result for [zw]
Fix vector_mix and matrix_from_scale with MSVC 19.44 and 19.51 for x86 with AVX: when the mix takes only [x] from a zero input, the compiler removes the blend
Fix an internal compiler error with MSVC 19.44 for arm64 in some functions that call quat_set_w (quatd, double)
Detect the C++ version from _MSVC_LANG first with MSVC: by default, MSVC sets __cplusplus to an old value
Performance and accuracy
Use float64 SIMD on ARM64: vector4d, quatd, mask4d, and mask4q are now two 2-lane registers, as with SSE2
Optimize many float64 functions with SSE2, vector_mix with SSE2 and NEON, the unaligned loads and stores, and vector_cross3 with NEON
Add the SSE4 code path to vector4d ceil
Use the ARM64 round instruction (half away from zero) for vector and scalar symmetric rounding
Pass vqd and vqsd by register on ARM64
Pass vqvd, matrix3x3d, matrix3x4d, and matrix4x4d by reference on ARM64 to remove a copy to the stack
Pass the third vector4d, quatd, mask4d, and mask4q argument by register with __vectorcall
Pass the vqd or vqsd of vq_mul_point3, vqs_mul_point3, and vqs_mul_point3_no_scale by register with __vectorcall
Pass the vector4d of matrix_set_axis (double) by register with __vectorcall and on ARM64
Pass the vector4d of vqv_inverse (double) and qvv_inverse (double) by register with __vectorcall
Use the type arg aliases in the casts from double to float types
Use a square root and a division instead of the reciprocal square root estimate in float32 quat_normalize and quat_lerp with SSE2: the results are more accurate and do not change between Intel and AMD CPUs
Use a division instead of the reciprocal estimate in vector_reciprocal on ARMv7: the results are more accurate
Use a square root instead of two reciprocals in quat_from_matrix
Divide by the length instead of a multiplication by the reciprocal length to normalize float32 vectors and quaternions with SSE2 and WebAssembly SIMD: the code is faster and more accurate. This applies to vector_normalize, vector_normalize2, vector_normalize3, quat_normalize, quat_lerp, quat_to_axis_angle, quat_get_axis, and the functions that call them
Divide by the sine of the half angle instead of a multiplication by its reciprocal in float32 quat_slerp with SSE2 and WebAssembly SIMD
Multiply by the reciprocal length instead of two vector divisions in float64 quat_normalize: the code is faster
Use one division instead of a reciprocal and a multiplication to scale the result of quat_rotation_log
Compute sin(half angle) in quat_slerp as sqrt((1 - cos) * (1 + cos)) instead of sqrt(1 - cos^2): the result is more accurate for small angles. For example, the float32 error for an angle of 0.0336 rad is 1.0E-7 instead of 3.0E-5
Replicate a lane with one NEON dup instruction in float32 vector_dup_x/y/z/w and when vector_mix uses the same lane four times: on ARMv7, GCC previously moved the lane through a general purpose register or used a table lookup (vtbl)