Merge pull request #66 from treeform/dev
Right-hand z-forward coordinate system
This commit is contained in:
commit
7282ae1247
5 changed files with 374 additions and 80 deletions
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
|
@ -6,12 +6,16 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
|
nim-version: ['1.4.0', '1.4.x', 'stable']
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: jiro4989/setup-nim-action@v1
|
- uses: jiro4989/setup-nim-action@v1
|
||||||
|
with:
|
||||||
|
nim-version: ${{ matrix.nim-version }}
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- run: nimble test -y
|
- run: nimble test -y
|
||||||
- run: nimble test --gc:orc -y
|
- run: nimble test --gc:orc -y
|
||||||
- run: nimble test -y -d:vmathObjBased
|
- run: nimble test -y -d:vmathObjBased
|
||||||
|
|
42
README.md
42
README.md
|
@ -1,6 +1,6 @@
|
||||||
<img src="docs/banner.png">
|
<img src="docs/banner.png">
|
||||||
|
|
||||||
# VMath - 2d and 3d vector math.
|
# VMath - 2D and 3D vector math.
|
||||||
|
|
||||||
`nimble install vmath`
|
`nimble install vmath`
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
This library has no dependencies other than the Nim standard library.
|
This library has no dependencies other than the Nim standard library.
|
||||||
|
|
||||||
|
Supports c, cpp and js backend.
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
Your one stop shop for vector math routines for 2d and 3d graphics.
|
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)
|
* [3d Ray Trace Benchmark](tests/bench_raytracer.nim)
|
||||||
* [2d SVG Render Benchmark](https://github.com/treeform/pixie/blob/master/tests/bench_svg.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:
|
# 0.x.x to 1.0.0 vmath breaking changes:
|
||||||
|
|
||||||
* `vec3(v)` no longer works please use `vec3(v.x, v.y, 0)` instead.
|
* `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.
|
Your one stop shop for vector math routines for 2d and 3d graphics.
|
||||||
|
|
||||||
* Pure Nim with no dependencies.
|
* Pure Nim with no dependencies.
|
||||||
* Very similar to GLSL Shader Language with extra stuff.
|
* Very similar to GLSL Shader Language with extra stuff.
|
||||||
* Extensively benchmarked.
|
* Extensively benchmarked.
|
||||||
|
|
||||||
====== =========== ===================================================
|
====== =========== =================================================
|
||||||
Type Constructor Description
|
Type Constructor Description
|
||||||
====== =========== ===================================================
|
====== =========== =================================================
|
||||||
BVec# bvec# a vector of booleans
|
BVec# bvec# vector of booleans
|
||||||
IVec# ivec# a vector of signed integers
|
IVec# ivec# vector of signed integers
|
||||||
UVec# uvec# a vector of unsigned integers
|
UVec# uvec# vector of unsigned integers
|
||||||
Vec# vec# a vector of single-precision floating-point numbers
|
Vec# vec# vector of single-precision floating-point numbers
|
||||||
DVec# dvec# a vector of double-precision floating-point numbers
|
DVec# dvec# vector of double-precision floating-point numbers
|
||||||
====== =========== ===================================================
|
====== =========== =================================================
|
||||||
|
|
||||||
You can use these constructors to make them:
|
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.
|
## Makes v be multiple of n. Rounding to integer quantize by 1.0.
|
||||||
trunc(v / n) * n
|
trunc(v / n) * n
|
||||||
|
|
||||||
proc fractional*[T: SomeFloat](v: T): T =
|
proc fract*[T: SomeFloat](v: T): T =
|
||||||
## Returns fractional part of a number.
|
## Returns fractional part of a number.
|
||||||
## 3.14 -> 0.14
|
## 3.14 -> 0.14
|
||||||
## -3.14 -> 0.14
|
## -3.14 -> 0.14
|
||||||
result = abs(v)
|
result = abs(v)
|
||||||
result = result - trunc(result)
|
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 =
|
proc inversesqrt*[T: float32|float64](v: T): T =
|
||||||
## Returns inverse square root.
|
## Returns inverse square root.
|
||||||
1/sqrt(v)
|
1/sqrt(v)
|
||||||
|
@ -425,19 +427,21 @@ proc mix*[T: SomeFloat](a, b, v: T): T =
|
||||||
v * (b - a) + a
|
v * (b - a) + a
|
||||||
|
|
||||||
proc fixAngle*[T: SomeFloat](angle: T): T =
|
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
|
result = angle
|
||||||
while result > PI:
|
while result > PI:
|
||||||
result -= PI * 2
|
result -= PI * 2
|
||||||
while result < -PI:
|
while result <= -PI:
|
||||||
result += PI * 2
|
result += PI * 2
|
||||||
|
|
||||||
proc angleBetween*[T: SomeFloat](a, b: T): T =
|
proc angleBetween*[T: SomeFloat](a, b: T): T =
|
||||||
## Angle between angle a and angle b.
|
## Angle between angle a and angle b.
|
||||||
|
## All angles assume radians.
|
||||||
fixAngle(b - a)
|
fixAngle(b - a)
|
||||||
|
|
||||||
proc turnAngle*[T: SomeFloat](a, b, speed: T): T =
|
proc turnAngle*[T: SomeFloat](a, b, speed: T): T =
|
||||||
## Move from angle a to angle b with step of v.
|
## Move from angle a to angle b with step of v.
|
||||||
|
## All angles assume radians.
|
||||||
var
|
var
|
||||||
turn = fixAngle(b - a)
|
turn = fixAngle(b - a)
|
||||||
if abs(turn) < speed:
|
if abs(turn) < speed:
|
||||||
|
@ -725,6 +729,11 @@ genMathFn(sqrt)
|
||||||
genMathFn(floor)
|
genMathFn(floor)
|
||||||
genMathFn(ceil)
|
genMathFn(ceil)
|
||||||
genMathFn(abs)
|
genMathFn(abs)
|
||||||
|
genMathFn(trunc)
|
||||||
|
genMathFn(fract)
|
||||||
|
genMathFn(quantize)
|
||||||
|
genMathFn(toRadians)
|
||||||
|
genMathFn(toDegrees)
|
||||||
|
|
||||||
template genBoolFn(fn, op: untyped) =
|
template genBoolFn(fn, op: untyped) =
|
||||||
proc fn*[T](a, b: GVec2[T]): BVec2 =
|
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, 0] = pos.x
|
||||||
a[2, 1] = pos.y
|
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] =
|
proc pos*[T](a: GMat4[T]): GVec3[T] =
|
||||||
|
## Position of the matrix.
|
||||||
gvec3[T](a[3].x, a[3].y, a[3].z)
|
gvec3[T](a[3].x, a[3].y, a[3].z)
|
||||||
|
|
||||||
proc `pos=`*[T](a: var GMat4[T], pos: GVec3[T]) =
|
proc `pos=`*[T](a: var GMat4[T], pos: GVec3[T]) =
|
||||||
|
## See the position of the matrix.
|
||||||
a[3, 0] = pos.x
|
a[3, 0] = pos.x
|
||||||
a[3, 1] = pos.y
|
a[3, 1] = pos.y
|
||||||
a[3, 2] = pos.z
|
a[3, 2] = pos.z
|
||||||
|
@ -1244,7 +1285,8 @@ proc translate*[T](v: GVec3[T]): GMat4[T] =
|
||||||
)
|
)
|
||||||
|
|
||||||
proc rotate*[T](angle: T): GMat3[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
|
let
|
||||||
sin = sin(angle)
|
sin = sin(angle)
|
||||||
cos = cos(angle)
|
cos = cos(angle)
|
||||||
|
@ -1254,22 +1296,126 @@ proc rotate*[T](angle: T): GMat3[T] =
|
||||||
0, 0, 1
|
0, 0, 1
|
||||||
)
|
)
|
||||||
|
|
||||||
proc hrp*[T](m: GMat4[T]): GVec3[T] =
|
proc rotationOnly*[T](a: GMat4[T]): GMat4[T] {.inline.} =
|
||||||
## Return heading, rotation and pivot of a matrix.
|
## Clears the positional component and returns rotation only.
|
||||||
var heading, pitch, roll: float32
|
## Assumes matrix has not been scaled.
|
||||||
if m[1] > 0.998: # singularity at north pole
|
result = a
|
||||||
heading = arctan2(m[2], m[10])
|
result.pos = gvec3(0, 0, 0)
|
||||||
pitch = PI / 2
|
|
||||||
roll = 0
|
proc rotateX*[T](angle: T): GMat4[T] =
|
||||||
elif m[1] < -0.998: # singularity at south pole
|
## Return a rotation matrix around X with angle.
|
||||||
heading = arctan2(m[2], m[10])
|
## All angles assume radians.
|
||||||
pitch = -PI / 2
|
result[0, 0] = 1
|
||||||
roll = 0
|
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:
|
else:
|
||||||
heading = arctan2(-m[8], m[0])
|
# Normal case.
|
||||||
pitch = arctan2(-m[6], m[5])
|
result.y = -arctan2(m[2, 0], m[2, 2])
|
||||||
roll = arcsin(m[4])
|
result.z = -arctan2(m[0, 1], m[1, 1])
|
||||||
gvec3[T](heading, pitch, roll)
|
|
||||||
|
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] =
|
proc frustum*[T](left, right, bottom, top, near, far: T): GMat4[T] =
|
||||||
## Create a frustum matrix.
|
## 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, 2] = T(-(far + near) / fn)
|
||||||
result[3, 3] = 1
|
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.
|
## Create a matrix that would convert eye pos to looking at center.
|
||||||
let
|
let
|
||||||
eyex = eye[0]
|
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, 2] = -(z0 * eyex + z1 * eyey + z2 * eyez)
|
||||||
result[3, 3] = 1
|
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.
|
## Look center from eye with default UP vector.
|
||||||
lookAt(eye, center, gvec3(T(0), 0, 1))
|
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.
|
## Return a rotation matrix with axis and angle.
|
||||||
fromAxisAngle(axis, angle).mat4()
|
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):
|
when defined(release):
|
||||||
{.pop.}
|
{.pop.}
|
||||||
{.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 quantize(-1.23456789, 0.01) ~= -1.23
|
doAssert quantize(-1.23456789, 0.01) ~= -1.23
|
||||||
|
|
||||||
doAssert fractional(0.0) ~= 0.0
|
doAssert fract(0.0) ~= 0.0
|
||||||
doAssert fractional(3.14) ~= 0.14
|
doAssert fract(3.14) ~= 0.14
|
||||||
doAssert fractional(-3.14) ~= 0.14
|
doAssert fract(-3.14) ~= 0.14
|
||||||
doAssert fractional(1.23456789) ~= 0.23456789
|
doAssert fract(1.23456789) ~= 0.23456789
|
||||||
doAssert fractional(-1.23456789) ~= 0.23456789
|
doAssert fract(-1.23456789) ~= 0.23456789
|
||||||
|
|
||||||
doAssert lerp(0.0, 1.0, 0.5) ~= 0.5
|
doAssert mix(0.0, 1.0, 0.5) ~= 0.5
|
||||||
doAssert lerp(0.0, 10.0, 0.5) ~= 5.0
|
doAssert mix(0.0, 10.0, 0.5) ~= 5.0
|
||||||
doAssert lerp(0.0, 100.0, 0.5) ~= 50.0
|
doAssert mix(0.0, 100.0, 0.5) ~= 50.0
|
||||||
doAssert lerp(-1.0, 1.0, 0.25) ~= -0.5
|
doAssert mix(-1.0, 1.0, 0.25) ~= -0.5
|
||||||
doAssert lerp(-10.0, 10.0, 0.25) ~= -5.0
|
doAssert mix(-10.0, 10.0, 0.25) ~= -5.0
|
||||||
doAssert lerp(-100.0, 100.0, 0.25) ~= -50.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, 1.0, 0.5) ~= 0.5
|
||||||
doAssert mix(0.0, 10.0, 0.5) ~= 5.0
|
doAssert mix(0.0, 10.0, 0.5) ~= 5.0
|
||||||
|
@ -863,29 +863,7 @@ block:
|
||||||
)
|
)
|
||||||
|
|
||||||
block:
|
block:
|
||||||
# test quat and matrix lookat
|
# test quat and matrix
|
||||||
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))
|
|
||||||
|
|
||||||
doAssert ortho[float32](-1, 1, 1, -1, -1000, 1000) ~= mat4(
|
doAssert ortho[float32](-1, 1, 1, -1, -1000, 1000) ~= mat4(
|
||||||
1.0, 0.0, 0.0, 0.0,
|
1.0, 0.0, 0.0, 0.0,
|
||||||
0.0, -1.0, 0.0, 0.0,
|
0.0, -1.0, 0.0, 0.0,
|
||||||
|
@ -1045,4 +1023,136 @@ block:
|
||||||
b = a / 2
|
b = a / 2
|
||||||
when compiles(b = a div 2): doAssert false # type mismatch
|
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"
|
echo "test finished successfully"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
version = "1.2.0"
|
version = "2.0.0"
|
||||||
author = "Andre von Houck"
|
author = "Andre von Houck"
|
||||||
description = "Your single stop for vector math routines for 2d and 3d graphics."
|
description = "Your single stop for vector math routines for 2d and 3d graphics."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
Loading…
Reference in a new issue