Merge pull request #66 from treeform/dev
Right-hand z-forward coordinate system
This commit is contained in:
commit
7282ae1247
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
|
@ -6,12 +6,16 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
nim-version: ['1.4.0', '1.4.x', 'stable']
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: jiro4989/setup-nim-action@v1
|
||||
with:
|
||||
nim-version: ${{ matrix.nim-version }}
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: nimble test -y
|
||||
- run: nimble test --gc:orc -y
|
||||
- run: nimble test -y -d:vmathObjBased
|
||||
|
|
42
README.md
42
README.md
|
@ -1,6 +1,6 @@
|
|||
<img src="docs/banner.png">
|
||||
|
||||
# VMath - 2d and 3d vector math.
|
||||
# VMath - 2D and 3D vector math.
|
||||
|
||||
`nimble install vmath`
|
||||
|
||||
|
@ -10,6 +10,8 @@
|
|||
|
||||
This library has no dependencies other than the Nim standard library.
|
||||
|
||||
Supports c, cpp and js backend.
|
||||
|
||||
## About
|
||||
|
||||
Your one stop shop for vector math routines for 2d and 3d graphics.
|
||||
|
@ -89,6 +91,44 @@ vmathObjArrayBased ................ 73.968 ms 74.292 ms ±0.631 x100
|
|||
* [3d Ray Trace Benchmark](tests/bench_raytracer.nim)
|
||||
* [2d SVG Render Benchmark](https://github.com/treeform/pixie/blob/master/tests/bench_svg.nim)
|
||||
|
||||
## Zmod - GLSL mod
|
||||
|
||||
GLSL uses a different type of float point mod. Because mod is a Nim keyword please use `zmod` when you need GLSL `mod` behavior.
|
||||
|
||||
## Coordinate System
|
||||
|
||||
Right-hand z-forward coordinate system
|
||||
|
||||
This is the same system used in the GLTF file format.
|
||||
|
||||
> glTF uses a right-handed coordinate system.
|
||||
> glTF defines +Y as up, +Z as forward, and -X as right;
|
||||
> the front of a glTF asset faces +Z.
|
||||
|
||||
[glTF Spec 2.0](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#coordinate-system-and-units)
|
||||
|
||||
## OpenGL matrix column-major notation.
|
||||
|
||||
> [9.005](https://www.opengl.org/archives/resources/faq/technical/transformations.htm) For programming purposes, OpenGL matrices are 16-value arrays with base vectors laid out contiguously in memory. The translation components occupy the 13th, 14th, and 15th elements of the 16-element matrix, where indices are numbered from 1 to 16 as described in section 2.11.2 of the [OpenGL 2.1 Specification](https://registry.khronos.org/OpenGL/specs/gl/glspec21.pdf).
|
||||
>
|
||||
> Sadly, the use of column-major format in the spec and blue book has resulted in endless confusion in the OpenGL programming community. Column-major notation suggests that matrices are not laid out in memory as a programmer would expect.
|
||||
|
||||
OpenGL/GLSL/vmath vs Math/Specification notation:
|
||||
```
|
||||
mat4([
|
||||
a, b, c, 0, | a d g x |
|
||||
d, e, f, 0, | b e h y |
|
||||
g, h, i, 0, | c f i z |
|
||||
x, y, z, 1 | 0 0 0 1 |
|
||||
])
|
||||
```
|
||||
|
||||
# 1.x.x to 2.0.0 vmath breaking changes:
|
||||
* New right-hand-z-forward cordate system and functions that care about
|
||||
coordinate system where moved there.
|
||||
* deprecated `lookAt()` please use `toAngles()`/`fromAngles()` instead.
|
||||
* deprecated `fractional()` use `frac()` instead.
|
||||
|
||||
# 0.x.x to 1.0.0 vmath breaking changes:
|
||||
|
||||
* `vec3(v)` no longer works please use `vec3(v.x, v.y, 0)` instead.
|
||||
|
|
226
src/vmath.nim
226
src/vmath.nim
|
@ -1,22 +1,20 @@
|
|||
##[
|
||||
|
||||
This library has no dependencies other than the Nim standard libarary.
|
||||
|
||||
Your one stop shop for vector math routines for 2d and 3d graphics.
|
||||
|
||||
* Pure Nim with no dependencies.
|
||||
* Very similar to GLSL Shader Language with extra stuff.
|
||||
* Extensively benchmarked.
|
||||
|
||||
====== =========== ===================================================
|
||||
====== =========== =================================================
|
||||
Type Constructor Description
|
||||
====== =========== ===================================================
|
||||
BVec# bvec# a vector of booleans
|
||||
IVec# ivec# a vector of signed integers
|
||||
UVec# uvec# a vector of unsigned integers
|
||||
Vec# vec# a vector of single-precision floating-point numbers
|
||||
DVec# dvec# a vector of double-precision floating-point numbers
|
||||
====== =========== ===================================================
|
||||
====== =========== =================================================
|
||||
BVec# bvec# vector of booleans
|
||||
IVec# ivec# vector of signed integers
|
||||
UVec# uvec# vector of unsigned integers
|
||||
Vec# vec# vector of single-precision floating-point numbers
|
||||
DVec# dvec# vector of double-precision floating-point numbers
|
||||
====== =========== =================================================
|
||||
|
||||
You can use these constructors to make them:
|
||||
|
||||
|
@ -406,13 +404,17 @@ proc quantize*[T: SomeFloat](v, n: T): T =
|
|||
## Makes v be multiple of n. Rounding to integer quantize by 1.0.
|
||||
trunc(v / n) * n
|
||||
|
||||
proc fractional*[T: SomeFloat](v: T): T =
|
||||
proc fract*[T: SomeFloat](v: T): T =
|
||||
## Returns fractional part of a number.
|
||||
## 3.14 -> 0.14
|
||||
## -3.14 -> 0.14
|
||||
result = abs(v)
|
||||
result = result - trunc(result)
|
||||
|
||||
proc fractional*[T: SomeFloat](v: T): T {.deprecated: "Use frac() insetad"} =
|
||||
## Returns fractional part of a number.
|
||||
fract(v)
|
||||
|
||||
proc inversesqrt*[T: float32|float64](v: T): T =
|
||||
## Returns inverse square root.
|
||||
1/sqrt(v)
|
||||
|
@ -425,19 +427,21 @@ proc mix*[T: SomeFloat](a, b, v: T): T =
|
|||
v * (b - a) + a
|
||||
|
||||
proc fixAngle*[T: SomeFloat](angle: T): T =
|
||||
## Make angle be from -PI to PI radians.
|
||||
## Normalize the angle to be from -PI to PI radians.
|
||||
result = angle
|
||||
while result > PI:
|
||||
result -= PI * 2
|
||||
while result < -PI:
|
||||
while result <= -PI:
|
||||
result += PI * 2
|
||||
|
||||
proc angleBetween*[T: SomeFloat](a, b: T): T =
|
||||
## Angle between angle a and angle b.
|
||||
## All angles assume radians.
|
||||
fixAngle(b - a)
|
||||
|
||||
proc turnAngle*[T: SomeFloat](a, b, speed: T): T =
|
||||
## Move from angle a to angle b with step of v.
|
||||
## All angles assume radians.
|
||||
var
|
||||
turn = fixAngle(b - a)
|
||||
if abs(turn) < speed:
|
||||
|
@ -725,6 +729,11 @@ genMathFn(sqrt)
|
|||
genMathFn(floor)
|
||||
genMathFn(ceil)
|
||||
genMathFn(abs)
|
||||
genMathFn(trunc)
|
||||
genMathFn(fract)
|
||||
genMathFn(quantize)
|
||||
genMathFn(toRadians)
|
||||
genMathFn(toDegrees)
|
||||
|
||||
template genBoolFn(fn, op: untyped) =
|
||||
proc fn*[T](a, b: GVec2[T]): BVec2 =
|
||||
|
@ -970,10 +979,42 @@ proc `pos=`*[T](a: var GMat3[T], pos: GVec2[T]) =
|
|||
a[2, 0] = pos.x
|
||||
a[2, 1] = pos.y
|
||||
|
||||
proc forward*[T](a: GMat4[T]): GVec3[T] {.inline.} =
|
||||
## Vector facing +Z.
|
||||
result.x = a[2, 0]
|
||||
result.y = a[2, 1]
|
||||
result.z = a[2, 2]
|
||||
|
||||
proc back*[T](a: GMat4[T]): GVec3[T] {.inline.} =
|
||||
## Vector facing -Z.
|
||||
-a.forward()
|
||||
|
||||
proc left*[T](a: GMat4[T]): GVec3[T] {.inline.} =
|
||||
## Vector facing +X.
|
||||
result.x = a[0, 0]
|
||||
result.y = a[0, 1]
|
||||
result.z = a[0, 2]
|
||||
|
||||
proc right*[T](a: GMat4[T]): GVec3[T] {.inline.} =
|
||||
## Vector facing -X.
|
||||
-a.left()
|
||||
|
||||
proc up*[T](a: GMat4[T]): GVec3[T] {.inline.} =
|
||||
## Vector facing +Y.
|
||||
result.x = a[1, 0]
|
||||
result.y = a[1, 1]
|
||||
result.z = a[1, 2]
|
||||
|
||||
proc down*[T](a: GMat4[T]): GVec3[T] {.inline.} =
|
||||
## Vector facing -X.
|
||||
-a.up()
|
||||
|
||||
proc pos*[T](a: GMat4[T]): GVec3[T] =
|
||||
## Position of the matrix.
|
||||
gvec3[T](a[3].x, a[3].y, a[3].z)
|
||||
|
||||
proc `pos=`*[T](a: var GMat4[T], pos: GVec3[T]) =
|
||||
## See the position of the matrix.
|
||||
a[3, 0] = pos.x
|
||||
a[3, 1] = pos.y
|
||||
a[3, 2] = pos.z
|
||||
|
@ -1244,7 +1285,8 @@ proc translate*[T](v: GVec3[T]): GMat4[T] =
|
|||
)
|
||||
|
||||
proc rotate*[T](angle: T): GMat3[T] =
|
||||
## Create a rotation matrix by an angle.
|
||||
## Create a 2D rotation matrix by an angle.
|
||||
## All angles assume radians.
|
||||
let
|
||||
sin = sin(angle)
|
||||
cos = cos(angle)
|
||||
|
@ -1254,22 +1296,126 @@ proc rotate*[T](angle: T): GMat3[T] =
|
|||
0, 0, 1
|
||||
)
|
||||
|
||||
proc hrp*[T](m: GMat4[T]): GVec3[T] =
|
||||
## Return heading, rotation and pivot of a matrix.
|
||||
var heading, pitch, roll: float32
|
||||
if m[1] > 0.998: # singularity at north pole
|
||||
heading = arctan2(m[2], m[10])
|
||||
pitch = PI / 2
|
||||
roll = 0
|
||||
elif m[1] < -0.998: # singularity at south pole
|
||||
heading = arctan2(m[2], m[10])
|
||||
pitch = -PI / 2
|
||||
roll = 0
|
||||
proc rotationOnly*[T](a: GMat4[T]): GMat4[T] {.inline.} =
|
||||
## Clears the positional component and returns rotation only.
|
||||
## Assumes matrix has not been scaled.
|
||||
result = a
|
||||
result.pos = gvec3(0, 0, 0)
|
||||
|
||||
proc rotateX*[T](angle: T): GMat4[T] =
|
||||
## Return a rotation matrix around X with angle.
|
||||
## All angles assume radians.
|
||||
result[0, 0] = 1
|
||||
result[0, 1] = 0
|
||||
result[0, 2] = 0
|
||||
result[0, 3] = 0
|
||||
|
||||
result[1, 0] = 0
|
||||
result[1, 1] = cos(angle)
|
||||
result[1, 2] = -sin(angle)
|
||||
result[1, 3] = 0
|
||||
|
||||
result[2, 0] = 0
|
||||
result[2, 1] = sin(angle)
|
||||
result[2, 2] = cos(angle)
|
||||
result[2, 3] = 0
|
||||
|
||||
result[3, 0] = 0
|
||||
result[3, 1] = 0
|
||||
result[3, 2] = 0
|
||||
result[3, 3] = 1
|
||||
|
||||
proc rotateY*[T](angle: T): GMat4[T] =
|
||||
## Return a rotation matrix around Y with angle.
|
||||
## All angles assume radians.
|
||||
result[0, 0] = cos(angle)
|
||||
result[0, 1] = 0
|
||||
result[0, 2] = sin(angle)
|
||||
result[0, 3] = 0
|
||||
|
||||
result[1, 0] = 0
|
||||
result[1, 1] = 1
|
||||
result[1, 2] = 0
|
||||
result[1, 3] = 0
|
||||
|
||||
result[2, 0] = -sin(angle)
|
||||
result[2, 1] = 0
|
||||
result[2, 2] = cos(angle)
|
||||
result[2, 3] = 0
|
||||
|
||||
result[3, 0] = 0
|
||||
result[3, 1] = 0
|
||||
result[3, 2] = 0
|
||||
result[3, 3] = 1
|
||||
|
||||
proc rotateZ*[T](angle: T): GMat4[T] =
|
||||
## Return a rotation matrix around Z with angle.
|
||||
## All angles assume radians.
|
||||
result[0, 0] = cos(angle)
|
||||
result[0, 1] = -sin(angle)
|
||||
result[0, 2] = 0
|
||||
result[0, 3] = 0
|
||||
|
||||
result[1, 0] = sin(angle)
|
||||
result[1, 1] = cos(angle)
|
||||
result[1, 2] = 0
|
||||
result[1, 3] = 0
|
||||
|
||||
result[2, 0] = 0
|
||||
result[2, 1] = 0
|
||||
result[2, 2] = 1
|
||||
result[2, 3] = 0
|
||||
|
||||
result[3, 0] = 0
|
||||
result[3, 1] = 0
|
||||
result[3, 2] = 0
|
||||
result[3, 3] = 1
|
||||
|
||||
proc toAngles*[T](a: GVec3[T]): GVec3[T] =
|
||||
## Given a 3d vector, computes Euler angles: pitch and yaw
|
||||
## pitch (x rotation)
|
||||
## yaw (y rotation)
|
||||
## roll (z rotation) - always 0 in vector case
|
||||
## All angles assume radians.
|
||||
if a == gvec3[T](T(0), T(0), T(0)):
|
||||
return
|
||||
let
|
||||
yaw = -arctan2(a.x, a.z)
|
||||
pitch = -arctan2(sqrt(a.x*a.x + a.z*a.z), a.y) + T(PI/2)
|
||||
result.x = pitch.fixAngle
|
||||
result.y = yaw.fixAngle
|
||||
|
||||
proc toAngles*[T](origin, target: GVec3[T]): GVec3[T] =
|
||||
## Gives Euler angles from origin to target
|
||||
## pitch (x rotation)
|
||||
## yaw (y rotation)
|
||||
## roll (z rotation) - always 0 in vector case
|
||||
## All angles assume radians.
|
||||
toAngles(target - origin)
|
||||
|
||||
proc toAngles*[T](m: GMat4[T]): GVec3[T] =
|
||||
## Decomposes the matrix into Euler angles:
|
||||
## pitch (x rotation)
|
||||
## yaw (y rotation)
|
||||
## roll (z rotation)
|
||||
## Assumes matrix has not been scaled.
|
||||
## All angles assume radians.
|
||||
result.x = arcsin(m[2,1])
|
||||
if result.x > PI/2:
|
||||
# Degenerate case over north pole.
|
||||
result.y = arctan2(m[0, 2], m[0, 0])
|
||||
elif result.x < -PI/2:
|
||||
# Degenerate case over south pole.
|
||||
result.y = arctan2(m[0, 2], m[0, 0])
|
||||
else:
|
||||
heading = arctan2(-m[8], m[0])
|
||||
pitch = arctan2(-m[6], m[5])
|
||||
roll = arcsin(m[4])
|
||||
gvec3[T](heading, pitch, roll)
|
||||
# Normal case.
|
||||
result.y = -arctan2(m[2, 0], m[2, 2])
|
||||
result.z = -arctan2(m[0, 1], m[1, 1])
|
||||
|
||||
proc fromAngles*[T](a: GVec3[T]): GMat4[T] =
|
||||
## Takes a vector containing Euler angles and returns a matrix.
|
||||
## All angles assume radians.
|
||||
rotateY(a.y) * rotateX(a.x) * rotateZ(a.z)
|
||||
|
||||
proc frustum*[T](left, right, bottom, top, near, far: T): GMat4[T] =
|
||||
## Create a frustum matrix.
|
||||
|
@ -1332,7 +1478,10 @@ proc ortho*[T](left, right, bottom, top, near, far: T): GMat4[T] =
|
|||
result[3, 2] = T(-(far + near) / fn)
|
||||
result[3, 3] = 1
|
||||
|
||||
proc lookAt*[T](eye, center, up: GVec3[T]): GMat4[T] =
|
||||
proc lookAt*[T](eye, center, up: GVec3[T]): GMat4[T]
|
||||
{.deprecated: "Wrong coordinate system. " &
|
||||
"Use toAngles(eye, center).fromAngles() instead to get " &
|
||||
"right-handed-z-forward coordinate system".} =
|
||||
## Create a matrix that would convert eye pos to looking at center.
|
||||
let
|
||||
eyex = eye[0]
|
||||
|
@ -1413,7 +1562,10 @@ proc lookAt*[T](eye, center, up: GVec3[T]): GMat4[T] =
|
|||
result[3, 2] = -(z0 * eyex + z1 * eyey + z2 * eyez)
|
||||
result[3, 3] = 1
|
||||
|
||||
proc lookAt*[T](eye, center: GVec3[T]): GMat4[T] =
|
||||
proc lookAt*[T](eye, center: GVec3[T]): GMat4[T]
|
||||
{.deprecated: "Wrong coordinate system. " &
|
||||
"Use toAngles(eye, center).fromAngles() instead to get " &
|
||||
"right-handed-z-forward coordinate system".} =
|
||||
## Look center from eye with default UP vector.
|
||||
lookAt(eye, center, gvec3(T(0), 0, 1))
|
||||
|
||||
|
@ -1598,18 +1750,6 @@ proc rotate*[T](angle: T, axis: GVec3[T]): GMat4[T] =
|
|||
## Return a rotation matrix with axis and angle.
|
||||
fromAxisAngle(axis, angle).mat4()
|
||||
|
||||
proc rotateX*[T](angle: T): GMat4[T] =
|
||||
## Return a rotation matrix around X with angle.
|
||||
fromAxisAngle(gvec3[T](1, 0, 0), angle).mat4()
|
||||
|
||||
proc rotateY*[T](angle: T): GMat4[T] =
|
||||
## Return a rotation matrix around Y with angle.
|
||||
fromAxisAngle(gvec3[T](0, 1, 0), angle).mat4()
|
||||
|
||||
proc rotateZ*[T](angle: T): GMat4[T] =
|
||||
## Return a rotation matrix around Z with angle.
|
||||
fromAxisAngle(gvec3[T](0, 0, 1), angle).mat4()
|
||||
|
||||
when defined(release):
|
||||
{.pop.}
|
||||
{.pop.}
|
||||
|
|
178
tests/test.nim
178
tests/test.nim
|
@ -53,18 +53,18 @@ block:
|
|||
doAssert quantize(1.23456789, 0.01) ~= 1.23
|
||||
doAssert quantize(-1.23456789, 0.01) ~= -1.23
|
||||
|
||||
doAssert fractional(0.0) ~= 0.0
|
||||
doAssert fractional(3.14) ~= 0.14
|
||||
doAssert fractional(-3.14) ~= 0.14
|
||||
doAssert fractional(1.23456789) ~= 0.23456789
|
||||
doAssert fractional(-1.23456789) ~= 0.23456789
|
||||
doAssert fract(0.0) ~= 0.0
|
||||
doAssert fract(3.14) ~= 0.14
|
||||
doAssert fract(-3.14) ~= 0.14
|
||||
doAssert fract(1.23456789) ~= 0.23456789
|
||||
doAssert fract(-1.23456789) ~= 0.23456789
|
||||
|
||||
doAssert lerp(0.0, 1.0, 0.5) ~= 0.5
|
||||
doAssert lerp(0.0, 10.0, 0.5) ~= 5.0
|
||||
doAssert lerp(0.0, 100.0, 0.5) ~= 50.0
|
||||
doAssert lerp(-1.0, 1.0, 0.25) ~= -0.5
|
||||
doAssert lerp(-10.0, 10.0, 0.25) ~= -5.0
|
||||
doAssert lerp(-100.0, 100.0, 0.25) ~= -50.0
|
||||
doAssert mix(0.0, 1.0, 0.5) ~= 0.5
|
||||
doAssert mix(0.0, 10.0, 0.5) ~= 5.0
|
||||
doAssert mix(0.0, 100.0, 0.5) ~= 50.0
|
||||
doAssert mix(-1.0, 1.0, 0.25) ~= -0.5
|
||||
doAssert mix(-10.0, 10.0, 0.25) ~= -5.0
|
||||
doAssert mix(-100.0, 100.0, 0.25) ~= -50.0
|
||||
|
||||
doAssert mix(0.0, 1.0, 0.5) ~= 0.5
|
||||
doAssert mix(0.0, 10.0, 0.5) ~= 5.0
|
||||
|
@ -863,29 +863,7 @@ block:
|
|||
)
|
||||
|
||||
block:
|
||||
# test quat and matrix lookat
|
||||
doAssert lookAt(vec3(1, 2, 3), vec3(0, 0, 0)).quat ~=
|
||||
quat(
|
||||
0.07232953608036041,
|
||||
0.3063928484916687,
|
||||
0.9237624406814575,
|
||||
0.2180707305669785
|
||||
)
|
||||
doAssert lookAt(vec3(0, 0, 0), vec3(0, 0, 0)).quat ~= quat(0.0, 0.0, 0.0, 1.0)
|
||||
doAssert lookAt(vec3(1, 0, 0), vec3(0, 0, 0)).quat ~= quat(0.5, 0.5, 0.5, 0.5)
|
||||
doAssert lookAt(vec3(0, 1, 0), vec3(0, 0, 0)).quat ~=
|
||||
quat(
|
||||
0.0,
|
||||
0.7071067690849304,
|
||||
0.7071067690849304,
|
||||
0.0
|
||||
)
|
||||
doAssert lookAt(vec3(0, 0, 1), vec3(0, 0, 0)).quat ~= quat(0.0, 0.0, 0.0, 1.0)
|
||||
|
||||
let
|
||||
a = lookAt(vec3(1, 2, 3), vec3(0, 0, 0))
|
||||
b = lookAt(dvec3(1, 2, 3), dvec3(0, 0, 0))
|
||||
|
||||
# test quat and matrix
|
||||
doAssert ortho[float32](-1, 1, 1, -1, -1000, 1000) ~= mat4(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, -1.0, 0.0, 0.0,
|
||||
|
@ -1045,4 +1023,136 @@ block:
|
|||
b = a / 2
|
||||
when compiles(b = a div 2): doAssert false # type mismatch
|
||||
|
||||
proc eq(a, b: Vec3): bool =
|
||||
const epsilon = 0.001
|
||||
return abs(angleBetween(a.x, b.x)) < epsilon and
|
||||
abs(angleBetween(a.y, b.y)) < epsilon and
|
||||
abs(angleBetween(a.z, b.z)) < epsilon
|
||||
|
||||
const PI = PI.float32
|
||||
|
||||
block:
|
||||
# test Euler angles from a vector
|
||||
doAssert vec3(0, 0, 0).toAngles.eq vec3(0f, 0f, 0f)
|
||||
doAssert vec3(0, 0, 1).toAngles.eq vec3(0f, 0f, 0f) # forward
|
||||
doAssert vec3(0, 0, -1).toAngles.eq vec3(0f, PI, 0f) # back
|
||||
doAssert vec3(-1, 0, 0).toAngles.eq vec3(0f, PI/2, 0f) # right
|
||||
doAssert vec3(1, 0, 0).toAngles.eq vec3(0f, -PI/2, 0f) # left
|
||||
doAssert vec3(0, 1, 0).toAngles.eq vec3(PI/2, 0f, 0f) # up
|
||||
doAssert vec3(0, -1, 0).toAngles.eq vec3(-PI/2, 0f, 0f) # down
|
||||
|
||||
block:
|
||||
# test Euler angles from a matrix
|
||||
doAssert translate(vec3(0, 0, 0)).toAngles.eq vec3(0f, 0f, 0f)
|
||||
doAssert rotateX(0f).toAngles.eq vec3(0f, 0f, 0f) # forward
|
||||
doAssert rotateY(PI).toAngles.eq vec3(0f, -PI, 0f) # back
|
||||
doAssert rotateY(PI/2).toAngles.eq vec3(0f, PI/2, 0f) # back
|
||||
doAssert rotateY(-PI/2).toAngles.eq vec3(0f, -PI/2, 0f) # back
|
||||
doAssert rotateX(PI/2).toAngles.eq vec3(PI/2, 0f, 0f) # up
|
||||
doAssert rotateX(-PI/2).toAngles.eq vec3(-PI/2, 0f, 0f) # down
|
||||
doAssert rotateZ(PI/2).toAngles.eq vec3(0f, 0f, PI/2) # tilt right
|
||||
doAssert rotateZ(-PI/2).toAngles.eq vec3(0f, 0f, -PI/2) # tilt left
|
||||
|
||||
doAssert mat4().toAngles.eq vec3(0, 0, 0)
|
||||
|
||||
doAssert rotateX(10.toRadians()).toAngles.eq vec3(10.toRadians(), 0, 0)
|
||||
doAssert rotateY(10.toRadians()).toAngles.eq vec3(0, 10.toRadians(), 0)
|
||||
doAssert rotateZ(10.toRadians()).toAngles.eq vec3(0, 0, 10.toRadians())
|
||||
doAssert rotateX(89.toRadians()).toAngles.eq vec3(89.toRadians(), 0, 0)
|
||||
doAssert rotateY(89.toRadians()).toAngles.eq vec3(0, 89.toRadians(), 0)
|
||||
doAssert rotateZ(89.toRadians()).toAngles.eq vec3(0, 0, 89.toRadians())
|
||||
doAssert rotateX(90.toRadians()).toAngles.eq vec3(90.toRadians(), 0, 0)
|
||||
doAssert rotateY(90.toRadians()).toAngles.eq vec3(0, 90.toRadians(), 0)
|
||||
doAssert rotateZ(90.toRadians()).toAngles.eq vec3(0, 0, 90.toRadians())
|
||||
doAssert rotateX(90.toRadians()).toAngles.eq vec3(90.toRadians(), 0, 0)
|
||||
doAssert rotateY(90.toRadians()).toAngles.eq vec3(0, 90.toRadians(), 0)
|
||||
doAssert rotateZ(-90.toRadians()).toAngles.eq vec3(0, 0, -90.toRadians())
|
||||
doAssert rotateY(180.toRadians()).toAngles.eq vec3(0, -180.toRadians(), 0)
|
||||
doAssert rotateZ(180.toRadians()).toAngles.eq vec3(0, 0, 180.toRadians())
|
||||
doAssert rotateY(-180.toRadians()).toAngles.eq vec3(0, 180.toRadians(), 0)
|
||||
doAssert rotateZ(-180.toRadians()).toAngles.eq vec3(0, 0, 180.toRadians())
|
||||
|
||||
block:
|
||||
# Euler angles fuzzing tests.
|
||||
|
||||
# Test fromAngles with and without roll have same forward
|
||||
for i in 0 .. 1000:
|
||||
let
|
||||
xr = rand(-89.9f .. 89.9f).toRadians
|
||||
yr = rand(-180 .. 180).toRadians
|
||||
zr = rand(-180 .. 180).toRadians
|
||||
a = vec3(xr, yr, zr)
|
||||
b = vec3(xr, yr, 0f)
|
||||
ma = fromAngles(a)
|
||||
mb = fromAngles(b)
|
||||
|
||||
doAssert ma.forward() ~= mb.forward()
|
||||
|
||||
# Test forward/back, right/left, up/down combos
|
||||
for i in 0 .. 1000:
|
||||
let
|
||||
xr = rand(-89.9f .. 89.9f).toRadians
|
||||
yr = rand(-180 .. 180).toRadians
|
||||
zr = rand(-180 .. 180).toRadians
|
||||
b = vec3(xr, yr, zr)
|
||||
m = fromAngles(b)
|
||||
|
||||
doAssert m.forward() ~= m * vec3(0, 0, 1)
|
||||
doAssert m.back() ~= m * vec3(0, 0, -1)
|
||||
|
||||
doAssert m.right() ~= m * vec3(-1, 0, 0)
|
||||
doAssert m.left() ~= m * vec3(1, 0, 0)
|
||||
|
||||
doAssert m.up() ~= m * vec3(0, 1, 0)
|
||||
doAssert m.down() ~= m * vec3(0, -1, 0)
|
||||
|
||||
# Test non-polar and non-rotated cases
|
||||
for i in 0 .. 1000:
|
||||
let
|
||||
xr = rand(-89.9f .. 89.9f).toRadians
|
||||
yr = rand(-180 .. 180).toRadians
|
||||
zr = 0f
|
||||
b = vec3(xr, yr, zr)
|
||||
m = fromAngles(b)
|
||||
a = m.toAngles()
|
||||
doAssert a.eq(b)
|
||||
|
||||
# Test non-polar cases
|
||||
for i in 0 .. 1000:
|
||||
let
|
||||
xr = rand(-89.9f .. 89.9f).toRadians
|
||||
yr = rand(-180 .. 180).toRadians
|
||||
zr = rand(-180 .. 180).toRadians
|
||||
b = vec3(xr, yr, zr)
|
||||
m = fromAngles(b)
|
||||
a = m.toAngles()
|
||||
doAssert a.eq(b)
|
||||
|
||||
# Test polar and non-rotated cases
|
||||
for i in 0 .. 1000:
|
||||
let
|
||||
xr = sample([-90, 90]).toRadians
|
||||
yr = rand(-180 .. 180).toRadians
|
||||
zr = 0f
|
||||
b = vec3(xr, yr, zr)
|
||||
m = fromAngles(b)
|
||||
a = m.toAngles()
|
||||
doAssert a.eq(b)
|
||||
|
||||
# Test polar and crazy rotated cases
|
||||
for i in 0 .. 1000:
|
||||
let
|
||||
xr = sample([-90, 90]).toRadians
|
||||
yr = rand(-180 .. 180).toRadians
|
||||
zr = rand(-180 .. 180).toRadians
|
||||
b = vec3(xr, yr, zr)
|
||||
m = fromAngles(b)
|
||||
a = m.toAngles()
|
||||
|
||||
doAssert abs(angleBetween(a.x, b.x)) < 0.001
|
||||
if xr > 0:
|
||||
doAssert abs(angleBetween(a.y, b.y + b.z)) < 0.001
|
||||
else:
|
||||
doAssert abs(angleBetween(a.y, b.y - b.z)) < 0.001
|
||||
|
||||
echo "test finished successfully"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
version = "1.2.0"
|
||||
version = "2.0.0"
|
||||
author = "Andre von Houck"
|
||||
description = "Your single stop for vector math routines for 2d and 3d graphics."
|
||||
license = "MIT"
|
||||
|
|
Loading…
Reference in a new issue