From f72f9bf022dc69516e0dd7040ae8a278d960772d Mon Sep 17 00:00:00 2001 From: treeform Date: Fri, 7 Aug 2026 08:34:24 -0700 Subject: [PATCH 1/5] Fix orthogonal vectors with mixed signs --- src/vmath.nim | 8 ++++---- tests/tests.nim | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vmath.nim b/src/vmath.nim index a320736..62d3bfe 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -1908,14 +1908,14 @@ proc quatInverse*[T](q: GVec4[T]): GVec4[T] = proc orthogonal*[T](v: GVec3[T]): GVec3[T] = ## Returns orthogonal vector to given vector. let - v = abs(v) + vAbs = abs(v) other: type(v) = - if v.x < v.y: - if v.x < v.z: + if vAbs.x < vAbs.y: + if vAbs.x < vAbs.z: gvec3(T(1), 0, 0) # X_AXIS else: gvec3(T(0), 0, 1) # Z_AXIS - elif v.y < v.z: + elif vAbs.y < vAbs.z: gvec3(T(0), 1, 0) # Y_AXIS else: gvec3(T(0), 0, 1) # Z_AXIS diff --git a/tests/tests.nim b/tests/tests.nim index b035cf2..70f5db6 100644 --- a/tests/tests.nim +++ b/tests/tests.nim @@ -772,10 +772,10 @@ suite "quaternion nlerp": check dist(nl, sl) < 0.01f suite "orthogonal vector": - test "orthogonal is perpendicular to abs(v)": - # orthogonal() uses abs(v) internally, so result is perpendicular to abs(v) + test "orthogonal is perpendicular to v": for v in [vec3(1, 0, 0), vec3(0, 1, 0), vec3(0, 0, 1), - vec3(1, 1, 0), vec3(1, 1, 1), vec3(3, 2, 7)]: + vec3(1, 1, 0), vec3(1, 1, 1), vec3(3, 2, 7), + vec3(0, -1, 1), vec3(-3, 2, -7)]: let o = orthogonal(v) check abs(dot(v, o)) < 1e-5f From 8e4ac13826a90bbe956f753c45b246a3d9e77093 Mon Sep 17 00:00:00 2001 From: treeform Date: Fri, 7 Aug 2026 08:36:56 -0700 Subject: [PATCH 2/5] Document orthogonal vector support --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6b5b644..d4ba883 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ We run identical math operations across vmath, GLSL, [nim-glm](https://github.co | Quaternion to axis-angle | ✅ | ✅ | ✅ | ✅ | ✅ | | Matrix inverse | ✅ | ✅ | ✅ | ✅ | ✅ | | Cross product | ✅ | ✅ | ✅ | ✅ | ✅ | +| Orthogonal vector | ✅ | ❌ | ❌ | ❌ | ✅ | | Slerp | ✅ | ✅ | ✅ | ✅ | ✅ | | fromTwoVectors | ✅ | ✅ | ✅ | ✅ | ✅ | | Quat decomposition (sign) | ✅ | ✅ | ✅ | ✅ | ✅ | @@ -150,6 +151,8 @@ We run identical math operations across vmath, GLSL, [nim-glm](https://github.co ❌ **Element access note**: Jolt does convention differs from vmath's math-style `[row, col]` interpretation it follows the DirectX/HLSL convention. +❌ **Orthogonal vector note**: Jolt Physics provides `GetNormalizedPerpendicular()`. vmath's `orthogonal()` returns a perpendicular vector without normalizing it. GLSL and nim-GLM have no direct equivalent, while gl-matrix only computes one internally as part of `quat.rotationTo()`. + # 2.x.x to 3.0.0 vmath breaking changes: Version `3.0.0` changed rotation to be CCW (counter-clockwise) and updated the quaternion conventions to match GLSL, GLM, gl-matrix. Added a multi-library conformance suite. From 139ad7e5e2b17a21e182ef0abb570c082f4442d9 Mon Sep 17 00:00:00 2001 From: treeform Date: Fri, 7 Aug 2026 08:40:26 -0700 Subject: [PATCH 3/5] Test antiparallel mixed-sign rotation --- tests/tests.nim | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/tests.nim b/tests/tests.nim index 70f5db6..610700b 100644 --- a/tests/tests.nim +++ b/tests/tests.nim @@ -1166,6 +1166,13 @@ suite "fromTwoVectors": q = fromTwoVectors(a, b) check q.mat4() * a ~= b + test "antiparallel mixed-sign vectors": + let + a = normalize(vec3(0, -1, 1)) + b = -a + q = fromTwoVectors(a, b) + check q.mat4() * a ~= b + test "fromTwoVectors fuzz": for _ in 0 ..< 1000: let From ca2cb116642a86f4898bc989a2fa17aac104038a Mon Sep 17 00:00:00 2001 From: treeform Date: Fri, 7 Aug 2026 08:42:04 -0700 Subject: [PATCH 4/5] Compare fromTwoVectors APIs --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d4ba883..fd89ff9 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,8 @@ We run identical math operations across vmath, GLSL, [nim-glm](https://github.co ❌ **Orthogonal vector note**: Jolt Physics provides `GetNormalizedPerpendicular()`. vmath's `orthogonal()` returns a perpendicular vector without normalizing it. GLSL and nim-GLM have no direct equivalent, while gl-matrix only computes one internally as part of `quat.rotationTo()`. +ℹ️ **fromTwoVectors note**: vmath's `fromTwoVectors(a, b)` normalizes its inputs and handles antiparallel vectors with an orthogonal fallback. gl-matrix exposes the equivalent `quat.rotationTo(out, a, b)`, assumes unit inputs, and handles nearly antiparallel vectors. Jolt Physics exposes `Quat::sFromTo(from, to)`, supports arbitrary vector lengths, and handles antiparallel and zero-length inputs. nim-GLM exposes `quat(u, v)`, but assumes unit inputs and does not special-case antiparallel vectors. GLSL has no built-in quaternion or `fromTwoVectors`; the conformance suite supplies its own implementation for comparison. + # 2.x.x to 3.0.0 vmath breaking changes: Version `3.0.0` changed rotation to be CCW (counter-clockwise) and updated the quaternion conventions to match GLSL, GLM, gl-matrix. Added a multi-library conformance suite. From 99599244ecbee75952094cd8f7a7c9ebb9932e28 Mon Sep 17 00:00:00 2001 From: treeform Date: Fri, 7 Aug 2026 08:44:04 -0700 Subject: [PATCH 5/5] Remove verbose fromTwoVectors note --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index fd89ff9..d4ba883 100644 --- a/README.md +++ b/README.md @@ -153,8 +153,6 @@ We run identical math operations across vmath, GLSL, [nim-glm](https://github.co ❌ **Orthogonal vector note**: Jolt Physics provides `GetNormalizedPerpendicular()`. vmath's `orthogonal()` returns a perpendicular vector without normalizing it. GLSL and nim-GLM have no direct equivalent, while gl-matrix only computes one internally as part of `quat.rotationTo()`. -ℹ️ **fromTwoVectors note**: vmath's `fromTwoVectors(a, b)` normalizes its inputs and handles antiparallel vectors with an orthogonal fallback. gl-matrix exposes the equivalent `quat.rotationTo(out, a, b)`, assumes unit inputs, and handles nearly antiparallel vectors. Jolt Physics exposes `Quat::sFromTo(from, to)`, supports arbitrary vector lengths, and handles antiparallel and zero-length inputs. nim-GLM exposes `quat(u, v)`, but assumes unit inputs and does not special-case antiparallel vectors. GLSL has no built-in quaternion or `fromTwoVectors`; the conformance suite supplies its own implementation for comparison. - # 2.x.x to 3.0.0 vmath breaking changes: Version `3.0.0` changed rotation to be CCW (counter-clockwise) and updated the quaternion conventions to match GLSL, GLM, gl-matrix. Added a multi-library conformance suite.