From 10b77fe50a44cde152b2432ec956e4440bfa24a5 Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 08:49:34 -0700 Subject: [PATCH 01/35] gl matrix and jolt match. --- experiments/dump_glmatrix.txt | 198 +++++++++++++++++++++++++++++ experiments/dump_jolt.nim | 231 ++++++++++++++++++++++++++++++++++ experiments/dump_jolt.txt | 198 +++++++++++++++++++++++++++++ experiments/dump_vmath.nim | 197 +++++++++++++++++++++++++++++ experiments/dump_vmath.txt | 198 +++++++++++++++++++++++++++++ 5 files changed, 1022 insertions(+) create mode 100644 experiments/dump_glmatrix.txt create mode 100644 experiments/dump_jolt.nim create mode 100644 experiments/dump_jolt.txt create mode 100644 experiments/dump_vmath.nim create mode 100644 experiments/dump_vmath.txt diff --git a/experiments/dump_glmatrix.txt b/experiments/dump_glmatrix.txt new file mode 100644 index 0000000..80d59b3 --- /dev/null +++ b/experiments/dump_glmatrix.txt @@ -0,0 +1,198 @@ +== dump == +notes: matrices are printed in raw in-memory order, four scalars per line + +== matrix constructors and composition == +identity: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +matrix_a: +[ + +001.000 +005.000 +009.000 +013.000 + +002.000 +006.000 +010.000 +014.000 + +003.000 +007.000 +011.000 +015.000 + +004.000 +008.000 +012.000 +016.000 +] +matrix_b: +[ + +000.500 +001.500 -003.000 +000.000 + -001.000 +000.750 +004.000 +001.000 + +002.000 -000.500 +001.250 -001.500 + +000.250 +002.000 -002.500 +003.000 +] +scale: +[ + +002.000 +000.000 +000.000 +000.000 + +000.000 +003.000 +000.000 +000.000 + +000.000 +000.000 +004.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +translate: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +010.000 +020.000 +030.000 +001.000 +] +rotate_x: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +000.799 +000.602 +000.000 + +000.000 -000.602 +000.799 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_y: +[ + +000.921 +000.000 +000.391 +000.000 + +000.000 +001.000 +000.000 +000.000 + -000.391 +000.000 +000.921 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_z: +[ + +000.326 +000.946 +000.000 +000.000 + -000.946 +000.326 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation = rotate_z * rotate_y * rotate_x: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform = translate * rotate_z * rotate_y * rotate_x * scale: +[ + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 + +010.000 +020.000 +030.000 +001.000 +] + +== matrix multiply == +lhs: +[ + +001.000 +005.000 +009.000 +013.000 + +002.000 +006.000 +010.000 +014.000 + +003.000 +007.000 +011.000 +015.000 + +004.000 +008.000 +012.000 +016.000 +] +rhs: +[ + +000.500 +001.500 -003.000 +000.000 + -001.000 +000.750 +004.000 +001.000 + +002.000 -000.500 +001.250 -001.500 + +000.250 +002.000 -002.500 +003.000 +] +lhs * rhs: +[ + -005.500 -009.500 -013.500 -017.500 + +016.500 +035.500 +054.500 +073.500 + -001.250 +003.750 +008.750 +013.750 + +008.750 +019.750 +030.750 +041.750 +] +rhs * lhs: +[ + +016.750 +026.750 -004.250 +030.500 + +018.500 +030.500 -004.500 +033.000 + +020.250 +034.250 -004.750 +035.500 + +022.000 +038.000 -005.000 +038.000 +] + +== matrix vector multiply == +vec3_input: <+001.250, -002.500, +003.750> +vec4_input: <+001.250, -002.500, +003.750, +001.000> +transform * vec3: <+023.998, +014.529, +037.849> +transform * vec4: <+023.998, +014.529, +037.849, +001.000> +rotate_z * vec3: <+002.771, +000.368, +003.750> +translate * vec3: <+011.250, +017.500, +033.750> + +== quaternion constructors == +quat_identity: <+000.000, +000.000, +000.000, +001.000> +quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> +quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> +quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> +axis_normalized: <+000.267, +000.535, -000.802> +axis_angle_radians: +000.838 +from_axis_angle: <+000.109, +000.217, -000.326, +000.914> +from_axis_angle.mat4: +[ + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion multiply == +quat_x: <+000.317, +000.000, +000.000, +000.948> +quat_y: <+000.000, -000.199, +000.000, +000.980> +quat_z: <+000.000, +000.000, +000.581, +000.814> +quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> +quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> +quat_xy.mat4: +[ + +000.921 -000.235 +000.312 +000.000 + +000.000 +000.799 +000.602 +000.000 + -000.391 -000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +quat_xyz.mat4: +[ + +000.300 +000.679 +000.671 +000.000 + -000.870 +000.482 -000.099 +000.000 + -000.391 -000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion vector rotate == +input: <+001.250, -002.500, +003.750> +quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> +quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> +quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> +quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> +quat_z * input: <+002.771, +000.368, +003.750> + +== matrix quaternion roundtrip == +pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> +pure_rotation: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation.quat.mat4: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library +transform.rotation_only: +[ + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> +axis_mat.quat.mat4: +[ + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== basis directions == +canonical_right: <+001.000, +000.000, +000.000> +canonical_up: <+000.000, +001.000, +000.000> +canonical_forward: <+000.000, +000.000, +001.000> +quat_z.right: <+000.326, +000.946, +000.000> +quat_z.up: <-000.946, +000.326, +000.000> +quat_z.forward: <+000.000, +000.000, +001.000> diff --git a/experiments/dump_jolt.nim b/experiments/dump_jolt.nim new file mode 100644 index 0000000..ab6adcf --- /dev/null +++ b/experiments/dump_jolt.nim @@ -0,0 +1,231 @@ +import + std/[math, os, strutils], + jolty + +const + OutputPath = parentDir(currentSourcePath()) / "dump_jolt.txt" + +proc cleanFloat(value: float32): float32 = + if abs(value) < 0.0000005'f32: + 0'f32 + else: + value + +proc fmt(value: float32): string = + let cleaned = cleanFloat(value) + let sign = if cleaned < 0'f32: "-" else: "+" + + var whole = int(floor(abs(cleaned).float64)) + var frac = int(round((abs(cleaned).float64 - whole.float64) * 1000.0)) + if frac == 1000: + inc whole + frac = 0 + + result = sign + let wholeText = $whole + for _ in wholeText.len ..< 3: + result.add '0' + result.add wholeText + result.add '.' + + let fracText = $frac + for _ in fracText.len ..< 3: + result.add '0' + result.add fracText + +proc appendLine(lines: var seq[string], line = "") = + lines.add line + +proc dumpScalar(lines: var seq[string], label: string, value: float32) = + lines.appendLine(label & ": " & fmt(value)) + +proc dumpVec3(lines: var seq[string], label: string, value: JoltFloat3) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ">") + +proc dumpVec4(lines: var seq[string], label: string, value: JoltFloat4) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ", " & fmt(value.w) & ">") + +proc dumpQuat(lines: var seq[string], label: string, value: JoltQuat) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ", " & fmt(value.w) & ">") + +proc dumpMat4(lines: var seq[string], label: string, value: JoltMat44) = + lines.appendLine(label & ":") + lines.appendLine("[") + for offset in countup(0, 12, 4): + lines.appendLine( + " " & + fmt(value.m[offset + 0]) & " " & + fmt(value.m[offset + 1]) & " " & + fmt(value.m[offset + 2]) & " " & + fmt(value.m[offset + 3]) + ) + lines.appendLine("]") + +proc heading(lines: var seq[string], title: string) = + if lines.len > 0: + lines.appendLine() + lines.appendLine("== " & title & " ==") + +proc multiply(a, b: JoltMat44): JoltMat44 = + var ma = a + var mb = b + joltMat44Multiply(addr ma, addr mb) + +proc multiply(m: JoltMat44, v: JoltFloat3): JoltFloat3 = + var mm = m + joltMat44MultiplyVec3(addr mm, v.x, v.y, v.z) + +proc multiply(m: JoltMat44, v: JoltFloat4): JoltFloat4 = + var mm = m + joltMat44MultiplyVec4(addr mm, v.x, v.y, v.z, v.w) + +proc getRotationSafe(m: JoltMat44): JoltMat44 = + var mm = m + joltMat44GetRotationSafe(addr mm) + +proc getQuaternion(m: JoltMat44): JoltQuat = + var mm = m + joltMat44GetQuaternion(addr mm) + +proc quatRotate(axisX, axisY, axisZ, angle: float32): JoltQuat = + joltQuatRotation(axisX, axisY, axisZ, angle) + +proc quatMultiply(a, b: JoltQuat): JoltQuat = + joltQuatMultiply(a.x, a.y, a.z, a.w, b.x, b.y, b.z, b.w) + +proc quatRotateVec3(q: JoltQuat, v: JoltFloat3): JoltFloat3 = + joltQuatRotateVec3(q.x, q.y, q.z, q.w, v.x, v.y, v.z) + +proc main() = + let physics = newPhysicsSystem( + maxBodies = 1024, + maxPairs = 1024, + maxConstraints = 1024, + ) + + var lines: seq[string] + + let + angleA = 37'f32 * PI.float32 / 180'f32 + angleB = -23'f32 * PI.float32 / 180'f32 + angleC = 71'f32 * PI.float32 / 180'f32 + + matA = joltMat44FromMemory( + 1.0, 5.0, 9.0, 13.0, + 2.0, 6.0, 10.0, 14.0, + 3.0, 7.0, 11.0, 15.0, + 4.0, 8.0, 12.0, 16.0 + ) + matB = joltMat44FromMemory( + 0.5, 1.5, -3.0, 0.0, + -1.0, 0.75, 4.0, 1.0, + 2.0, -0.5, 1.25, -1.5, + 0.25, 2.0, -2.5, 3.0 + ) + vecA = JoltFloat3(x: 1.25, y: -2.5, z: 3.75) + vecB = JoltFloat4(x: 1.25, y: -2.5, z: 3.75, w: 1.0) + let + scaleM = joltMat44Scale(2.0, 3.0, 4.0) + translateM = joltMat44Translation(10.0, 20.0, 30.0) + rotateXM = joltMat44RotationX(angleA) + rotateYM = joltMat44RotationY(angleB) + rotateZM = joltMat44RotationZ(angleC) + pureRotationM = multiply(multiply(rotateZM, rotateYM), rotateXM) + let + axis = joltVec3Normalize(1.0, 2.0, -3.0) + axisAngle = 48'f32 * PI.float32 / 180'f32 + axisQuat = quatRotate(axis.x, axis.y, axis.z, axisAngle) + axisMat = joltMat44FromQuat(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + transformM = multiply(multiply(multiply(multiply(translateM, rotateZM), rotateYM), rotateXM), scaleM) + let + quatX = quatRotate(1.0, 0.0, 0.0, angleA) + quatY = quatRotate(0.0, 1.0, 0.0, angleB) + quatZ = quatRotate(0.0, 0.0, 1.0, angleC) + quatXY = quatMultiply(quatX, quatY) + quatXYZ = quatMultiply(quatXY, quatZ) + let + rotationOnlyM = getRotationSafe(transformM) + basisRight = JoltFloat3(x: 1.0, y: 0.0, z: 0.0) + basisUp = JoltFloat3(x: 0.0, y: 1.0, z: 0.0) + basisForward = JoltFloat3(x: 0.0, y: 0.0, z: 1.0) + + lines.heading("dump") + lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") + + lines.heading("matrix constructors and composition") + lines.dumpMat4("identity", joltMat44Identity()) + lines.dumpMat4("matrix_a", matA) + lines.dumpMat4("matrix_b", matB) + lines.dumpMat4("scale", scaleM) + lines.dumpMat4("translate", translateM) + lines.dumpMat4("rotate_x", rotateXM) + lines.dumpMat4("rotate_y", rotateYM) + lines.dumpMat4("rotate_z", rotateZM) + lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) + lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) + + lines.heading("matrix multiply") + lines.dumpMat4("lhs", matA) + lines.dumpMat4("rhs", matB) + lines.dumpMat4("lhs * rhs", multiply(matA, matB)) + lines.dumpMat4("rhs * lhs", multiply(matB, matA)) + + lines.heading("matrix vector multiply") + lines.dumpVec3("vec3_input", vecA) + lines.dumpVec4("vec4_input", vecB) + lines.dumpVec3("transform * vec3", multiply(transformM, vecA)) + lines.dumpVec4("transform * vec4", multiply(transformM, vecB)) + lines.dumpVec3("rotate_z * vec3", multiply(rotateZM, vecA)) + lines.dumpVec3("translate * vec3", multiply(translateM, vecA)) + + lines.heading("quaternion constructors") + lines.dumpQuat("quat_identity", joltQuatIdentity()) + lines.dumpQuat("quat_rotate_x", quatX) + lines.dumpQuat("quat_rotate_y", quatY) + lines.dumpQuat("quat_rotate_z", quatZ) + lines.dumpVec3("axis_normalized", axis) + lines.dumpScalar("axis_angle_radians", axisAngle) + lines.dumpQuat("from_axis_angle", axisQuat) + lines.dumpMat4("from_axis_angle.mat4", axisMat) + + lines.heading("quaternion multiply") + lines.dumpQuat("quat_x", quatX) + lines.dumpQuat("quat_y", quatY) + lines.dumpQuat("quat_z", quatZ) + lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) + lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) + lines.dumpMat4("quat_xy.mat4", joltMat44FromQuat(quatXY.x, quatXY.y, quatXY.z, quatXY.w)) + lines.dumpMat4("quat_xyz.mat4", joltMat44FromQuat(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w)) + + lines.heading("quaternion vector rotate") + lines.dumpVec3("input", vecA) + lines.dumpVec3("quat_rotate(quat_x, input)", quatRotateVec3(quatX, vecA)) + lines.dumpVec3("quat_rotate(quat_y, input)", quatRotateVec3(quatY, vecA)) + lines.dumpVec3("quat_rotate(quat_z, input)", quatRotateVec3(quatZ, vecA)) + lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotateVec3(axisQuat, vecA)) + lines.dumpVec3("quat_z * input", quatRotateVec3(quatZ, vecA)) + + lines.heading("matrix quaternion roundtrip") + let pureRotationQuat = getQuaternion(pureRotationM) + lines.dumpQuat("pure_rotation.quat", pureRotationQuat) + lines.dumpMat4("pure_rotation", pureRotationM) + lines.dumpMat4("pure_rotation.quat.mat4", joltMat44FromQuat(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w)) + lines.appendLine("transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library") + lines.dumpMat4("transform.rotation_only", rotationOnlyM) + let axisMatQuat = axisQuat + lines.dumpQuat("axis_mat.quat", axisMatQuat) + lines.dumpMat4("axis_mat.quat.mat4", joltMat44FromQuat(axisMatQuat.x, axisMatQuat.y, axisMatQuat.z, axisMatQuat.w)) + + lines.heading("basis directions") + lines.dumpVec3("canonical_right", basisRight) + lines.dumpVec3("canonical_up", basisUp) + lines.dumpVec3("canonical_forward", basisForward) + lines.dumpVec3("quat_z.right", quatRotateVec3(quatZ, basisRight)) + lines.dumpVec3("quat_z.up", quatRotateVec3(quatZ, basisUp)) + lines.dumpVec3("quat_z.forward", quatRotateVec3(quatZ, basisForward)) + + writeFile(OutputPath, lines.join("\n") & "\n") + physics.destroy() + echo "Wrote ", OutputPath + +main() diff --git a/experiments/dump_jolt.txt b/experiments/dump_jolt.txt new file mode 100644 index 0000000..80d59b3 --- /dev/null +++ b/experiments/dump_jolt.txt @@ -0,0 +1,198 @@ +== dump == +notes: matrices are printed in raw in-memory order, four scalars per line + +== matrix constructors and composition == +identity: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +matrix_a: +[ + +001.000 +005.000 +009.000 +013.000 + +002.000 +006.000 +010.000 +014.000 + +003.000 +007.000 +011.000 +015.000 + +004.000 +008.000 +012.000 +016.000 +] +matrix_b: +[ + +000.500 +001.500 -003.000 +000.000 + -001.000 +000.750 +004.000 +001.000 + +002.000 -000.500 +001.250 -001.500 + +000.250 +002.000 -002.500 +003.000 +] +scale: +[ + +002.000 +000.000 +000.000 +000.000 + +000.000 +003.000 +000.000 +000.000 + +000.000 +000.000 +004.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +translate: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +010.000 +020.000 +030.000 +001.000 +] +rotate_x: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +000.799 +000.602 +000.000 + +000.000 -000.602 +000.799 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_y: +[ + +000.921 +000.000 +000.391 +000.000 + +000.000 +001.000 +000.000 +000.000 + -000.391 +000.000 +000.921 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_z: +[ + +000.326 +000.946 +000.000 +000.000 + -000.946 +000.326 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation = rotate_z * rotate_y * rotate_x: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform = translate * rotate_z * rotate_y * rotate_x * scale: +[ + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 + +010.000 +020.000 +030.000 +001.000 +] + +== matrix multiply == +lhs: +[ + +001.000 +005.000 +009.000 +013.000 + +002.000 +006.000 +010.000 +014.000 + +003.000 +007.000 +011.000 +015.000 + +004.000 +008.000 +012.000 +016.000 +] +rhs: +[ + +000.500 +001.500 -003.000 +000.000 + -001.000 +000.750 +004.000 +001.000 + +002.000 -000.500 +001.250 -001.500 + +000.250 +002.000 -002.500 +003.000 +] +lhs * rhs: +[ + -005.500 -009.500 -013.500 -017.500 + +016.500 +035.500 +054.500 +073.500 + -001.250 +003.750 +008.750 +013.750 + +008.750 +019.750 +030.750 +041.750 +] +rhs * lhs: +[ + +016.750 +026.750 -004.250 +030.500 + +018.500 +030.500 -004.500 +033.000 + +020.250 +034.250 -004.750 +035.500 + +022.000 +038.000 -005.000 +038.000 +] + +== matrix vector multiply == +vec3_input: <+001.250, -002.500, +003.750> +vec4_input: <+001.250, -002.500, +003.750, +001.000> +transform * vec3: <+023.998, +014.529, +037.849> +transform * vec4: <+023.998, +014.529, +037.849, +001.000> +rotate_z * vec3: <+002.771, +000.368, +003.750> +translate * vec3: <+011.250, +017.500, +033.750> + +== quaternion constructors == +quat_identity: <+000.000, +000.000, +000.000, +001.000> +quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> +quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> +quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> +axis_normalized: <+000.267, +000.535, -000.802> +axis_angle_radians: +000.838 +from_axis_angle: <+000.109, +000.217, -000.326, +000.914> +from_axis_angle.mat4: +[ + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion multiply == +quat_x: <+000.317, +000.000, +000.000, +000.948> +quat_y: <+000.000, -000.199, +000.000, +000.980> +quat_z: <+000.000, +000.000, +000.581, +000.814> +quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> +quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> +quat_xy.mat4: +[ + +000.921 -000.235 +000.312 +000.000 + +000.000 +000.799 +000.602 +000.000 + -000.391 -000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +quat_xyz.mat4: +[ + +000.300 +000.679 +000.671 +000.000 + -000.870 +000.482 -000.099 +000.000 + -000.391 -000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion vector rotate == +input: <+001.250, -002.500, +003.750> +quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> +quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> +quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> +quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> +quat_z * input: <+002.771, +000.368, +003.750> + +== matrix quaternion roundtrip == +pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> +pure_rotation: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation.quat.mat4: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library +transform.rotation_only: +[ + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> +axis_mat.quat.mat4: +[ + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== basis directions == +canonical_right: <+001.000, +000.000, +000.000> +canonical_up: <+000.000, +001.000, +000.000> +canonical_forward: <+000.000, +000.000, +001.000> +quat_z.right: <+000.326, +000.946, +000.000> +quat_z.up: <-000.946, +000.326, +000.000> +quat_z.forward: <+000.000, +000.000, +001.000> diff --git a/experiments/dump_vmath.nim b/experiments/dump_vmath.nim new file mode 100644 index 0000000..4fd106d --- /dev/null +++ b/experiments/dump_vmath.nim @@ -0,0 +1,197 @@ +import + std/[math, os, strutils], + vmath + +const + OutputPath = parentDir(currentSourcePath()) / "dump_vmath.txt" + +proc cleanFloat(value: float32): float32 = + if abs(value) < 0.0000005'f32: + 0'f32 + else: + value + +proc fmt(value: float32): string = + let cleaned = cleanFloat(value) + let sign = if cleaned < 0'f32: "-" else: "+" + + var whole = int(floor(abs(cleaned).float64)) + var frac = int(round((abs(cleaned).float64 - whole.float64) * 1000.0)) + if frac == 1000: + inc whole + frac = 0 + + result = sign + let wholeText = $whole + for _ in wholeText.len ..< 3: + result.add '0' + result.add wholeText + result.add '.' + + let fracText = $frac + for _ in fracText.len ..< 3: + result.add '0' + result.add fracText + +proc appendLine(lines: var seq[string], line = "") = + lines.add(line) + +proc dumpScalar(lines: var seq[string], label: string, value: float32) = + lines.appendLine(label & ": " & fmt(value)) + +proc dumpVec3(lines: var seq[string], label: string, value: Vec3) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ">") + +proc dumpVec4(lines: var seq[string], label: string, value: Vec4) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ", " & fmt(value.w) & ">") + +proc dumpQuat(lines: var seq[string], label: string, value: Quat) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ", " & fmt(value.w) & ">") + +proc dumpMat4(lines: var seq[string], label: string, value: Mat4) = + lines.appendLine(label & ":") + lines.appendLine("[") + for row in 0 ..< 4: + lines.appendLine( + " " & + fmt(value[row, 0]) & " " & + fmt(value[row, 1]) & " " & + fmt(value[row, 2]) & " " & + fmt(value[row, 3]) + ) + lines.appendLine("]") + +proc heading(lines: var seq[string], title: string) = + if lines.len > 0: + lines.appendLine() + lines.appendLine("== " & title & " ==") + +proc rotationOnlyCopy(value: Mat4): Mat4 = + result = value + result[3, 0] = 0 + result[3, 1] = 0 + result[3, 2] = 0 + +proc main() = + var lines: seq[string] + + let + angleA = 37'f32.toRadians + angleB = -23'f32.toRadians + angleC = 71'f32.toRadians + + matA = mat4( + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 + ) + matB = mat4( + 0.5, -1.0, 2.0, 0.25, + 1.5, 0.75, -0.5, 2.0, + -3.0, 4.0, 1.25, -2.5, + 0.0, 1.0, -1.5, 3.0 + ) + vecA = vec3(1.25, -2.5, 3.75) + vecB = vec4(1.25, -2.5, 3.75, 1.0) + + scaleM = scale(vec3(2.0, 3.0, 4.0)) + translateM = translate(vec3(10.0, 20.0, 30.0)) + rotateXM = rotateX(angleA) + rotateYM = rotateY(angleB) + rotateZM = rotateZ(angleC) + pureRotationM = rotateZM * rotateYM * rotateXM + axis = normalize(vec3(1.0, 2.0, -3.0)) + axisAngle = 48'f32.toRadians + axisQuat = fromAxisAngle(axis, axisAngle) + axisMat = axisQuat.mat4() + transformM = translateM * rotateZM * rotateYM * rotateXM * scaleM + + quatX = quatRotateX(angleA) + quatY = quatRotateY(angleB) + quatZ = quatRotateZ(angleC) + quatXY = quatMultiply(quatX, quatY) + quatXYZ = quatMultiply(quatXY, quatZ) + + rotationOnlyM = rotationOnlyCopy(transformM) + basisRight = vec3(1.0, 0.0, 0.0) + basisUp = vec3(0.0, 1.0, 0.0) + basisForward = mat4().forward() + + lines.heading("dump") + lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") + + lines.heading("matrix constructors and composition") + lines.dumpMat4("identity", mat4()) + lines.dumpMat4("matrix_a", matA) + lines.dumpMat4("matrix_b", matB) + lines.dumpMat4("scale", scaleM) + lines.dumpMat4("translate", translateM) + lines.dumpMat4("rotate_x", rotateXM) + lines.dumpMat4("rotate_y", rotateYM) + lines.dumpMat4("rotate_z", rotateZM) + lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) + lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) + + lines.heading("matrix multiply") + lines.dumpMat4("lhs", matA) + lines.dumpMat4("rhs", matB) + lines.dumpMat4("lhs * rhs", matA * matB) + lines.dumpMat4("rhs * lhs", matB * matA) + + lines.heading("matrix vector multiply") + lines.dumpVec3("vec3_input", vecA) + lines.dumpVec4("vec4_input", vecB) + lines.dumpVec3("transform * vec3", transformM * vecA) + lines.dumpVec4("transform * vec4", transformM * vecB) + lines.dumpVec3("rotate_z * vec3", rotateZM * vecA) + lines.dumpVec3("translate * vec3", translateM * vecA) + + lines.heading("quaternion constructors") + lines.dumpQuat("quat_identity", quat()) + lines.dumpQuat("quat_rotate_x", quatX) + lines.dumpQuat("quat_rotate_y", quatY) + lines.dumpQuat("quat_rotate_z", quatZ) + lines.dumpVec3("axis_normalized", axis) + lines.dumpScalar("axis_angle_radians", axisAngle) + lines.dumpQuat("from_axis_angle", axisQuat) + lines.dumpMat4("from_axis_angle.mat4", axisMat) + + lines.heading("quaternion multiply") + lines.dumpQuat("quat_x", quatX) + lines.dumpQuat("quat_y", quatY) + lines.dumpQuat("quat_z", quatZ) + lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) + lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) + lines.dumpMat4("quat_xy.mat4", quatXY.mat4()) + lines.dumpMat4("quat_xyz.mat4", quatXYZ.mat4()) + + lines.heading("quaternion vector rotate") + lines.dumpVec3("input", vecA) + lines.dumpVec3("quat_rotate(quat_x, input)", quatRotate(quatX, vecA)) + lines.dumpVec3("quat_rotate(quat_y, input)", quatRotate(quatY, vecA)) + lines.dumpVec3("quat_rotate(quat_z, input)", quatRotate(quatZ, vecA)) + lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotate(axisQuat, vecA)) + lines.dumpVec3("quat_z * input", quatZ * vecA) + + lines.heading("matrix quaternion roundtrip") + lines.dumpQuat("pure_rotation.quat", pureRotationM.quat()) + lines.dumpMat4("pure_rotation", pureRotationM) + lines.dumpMat4("pure_rotation.quat.mat4", pureRotationM.quat().mat4()) + lines.appendLine("transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library") + lines.dumpMat4("transform.rotation_only", rotationOnlyM) + lines.dumpQuat("axis_mat.quat", axisMat.quat()) + lines.dumpMat4("axis_mat.quat.mat4", axisMat.quat().mat4()) + + lines.heading("basis directions") + lines.dumpVec3("canonical_right", basisRight) + lines.dumpVec3("canonical_up", basisUp) + lines.dumpVec3("canonical_forward", basisForward) + lines.dumpVec3("quat_z.right", quatZ * basisRight) + lines.dumpVec3("quat_z.up", quatZ * basisUp) + lines.dumpVec3("quat_z.forward", quatZ * basisForward) + + writeFile(OutputPath, lines.join("\n") & "\n") + echo "Wrote ", OutputPath + +main() diff --git a/experiments/dump_vmath.txt b/experiments/dump_vmath.txt new file mode 100644 index 0000000..6e433bb --- /dev/null +++ b/experiments/dump_vmath.txt @@ -0,0 +1,198 @@ +== dump == +notes: matrices are printed in raw in-memory order, four scalars per line + +== matrix constructors and composition == +identity: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +matrix_a: +[ + +001.000 +002.000 +003.000 +004.000 + +005.000 +006.000 +007.000 +008.000 + +009.000 +010.000 +011.000 +012.000 + +013.000 +014.000 +015.000 +016.000 +] +matrix_b: +[ + +000.500 -001.000 +002.000 +000.250 + +001.500 +000.750 -000.500 +002.000 + -003.000 +004.000 +001.250 -002.500 + +000.000 +001.000 -001.500 +003.000 +] +scale: +[ + +002.000 +000.000 +000.000 +000.000 + +000.000 +003.000 +000.000 +000.000 + +000.000 +000.000 +004.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +translate: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +010.000 +020.000 +030.000 +001.000 +] +rotate_x: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +000.799 -000.602 +000.000 + +000.000 +000.602 +000.799 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_y: +[ + +000.921 +000.000 -000.391 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.391 +000.000 +000.921 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_z: +[ + +000.326 -000.946 +000.000 +000.000 + +000.946 +000.326 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation = rotate_z * rotate_y * rotate_x: +[ + +000.300 -000.870 -000.391 +000.000 + +000.679 +000.482 -000.554 +000.000 + +000.671 -000.099 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform = translate * rotate_z * rotate_y * rotate_x * scale: +[ + +000.599 -001.741 -000.781 +000.000 + +002.036 +001.447 -001.662 +000.000 + +002.682 -000.396 +002.941 +000.000 + +010.000 +020.000 +030.000 +001.000 +] + +== matrix multiply == +lhs: +[ + +001.000 +002.000 +003.000 +004.000 + +005.000 +006.000 +007.000 +008.000 + +009.000 +010.000 +011.000 +012.000 + +013.000 +014.000 +015.000 +016.000 +] +rhs: +[ + +000.500 -001.000 +002.000 +000.250 + +001.500 +000.750 -000.500 +002.000 + -003.000 +004.000 +001.250 -002.500 + +000.000 +001.000 -001.500 +003.000 +] +lhs * rhs: +[ + +016.750 +018.500 +020.250 +022.000 + +026.750 +030.500 +034.250 +038.000 + -004.250 -004.500 -004.750 -005.000 + +030.500 +033.000 +035.500 +038.000 +] +rhs * lhs: +[ + -005.500 +016.500 -001.250 +008.750 + -009.500 +035.500 +003.750 +019.750 + -013.500 +054.500 +008.750 +030.750 + -017.500 +073.500 +013.750 +041.750 +] + +== matrix vector multiply == +vec3_input: <+001.250, -002.500, +003.750> +vec4_input: <+001.250, -002.500, +003.750, +001.000> +transform * vec3: <+015.719, +012.720, +044.205> +transform * vec4: <+015.719, +012.720, +044.205, +001.000> +rotate_z * vec3: <-001.957, -001.996, +003.750> +translate * vec3: <+011.250, +017.500, +033.750> + +== quaternion constructors == +quat_identity: <+000.000, +000.000, +000.000, +001.000> +quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> +quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> +quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> +axis_normalized: <+000.267, +000.535, -000.802> +axis_angle_radians: +000.838 +from_axis_angle: <+000.109, +000.217, -000.326, +000.914> +from_axis_angle.mat4: +[ + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion multiply == +quat_x: <+000.317, +000.000, +000.000, +000.948> +quat_y: <+000.000, -000.199, +000.000, +000.980> +quat_z: <+000.000, +000.000, +000.581, +000.814> +quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> +quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> +quat_xy.mat4: +[ + +000.921 +000.000 -000.391 +000.000 + -000.235 +000.799 -000.554 +000.000 + +000.312 +000.602 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +quat_xyz.mat4: +[ + +000.300 -000.870 -000.391 +000.000 + +000.679 +000.482 -000.554 +000.000 + +000.671 -000.099 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion vector rotate == +input: <+001.250, -002.500, +003.750> +quat_rotate(quat_x, input): <+001.250, +000.260, +004.499> +quat_rotate(quat_y, input): <+002.616, -002.500, +002.963> +quat_rotate(quat_z, input): <-001.957, -001.996, +003.750> +quat_rotate(from_axis_angle, input): <+000.482, -000.892, +004.566> +quat_z * input: <-001.957, -001.996, +003.750> + +== matrix quaternion roundtrip == +pure_rotation.quat: <+000.143, -000.334, +000.488, +000.793> +pure_rotation: +[ + +000.300 -000.870 -000.391 +000.000 + +000.679 +000.482 -000.554 +000.000 + +000.671 -000.099 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation.quat.mat4: +[ + +000.300 -000.870 -000.391 +000.000 + +000.679 +000.482 -000.554 +000.000 + +000.671 -000.099 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library +transform.rotation_only: +[ + +000.599 -001.741 -000.781 +000.000 + +002.036 +001.447 -001.662 +000.000 + +002.682 -000.396 +002.941 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> +axis_mat.quat.mat4: +[ + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== basis directions == +canonical_right: <+001.000, +000.000, +000.000> +canonical_up: <+000.000, +001.000, +000.000> +canonical_forward: <+000.000, +000.000, +001.000> +quat_z.right: <+000.326, -000.946, +000.000> +quat_z.up: <+000.946, +000.326, +000.000> +quat_z.forward: <+000.000, +000.000, +001.000> From 9e566e25a1d3213f9de3f376e8a59649be8e45ad Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 09:07:21 -0700 Subject: [PATCH 02/35] it works! --- experiments/dump_glm.nim | 215 ++++++++++++++++++++++++++++++ experiments/dump_glm.txt | 198 ++++++++++++++++++++++++++++ experiments/dump_vmath.nim | 8 +- experiments/dump_vmath.txt | 136 +++++++++---------- src/vmath.nim | 262 ++++++++++++++++--------------------- 5 files changed, 598 insertions(+), 221 deletions(-) create mode 100644 experiments/dump_glm.nim create mode 100644 experiments/dump_glm.txt diff --git a/experiments/dump_glm.nim b/experiments/dump_glm.nim new file mode 100644 index 0000000..665b67b --- /dev/null +++ b/experiments/dump_glm.nim @@ -0,0 +1,215 @@ +import + std/[math, os, strutils], + glm + +const + OutputPath = parentDir(currentSourcePath()) / "dump_glm.txt" + +proc cleanFloat(value: float32): float32 = + if abs(value) < 0.0000005'f32: + 0'f32 + else: + value + +proc fmt(value: float32): string = + let cleaned = cleanFloat(value) + let sign = if cleaned < 0'f32: "-" else: "+" + + var whole = int(floor(abs(cleaned).float64)) + var frac = int(round((abs(cleaned).float64 - whole.float64) * 1000.0)) + if frac == 1000: + inc whole + frac = 0 + + result = sign + let wholeText = $whole + for _ in wholeText.len ..< 3: + result.add '0' + result.add wholeText + result.add '.' + + let fracText = $frac + for _ in fracText.len ..< 3: + result.add '0' + result.add fracText + +proc appendLine(lines: var seq[string], line = "") = + lines.add line + +proc dumpScalar(lines: var seq[string], label: string, value: float32) = + lines.appendLine(label & ": " & fmt(value)) + +proc dumpVec3(lines: var seq[string], label: string, value: Vec3f) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ">") + +proc dumpVec4(lines: var seq[string], label: string, value: Vec4f) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ", " & fmt(value.w) & ">") + +proc dumpQuat(lines: var seq[string], label: string, value: Quatf) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ", " & fmt(value.w) & ">") + +proc dumpMat4(lines: var seq[string], label: string, value: Mat4f) = + lines.appendLine(label & ":") + lines.appendLine("[") + for col in 0 ..< 4: + lines.appendLine( + " " & + fmt(value[col, 0]) & " " & + fmt(value[col, 1]) & " " & + fmt(value[col, 2]) & " " & + fmt(value[col, 3]) + ) + lines.appendLine("]") + +proc heading(lines: var seq[string], title: string) = + if lines.len > 0: + lines.appendLine() + lines.appendLine("== " & title & " ==") + +proc mat4FromRows( + m00, m01, m02, m03: float32, + m10, m11, m12, m13: float32, + m20, m21, m22, m23: float32, + m30, m31, m32, m33: float32 +): Mat4f = + result[0] = vec4f(m00, m10, m20, m30) + result[1] = vec4f(m01, m11, m21, m31) + result[2] = vec4f(m02, m12, m22, m32) + result[3] = vec4f(m03, m13, m23, m33) + +proc transformVec3ByMat4(matrix: Mat4f, value: Vec3f): Vec3f = + (matrix * vec4f(value, 1'f32)).xyz + +proc transformVec4ByMat4(matrix: Mat4f, value: Vec4f): Vec4f = + matrix * value + +proc rotationOnlyCopy(value: Mat4f): Mat4f = + result = value + result[3] = vec4f(0'f32, 0'f32, 0'f32, 1'f32) + +proc main() = + var lines: seq[string] + + let + angleA = 37'f32 * PI.float32 / 180'f32 + angleB = -23'f32 * PI.float32 / 180'f32 + angleC = 71'f32 * PI.float32 / 180'f32 + + matA = mat4FromRows( + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 + ) + matB = mat4FromRows( + 0.5, -1.0, 2.0, 0.25, + 1.5, 0.75, -0.5, 2.0, + -3.0, 4.0, 1.25, -2.5, + 0.0, 1.0, -1.5, 3.0 + ) + vecA = vec3f(1.25, -2.5, 3.75) + vecB = vec4f(1.25, -2.5, 3.75, 1.0) + + scaleM = mat4f(1).scale(2'f32, 3'f32, 4'f32) + translateM = mat4f(1).translate(10'f32, 20'f32, 30'f32) + rotateXM = mat4f(1).rotateX(angleA) + rotateYM = mat4f(1).rotateY(angleB) + rotateZM = mat4f(1).rotateZ(angleC) + pureRotationM = rotateZM * rotateYM * rotateXM + + axis = normalize(vec3f(1.0, 2.0, -3.0)) + axisAngle = 48'f32 * PI.float32 / 180'f32 + axisQuat = quatf(axis, axisAngle) + axisMat = axisQuat.mat4() + transformM = translateM * rotateZM * rotateYM * rotateXM * scaleM + + quatX = quatf(vec3f(1'f32, 0'f32, 0'f32), angleA) + quatY = quatf(vec3f(0'f32, 1'f32, 0'f32), angleB) + quatZ = quatf(vec3f(0'f32, 0'f32, 1'f32), angleC) + quatXY = quatX * quatY + quatXYZ = quatXY * quatZ + + rotationOnlyM = rotationOnlyCopy(transformM) + basisRight = vec3f(1'f32, 0'f32, 0'f32) + basisUp = vec3f(0'f32, 1'f32, 0'f32) + basisForward = vec3f(0'f32, 0'f32, 1'f32) + + lines.heading("dump") + lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") + + lines.heading("matrix constructors and composition") + lines.dumpMat4("identity", mat4f()) + lines.dumpMat4("matrix_a", matA) + lines.dumpMat4("matrix_b", matB) + lines.dumpMat4("scale", scaleM) + lines.dumpMat4("translate", translateM) + lines.dumpMat4("rotate_x", rotateXM) + lines.dumpMat4("rotate_y", rotateYM) + lines.dumpMat4("rotate_z", rotateZM) + lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) + lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) + + lines.heading("matrix multiply") + lines.dumpMat4("lhs", matA) + lines.dumpMat4("rhs", matB) + lines.dumpMat4("lhs * rhs", matA * matB) + lines.dumpMat4("rhs * lhs", matB * matA) + + lines.heading("matrix vector multiply") + lines.dumpVec3("vec3_input", vecA) + lines.dumpVec4("vec4_input", vecB) + lines.dumpVec3("transform * vec3", transformVec3ByMat4(transformM, vecA)) + lines.dumpVec4("transform * vec4", transformVec4ByMat4(transformM, vecB)) + lines.dumpVec3("rotate_z * vec3", transformVec3ByMat4(rotateZM, vecA)) + lines.dumpVec3("translate * vec3", transformVec3ByMat4(translateM, vecA)) + + lines.heading("quaternion constructors") + lines.dumpQuat("quat_identity", quatf()) + lines.dumpQuat("quat_rotate_x", quatX) + lines.dumpQuat("quat_rotate_y", quatY) + lines.dumpQuat("quat_rotate_z", quatZ) + lines.dumpVec3("axis_normalized", axis) + lines.dumpScalar("axis_angle_radians", axisAngle) + lines.dumpQuat("from_axis_angle", axisQuat) + lines.dumpMat4("from_axis_angle.mat4", axisMat) + + lines.heading("quaternion multiply") + lines.dumpQuat("quat_x", quatX) + lines.dumpQuat("quat_y", quatY) + lines.dumpQuat("quat_z", quatZ) + lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) + lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) + lines.dumpMat4("quat_xy.mat4", quatXY.mat4()) + lines.dumpMat4("quat_xyz.mat4", quatXYZ.mat4()) + + lines.heading("quaternion vector rotate") + lines.dumpVec3("input", vecA) + lines.dumpVec3("quat_rotate(quat_x, input)", quatX * vecA) + lines.dumpVec3("quat_rotate(quat_y, input)", quatY * vecA) + lines.dumpVec3("quat_rotate(quat_z, input)", quatZ * vecA) + lines.dumpVec3("quat_rotate(from_axis_angle, input)", axisQuat * vecA) + lines.dumpVec3("quat_z * input", quatZ * vecA) + + lines.heading("matrix quaternion roundtrip") + let pureRotationQuat = quat(pureRotationM) + lines.dumpQuat("pure_rotation.quat", pureRotationQuat) + lines.dumpMat4("pure_rotation", pureRotationM) + lines.dumpMat4("pure_rotation.quat.mat4", pureRotationQuat.mat4()) + lines.appendLine("transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library") + lines.dumpMat4("transform.rotation_only", rotationOnlyM) + let axisMatQuat = quat(axisMat) + lines.dumpQuat("axis_mat.quat", axisMatQuat) + lines.dumpMat4("axis_mat.quat.mat4", axisMatQuat.mat4()) + + lines.heading("basis directions") + lines.dumpVec3("canonical_right", basisRight) + lines.dumpVec3("canonical_up", basisUp) + lines.dumpVec3("canonical_forward", basisForward) + lines.dumpVec3("quat_z.right", quatZ * basisRight) + lines.dumpVec3("quat_z.up", quatZ * basisUp) + lines.dumpVec3("quat_z.forward", quatZ * basisForward) + + writeFile(OutputPath, lines.join("\n") & "\n") + echo "Wrote ", OutputPath + +main() diff --git a/experiments/dump_glm.txt b/experiments/dump_glm.txt new file mode 100644 index 0000000..80d59b3 --- /dev/null +++ b/experiments/dump_glm.txt @@ -0,0 +1,198 @@ +== dump == +notes: matrices are printed in raw in-memory order, four scalars per line + +== matrix constructors and composition == +identity: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +matrix_a: +[ + +001.000 +005.000 +009.000 +013.000 + +002.000 +006.000 +010.000 +014.000 + +003.000 +007.000 +011.000 +015.000 + +004.000 +008.000 +012.000 +016.000 +] +matrix_b: +[ + +000.500 +001.500 -003.000 +000.000 + -001.000 +000.750 +004.000 +001.000 + +002.000 -000.500 +001.250 -001.500 + +000.250 +002.000 -002.500 +003.000 +] +scale: +[ + +002.000 +000.000 +000.000 +000.000 + +000.000 +003.000 +000.000 +000.000 + +000.000 +000.000 +004.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +translate: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +010.000 +020.000 +030.000 +001.000 +] +rotate_x: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +000.799 +000.602 +000.000 + +000.000 -000.602 +000.799 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_y: +[ + +000.921 +000.000 +000.391 +000.000 + +000.000 +001.000 +000.000 +000.000 + -000.391 +000.000 +000.921 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_z: +[ + +000.326 +000.946 +000.000 +000.000 + -000.946 +000.326 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation = rotate_z * rotate_y * rotate_x: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform = translate * rotate_z * rotate_y * rotate_x * scale: +[ + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 + +010.000 +020.000 +030.000 +001.000 +] + +== matrix multiply == +lhs: +[ + +001.000 +005.000 +009.000 +013.000 + +002.000 +006.000 +010.000 +014.000 + +003.000 +007.000 +011.000 +015.000 + +004.000 +008.000 +012.000 +016.000 +] +rhs: +[ + +000.500 +001.500 -003.000 +000.000 + -001.000 +000.750 +004.000 +001.000 + +002.000 -000.500 +001.250 -001.500 + +000.250 +002.000 -002.500 +003.000 +] +lhs * rhs: +[ + -005.500 -009.500 -013.500 -017.500 + +016.500 +035.500 +054.500 +073.500 + -001.250 +003.750 +008.750 +013.750 + +008.750 +019.750 +030.750 +041.750 +] +rhs * lhs: +[ + +016.750 +026.750 -004.250 +030.500 + +018.500 +030.500 -004.500 +033.000 + +020.250 +034.250 -004.750 +035.500 + +022.000 +038.000 -005.000 +038.000 +] + +== matrix vector multiply == +vec3_input: <+001.250, -002.500, +003.750> +vec4_input: <+001.250, -002.500, +003.750, +001.000> +transform * vec3: <+023.998, +014.529, +037.849> +transform * vec4: <+023.998, +014.529, +037.849, +001.000> +rotate_z * vec3: <+002.771, +000.368, +003.750> +translate * vec3: <+011.250, +017.500, +033.750> + +== quaternion constructors == +quat_identity: <+000.000, +000.000, +000.000, +001.000> +quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> +quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> +quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> +axis_normalized: <+000.267, +000.535, -000.802> +axis_angle_radians: +000.838 +from_axis_angle: <+000.109, +000.217, -000.326, +000.914> +from_axis_angle.mat4: +[ + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion multiply == +quat_x: <+000.317, +000.000, +000.000, +000.948> +quat_y: <+000.000, -000.199, +000.000, +000.980> +quat_z: <+000.000, +000.000, +000.581, +000.814> +quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> +quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> +quat_xy.mat4: +[ + +000.921 -000.235 +000.312 +000.000 + +000.000 +000.799 +000.602 +000.000 + -000.391 -000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +quat_xyz.mat4: +[ + +000.300 +000.679 +000.671 +000.000 + -000.870 +000.482 -000.099 +000.000 + -000.391 -000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion vector rotate == +input: <+001.250, -002.500, +003.750> +quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> +quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> +quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> +quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> +quat_z * input: <+002.771, +000.368, +003.750> + +== matrix quaternion roundtrip == +pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> +pure_rotation: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation.quat.mat4: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library +transform.rotation_only: +[ + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> +axis_mat.quat.mat4: +[ + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== basis directions == +canonical_right: <+001.000, +000.000, +000.000> +canonical_up: <+000.000, +001.000, +000.000> +canonical_forward: <+000.000, +000.000, +001.000> +quat_z.right: <+000.326, +000.946, +000.000> +quat_z.up: <-000.946, +000.326, +000.000> +quat_z.forward: <+000.000, +000.000, +001.000> diff --git a/experiments/dump_vmath.nim b/experiments/dump_vmath.nim index 4fd106d..1b4a6ea 100644 --- a/experiments/dump_vmath.nim +++ b/experiments/dump_vmath.nim @@ -116,7 +116,7 @@ proc main() = rotationOnlyM = rotationOnlyCopy(transformM) basisRight = vec3(1.0, 0.0, 0.0) basisUp = vec3(0.0, 1.0, 0.0) - basisForward = mat4().forward() + basisForward = vec3(0.0, 0.0, 1.0) lines.heading("dump") lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") @@ -187,9 +187,9 @@ proc main() = lines.dumpVec3("canonical_right", basisRight) lines.dumpVec3("canonical_up", basisUp) lines.dumpVec3("canonical_forward", basisForward) - lines.dumpVec3("quat_z.right", quatZ * basisRight) - lines.dumpVec3("quat_z.up", quatZ * basisUp) - lines.dumpVec3("quat_z.forward", quatZ * basisForward) + lines.dumpVec3("quat_z.right", quatRotate(quatZ, basisRight)) + lines.dumpVec3("quat_z.up", quatRotate(quatZ, basisUp)) + lines.dumpVec3("quat_z.forward", quatRotate(quatZ, basisForward)) writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/experiments/dump_vmath.txt b/experiments/dump_vmath.txt index 6e433bb..80d59b3 100644 --- a/experiments/dump_vmath.txt +++ b/experiments/dump_vmath.txt @@ -11,17 +11,17 @@ identity: ] matrix_a: [ - +001.000 +002.000 +003.000 +004.000 - +005.000 +006.000 +007.000 +008.000 - +009.000 +010.000 +011.000 +012.000 - +013.000 +014.000 +015.000 +016.000 + +001.000 +005.000 +009.000 +013.000 + +002.000 +006.000 +010.000 +014.000 + +003.000 +007.000 +011.000 +015.000 + +004.000 +008.000 +012.000 +016.000 ] matrix_b: [ - +000.500 -001.000 +002.000 +000.250 - +001.500 +000.750 -000.500 +002.000 - -003.000 +004.000 +001.250 -002.500 - +000.000 +001.000 -001.500 +003.000 + +000.500 +001.500 -003.000 +000.000 + -001.000 +000.750 +004.000 +001.000 + +002.000 -000.500 +001.250 -001.500 + +000.250 +002.000 -002.500 +003.000 ] scale: [ @@ -40,75 +40,75 @@ translate: rotate_x: [ +001.000 +000.000 +000.000 +000.000 - +000.000 +000.799 -000.602 +000.000 - +000.000 +000.602 +000.799 +000.000 + +000.000 +000.799 +000.602 +000.000 + +000.000 -000.602 +000.799 +000.000 +000.000 +000.000 +000.000 +001.000 ] rotate_y: [ - +000.921 +000.000 -000.391 +000.000 + +000.921 +000.000 +000.391 +000.000 +000.000 +001.000 +000.000 +000.000 - +000.391 +000.000 +000.921 +000.000 + -000.391 +000.000 +000.921 +000.000 +000.000 +000.000 +000.000 +001.000 ] rotate_z: [ - +000.326 -000.946 +000.000 +000.000 - +000.946 +000.326 +000.000 +000.000 + +000.326 +000.946 +000.000 +000.000 + -000.946 +000.326 +000.000 +000.000 +000.000 +000.000 +001.000 +000.000 +000.000 +000.000 +000.000 +001.000 ] pure_rotation = rotate_z * rotate_y * rotate_x: [ - +000.300 -000.870 -000.391 +000.000 - +000.679 +000.482 -000.554 +000.000 - +000.671 -000.099 +000.735 +000.000 + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform = translate * rotate_z * rotate_y * rotate_x * scale: [ - +000.599 -001.741 -000.781 +000.000 - +002.036 +001.447 -001.662 +000.000 - +002.682 -000.396 +002.941 +000.000 + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 +010.000 +020.000 +030.000 +001.000 ] == matrix multiply == lhs: [ - +001.000 +002.000 +003.000 +004.000 - +005.000 +006.000 +007.000 +008.000 - +009.000 +010.000 +011.000 +012.000 - +013.000 +014.000 +015.000 +016.000 + +001.000 +005.000 +009.000 +013.000 + +002.000 +006.000 +010.000 +014.000 + +003.000 +007.000 +011.000 +015.000 + +004.000 +008.000 +012.000 +016.000 ] rhs: [ - +000.500 -001.000 +002.000 +000.250 - +001.500 +000.750 -000.500 +002.000 - -003.000 +004.000 +001.250 -002.500 - +000.000 +001.000 -001.500 +003.000 + +000.500 +001.500 -003.000 +000.000 + -001.000 +000.750 +004.000 +001.000 + +002.000 -000.500 +001.250 -001.500 + +000.250 +002.000 -002.500 +003.000 ] lhs * rhs: [ - +016.750 +018.500 +020.250 +022.000 - +026.750 +030.500 +034.250 +038.000 - -004.250 -004.500 -004.750 -005.000 - +030.500 +033.000 +035.500 +038.000 + -005.500 -009.500 -013.500 -017.500 + +016.500 +035.500 +054.500 +073.500 + -001.250 +003.750 +008.750 +013.750 + +008.750 +019.750 +030.750 +041.750 ] rhs * lhs: [ - -005.500 +016.500 -001.250 +008.750 - -009.500 +035.500 +003.750 +019.750 - -013.500 +054.500 +008.750 +030.750 - -017.500 +073.500 +013.750 +041.750 + +016.750 +026.750 -004.250 +030.500 + +018.500 +030.500 -004.500 +033.000 + +020.250 +034.250 -004.750 +035.500 + +022.000 +038.000 -005.000 +038.000 ] == matrix vector multiply == vec3_input: <+001.250, -002.500, +003.750> vec4_input: <+001.250, -002.500, +003.750, +001.000> -transform * vec3: <+015.719, +012.720, +044.205> -transform * vec4: <+015.719, +012.720, +044.205, +001.000> -rotate_z * vec3: <-001.957, -001.996, +003.750> +transform * vec3: <+023.998, +014.529, +037.849> +transform * vec4: <+023.998, +014.529, +037.849, +001.000> +rotate_z * vec3: <+002.771, +000.368, +003.750> translate * vec3: <+011.250, +017.500, +033.750> == quaternion constructors == @@ -121,9 +121,9 @@ axis_angle_radians: +000.838 from_axis_angle: <+000.109, +000.217, -000.326, +000.914> from_axis_angle.mat4: [ - +000.693 +000.643 +000.326 +000.000 - -000.549 +000.764 -000.340 +000.000 - -000.468 +000.057 +000.882 +000.000 + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -135,57 +135,57 @@ quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> quat_xy.mat4: [ - +000.921 +000.000 -000.391 +000.000 - -000.235 +000.799 -000.554 +000.000 - +000.312 +000.602 +000.735 +000.000 + +000.921 -000.235 +000.312 +000.000 + +000.000 +000.799 +000.602 +000.000 + -000.391 -000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] quat_xyz.mat4: [ - +000.300 -000.870 -000.391 +000.000 - +000.679 +000.482 -000.554 +000.000 - +000.671 -000.099 +000.735 +000.000 + +000.300 +000.679 +000.671 +000.000 + -000.870 +000.482 -000.099 +000.000 + -000.391 -000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] == quaternion vector rotate == input: <+001.250, -002.500, +003.750> -quat_rotate(quat_x, input): <+001.250, +000.260, +004.499> -quat_rotate(quat_y, input): <+002.616, -002.500, +002.963> -quat_rotate(quat_z, input): <-001.957, -001.996, +003.750> -quat_rotate(from_axis_angle, input): <+000.482, -000.892, +004.566> -quat_z * input: <-001.957, -001.996, +003.750> +quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> +quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> +quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> +quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> +quat_z * input: <+002.771, +000.368, +003.750> == matrix quaternion roundtrip == -pure_rotation.quat: <+000.143, -000.334, +000.488, +000.793> +pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> pure_rotation: [ - +000.300 -000.870 -000.391 +000.000 - +000.679 +000.482 -000.554 +000.000 - +000.671 -000.099 +000.735 +000.000 + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] pure_rotation.quat.mat4: [ - +000.300 -000.870 -000.391 +000.000 - +000.679 +000.482 -000.554 +000.000 - +000.671 -000.099 +000.735 +000.000 + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library transform.rotation_only: [ - +000.599 -001.741 -000.781 +000.000 - +002.036 +001.447 -001.662 +000.000 - +002.682 -000.396 +002.941 +000.000 + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 +000.000 +000.000 +000.000 +001.000 ] axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> axis_mat.quat.mat4: [ - +000.693 +000.643 +000.326 +000.000 - -000.549 +000.764 -000.340 +000.000 - -000.468 +000.057 +000.882 +000.000 + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -193,6 +193,6 @@ axis_mat.quat.mat4: canonical_right: <+001.000, +000.000, +000.000> canonical_up: <+000.000, +001.000, +000.000> canonical_forward: <+000.000, +000.000, +001.000> -quat_z.right: <+000.326, -000.946, +000.000> -quat_z.up: <+000.946, +000.326, +000.000> +quat_z.right: <+000.326, +000.946, +000.000> +quat_z.up: <-000.946, +000.326, +000.000> quat_z.forward: <+000.000, +000.000, +001.000> diff --git a/src/vmath.nim b/src/vmath.nim index 7537572..c60840b 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -925,17 +925,17 @@ template genMatConstructor*(lower, upper, T: untyped) = m00, m01, m10, m11: T ): `upper 2` = - result[0, 0] = m00; result[0, 1] = m01 - result[1, 0] = m10; result[1, 1] = m11 + result[0, 0] = m00; result[1, 0] = m01 + result[0, 1] = m10; result[1, 1] = m11 proc `lower 3`*( m00, m01, m02, m10, m11, m12, m20, m21, m22: T ): `upper 3` = - result[0, 0] = m00; result[0, 1] = m01; result[0, 2] = m02 - result[1, 0] = m10; result[1, 1] = m11; result[1, 2] = m12 - result[2, 0] = m20; result[2, 1] = m21; result[2, 2] = m22 + result[0, 0] = m00; result[1, 0] = m01; result[2, 0] = m02 + result[0, 1] = m10; result[1, 1] = m11; result[2, 1] = m12 + result[0, 2] = m20; result[1, 2] = m21; result[2, 2] = m22 proc `lower 4`*( m00, m01, m02, m03, @@ -943,35 +943,35 @@ template genMatConstructor*(lower, upper, T: untyped) = m20, m21, m22, m23, m30, m31, m32, m33: T ): `upper 4` = - result[0, 0] = m00; result[0, 1] = m01 - result[0, 2] = m02; result[0, 3] = m03 + result[0, 0] = m00; result[1, 0] = m01 + result[2, 0] = m02; result[3, 0] = m03 - result[1, 0] = m10; result[1, 1] = m11 - result[1, 2] = m12; result[1, 3] = m13 + result[0, 1] = m10; result[1, 1] = m11 + result[2, 1] = m12; result[3, 1] = m13 - result[2, 0] = m20; result[2, 1] = m21 - result[2, 2] = m22; result[2, 3] = m23 + result[0, 2] = m20; result[1, 2] = m21 + result[2, 2] = m22; result[3, 2] = m23 - result[3, 0] = m30; result[3, 1] = m31 - result[3, 2] = m32; result[3, 3] = m33 + result[0, 3] = m30; result[1, 3] = m31 + result[2, 3] = m32; result[3, 3] = m33 proc `lower 2`*(a, b: GVec2[T]): `upper 2` = gmat2[T]( - a.x, a.y, - b.x, b.y + a.x, b.x, + a.y, b.y ) proc `lower 3`*(a, b, c: GVec3[T]): `upper 3` = gmat3[T]( - a.x, a.y, a.z, - b.x, b.y, b.z, - c.x, c.y, c.z, + a.x, b.x, c.x, + a.y, b.y, c.y, + a.z, b.z, c.z, ) proc `lower 4`*(a, b, c, d: GVec4[T]): `upper 4` = gmat4[T]( - a.x, a.y, a.z, a.w, - b.x, b.y, b.z, b.w, - c.x, c.y, c.z, c.w, - d.x, d.y, d.z, d.w, + a.x, b.x, c.x, d.x, + a.y, b.y, c.y, d.y, + a.z, b.z, c.z, d.z, + a.w, b.w, c.w, d.w, ) proc `lower 2`*(): `upper 2` = @@ -1010,7 +1010,7 @@ proc `~=`*[T](a, b: GMat4[T]): bool = a[0] ~= b[0] and a[1] ~= b[1] and a[2] ~= b[2] and a[3] ~= b[3] proc pos*[T](a: GMat3[T]): GVec2[T] = - gvec2[T](a[2].x, a[2].y) + gvec2[T](a[2, 0], a[2, 1]) proc `pos=`*[T](a: var GMat3[T], pos: GVec2[T]) = a[2, 0] = pos.x @@ -1027,15 +1027,15 @@ proc back*[T](a: GMat4[T]): GVec3[T] {.inline.} = -a.forward() proc left*[T](a: GMat4[T]): GVec3[T] {.inline.} = + ## Vector facing -X. + -a.right() + +proc right*[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] @@ -1048,7 +1048,7 @@ proc down*[T](a: GMat4[T]): GVec3[T] {.inline.} = 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, 0], a[3, 1], a[3, 2]) proc `pos=`*[T](a: var GMat4[T], pos: GVec3[T]) = ## See the position of the matrix. @@ -1057,17 +1057,11 @@ proc `pos=`*[T](a: var GMat4[T], pos: GVec3[T]) = a[3, 2] = pos.z proc `*`*[T](a, b: GMat3[T]): GMat3[T] = - result[0, 0] = b[0, 0] * a[0, 0] + b[0, 1] * a[1, 0] + b[0, 2] * a[2, 0] - result[0, 1] = b[0, 0] * a[0, 1] + b[0, 1] * a[1, 1] + b[0, 2] * a[2, 1] - result[0, 2] = b[0, 0] * a[0, 2] + b[0, 1] * a[1, 2] + b[0, 2] * a[2, 2] - - result[1, 0] = b[1, 0] * a[0, 0] + b[1, 1] * a[1, 0] + b[1, 2] * a[2, 0] - result[1, 1] = b[1, 0] * a[0, 1] + b[1, 1] * a[1, 1] + b[1, 2] * a[2, 1] - result[1, 2] = b[1, 0] * a[0, 2] + b[1, 1] * a[1, 2] + b[1, 2] * a[2, 2] - - result[2, 0] = b[2, 0] * a[0, 0] + b[2, 1] * a[1, 0] + b[2, 2] * a[2, 0] - result[2, 1] = b[2, 0] * a[0, 1] + b[2, 1] * a[1, 1] + b[2, 2] * a[2, 1] - result[2, 2] = b[2, 0] * a[0, 2] + b[2, 1] * a[1, 2] + b[2, 2] * a[2, 2] + for col in 0 ..< 3: + let v = a * b[col] + result[col, 0] = v.x + result[col, 1] = v.y + result[col, 2] = v.z proc `*`*[T](a: GMat2[T], b: GVec2[T]): GVec2[T] = gvec2[T]( @@ -1089,61 +1083,12 @@ proc `*`*[T](a: GMat3[T], b: GVec3[T]): GVec3[T] = ) proc `*`*[T](a, b: GMat4[T]): GMat4[T] = - let - a00 = a[0, 0] - a01 = a[0, 1] - a02 = a[0, 2] - a03 = a[0, 3] - a10 = a[1, 0] - a11 = a[1, 1] - a12 = a[1, 2] - a13 = a[1, 3] - a20 = a[2, 0] - a21 = a[2, 1] - a22 = a[2, 2] - a23 = a[2, 3] - a30 = a[3, 0] - a31 = a[3, 1] - a32 = a[3, 2] - a33 = a[3, 3] - - let - b00 = b[0, 0] - b01 = b[0, 1] - b02 = b[0, 2] - b03 = b[0, 3] - b10 = b[1, 0] - b11 = b[1, 1] - b12 = b[1, 2] - b13 = b[1, 3] - b20 = b[2, 0] - b21 = b[2, 1] - b22 = b[2, 2] - b23 = b[2, 3] - b30 = b[3, 0] - b31 = b[3, 1] - b32 = b[3, 2] - b33 = b[3, 3] - - result[0, 0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30 - result[0, 1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31 - result[0, 2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32 - result[0, 3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33 - - result[1, 0] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30 - result[1, 1] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31 - result[1, 2] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32 - result[1, 3] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33 - - result[2, 0] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30 - result[2, 1] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31 - result[2, 2] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32 - result[2, 3] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33 - - result[3, 0] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30 - result[3, 1] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31 - result[3, 2] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32 - result[3, 3] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33 + for col in 0 ..< 4: + let v = a * b[col] + result[col, 0] = v.x + result[col, 1] = v.y + result[col, 2] = v.z + result[col, 3] = v.w proc `*`*[T](a: GMat4[T], b: GVec3[T]): GVec3[T] = gvec3[T]( @@ -1314,12 +1259,25 @@ proc translate*[T](v: GVec2[T]): GMat3[T] = proc translate*[T](v: GVec3[T]): GMat4[T] = ## Create translation matrix. - gmat4[T]( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - v.x, v.y, v.z, 1 - ) + result[0, 0] = 1 + result[1, 0] = 0 + result[2, 0] = 0 + result[3, 0] = v.x + + result[0, 1] = 0 + result[1, 1] = 1 + result[2, 1] = 0 + result[3, 1] = v.y + + result[0, 2] = 0 + result[1, 2] = 0 + result[2, 2] = 1 + result[3, 2] = v.z + + result[0, 3] = 0 + result[1, 3] = 0 + result[2, 3] = 0 + result[3, 3] = 1 proc rotate*[T](angle: T): GMat3[T] = ## Create a 2D rotation matrix by an angle. @@ -1349,11 +1307,11 @@ proc rotateX*[T](angle: T): GMat4[T] = result[1, 0] = 0 result[1, 1] = cos(angle) - result[1, 2] = -sin(angle) + result[1, 2] = sin(angle) result[1, 3] = 0 result[2, 0] = 0 - result[2, 1] = sin(angle) + result[2, 1] = -sin(angle) result[2, 2] = cos(angle) result[2, 3] = 0 @@ -1367,7 +1325,7 @@ proc rotateY*[T](angle: T): GMat4[T] = ## All angles assume radians. result[0, 0] = cos(angle) result[0, 1] = 0 - result[0, 2] = sin(angle) + result[0, 2] = -sin(angle) result[0, 3] = 0 result[1, 0] = 0 @@ -1375,7 +1333,7 @@ proc rotateY*[T](angle: T): GMat4[T] = result[1, 2] = 0 result[1, 3] = 0 - result[2, 0] = -sin(angle) + result[2, 0] = sin(angle) result[2, 1] = 0 result[2, 2] = cos(angle) result[2, 3] = 0 @@ -1389,11 +1347,11 @@ 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, 1] = sin(angle) result[0, 2] = 0 result[0, 3] = 0 - result[1, 0] = sin(angle) + result[1, 0] = -sin(angle) result[1, 1] = cos(angle) result[1, 2] = 0 result[1, 3] = 0 @@ -1712,42 +1670,47 @@ proc nlerp*(a: Quat, b: Quat, v: float32): Quat = proc quat*[T](m: GMat4[T]): GVec4[T] = ## Create a quaternion from matrix. let - m00 = m[0, 0] - m01 = m[0, 1] - m02 = m[0, 2] - - m10 = m[1, 0] - m11 = m[1, 1] - m12 = m[1, 2] - - m20 = m[2, 0] - m21 = m[2, 1] - m22 = m[2, 2] - - var - q: GVec4[T] - t: T - - if m22 < 0: - if m00 > m11: - t = 1 + m00 - m11 - m22 - q = gvec4(t, m01 + m10, m20 + m02, m21 - m12) - else: - t = 1 - m00 + m11 - m22 - q = gvec4(m01 + m10, t, m12 + m21, m02 - m20) + fourXSquaredMinus1 = m[0, 0] - m[1, 1] - m[2, 2] + fourYSquaredMinus1 = m[1, 1] - m[0, 0] - m[2, 2] + fourZSquaredMinus1 = m[2, 2] - m[0, 0] - m[1, 1] + fourWSquaredMinus1 = m[0, 0] + m[1, 1] + m[2, 2] + + var biggestIndex = 0 + var fourBiggestSquaredMinus1 = fourWSquaredMinus1 + if fourXSquaredMinus1 > fourBiggestSquaredMinus1: + fourBiggestSquaredMinus1 = fourXSquaredMinus1 + biggestIndex = 1 + if fourYSquaredMinus1 > fourBiggestSquaredMinus1: + fourBiggestSquaredMinus1 = fourYSquaredMinus1 + biggestIndex = 2 + if fourZSquaredMinus1 > fourBiggestSquaredMinus1: + fourBiggestSquaredMinus1 = fourZSquaredMinus1 + biggestIndex = 3 + + let biggestVal = sqrt(fourBiggestSquaredMinus1 + T(1)) * T(0.5) + let mult = T(0.25) / biggestVal + + case biggestIndex + of 0: + result.w = biggestVal + result.x = (m[1, 2] - m[2, 1]) * mult + result.y = (m[2, 0] - m[0, 2]) * mult + result.z = (m[0, 1] - m[1, 0]) * mult + of 1: + result.w = (m[1, 2] - m[2, 1]) * mult + result.x = biggestVal + result.y = (m[0, 1] + m[1, 0]) * mult + result.z = (m[2, 0] + m[0, 2]) * mult + of 2: + result.w = (m[2, 0] - m[0, 2]) * mult + result.x = (m[0, 1] + m[1, 0]) * mult + result.y = biggestVal + result.z = (m[1, 2] + m[2, 1]) * mult else: - if m00 < - m11: - t = 1 - m00 - m11 + m22 - q = gvec4(m20 + m02, m12 + m21, t, m10 - m01) - else: - t = 1 + m00 + m11 + m22 - q = gvec4(m21 - m12, m02 - m20, m10 - m01, t) - q = q * (0.5 / sqrt(t)) - - if abs(q.length - 1.0) > 0.001: - return gvec4(T(0), 0, 0, 1) - - return q + result.w = (m[0, 1] - m[1, 0]) * mult + result.x = (m[2, 0] + m[0, 2]) * mult + result.y = (m[1, 2] + m[2, 1]) * mult + result.z = biggestVal proc mat4*[T](q: GVec4[T]): GMat4[T] = let @@ -1764,17 +1727,17 @@ proc mat4*[T](q: GVec4[T]): GMat4[T] = zw = q.z * q.w result[0, 0] = 1 - 2 * (yy + zz) - result[0, 1] = 0 + 2 * (xy - zw) - result[0, 2] = 0 + 2 * (xz + yw) + result[0, 1] = 0 + 2 * (xy + zw) + result[0, 2] = 0 + 2 * (xz - yw) result[0, 3] = 0 - result[1, 0] = 0 + 2 * (xy + zw) + result[1, 0] = 0 + 2 * (xy - zw) result[1, 1] = 1 - 2 * (xx + zz) - result[1, 2] = 0 + 2 * (yz - xw) + result[1, 2] = 0 + 2 * (yz + xw) result[1, 3] = 0 - result[2, 0] = 0 + 2 * (xz - yw) - result[2, 1] = 0 + 2 * (yz + xw) + result[2, 0] = 0 + 2 * (xz + yw) + result[2, 1] = 0 + 2 * (yz - xw) result[2, 2] = 1 - 2 * (xx + yy) result[2, 3] = 0 @@ -1859,8 +1822,9 @@ proc quatRotate*[T](q: GVec4[T], v: GVec3[T]): GVec3[T] = ## Rotate a vector directly by a quaternion without building a matrix. let qv = gvec3[T](q.x, q.y, q.z) - t = cross(v, qv) * 2 - v + q.w * t + cross(t, qv) + uv = cross(qv, v) + uuv = cross(qv, uv) + v + (uv * q.w + uuv) * 2 proc `*`*[T](a: GVec4[T], b: GVec3[T]): GVec3[T] {.inline.} = ## Rotate a vector directly by a quaternion. From b9377bab48ec521a5582d2dee7573db61b2374bf Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 09:08:27 -0700 Subject: [PATCH 03/35] we are back --- src/vmath.nim | 159 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 126 insertions(+), 33 deletions(-) diff --git a/src/vmath.nim b/src/vmath.nim index c60840b..5297f79 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -1057,11 +1057,39 @@ proc `pos=`*[T](a: var GMat4[T], pos: GVec3[T]) = a[3, 2] = pos.z proc `*`*[T](a, b: GMat3[T]): GMat3[T] = - for col in 0 ..< 3: - let v = a * b[col] - result[col, 0] = v.x - result[col, 1] = v.y - result[col, 2] = v.z + let + a00 = a[0, 0] + a01 = a[0, 1] + a02 = a[0, 2] + a10 = a[1, 0] + a11 = a[1, 1] + a12 = a[1, 2] + a20 = a[2, 0] + a21 = a[2, 1] + a22 = a[2, 2] + + let + b00 = b[0, 0] + b01 = b[0, 1] + b02 = b[0, 2] + b10 = b[1, 0] + b11 = b[1, 1] + b12 = b[1, 2] + b20 = b[2, 0] + b21 = b[2, 1] + b22 = b[2, 2] + + result[0, 0] = a00 * b00 + a10 * b01 + a20 * b02 + result[0, 1] = a01 * b00 + a11 * b01 + a21 * b02 + result[0, 2] = a02 * b00 + a12 * b01 + a22 * b02 + + result[1, 0] = a00 * b10 + a10 * b11 + a20 * b12 + result[1, 1] = a01 * b10 + a11 * b11 + a21 * b12 + result[1, 2] = a02 * b10 + a12 * b11 + a22 * b12 + + result[2, 0] = a00 * b20 + a10 * b21 + a20 * b22 + result[2, 1] = a01 * b20 + a11 * b21 + a21 * b22 + result[2, 2] = a02 * b20 + a12 * b21 + a22 * b22 proc `*`*[T](a: GMat2[T], b: GVec2[T]): GVec2[T] = gvec2[T]( @@ -1083,12 +1111,61 @@ proc `*`*[T](a: GMat3[T], b: GVec3[T]): GVec3[T] = ) proc `*`*[T](a, b: GMat4[T]): GMat4[T] = - for col in 0 ..< 4: - let v = a * b[col] - result[col, 0] = v.x - result[col, 1] = v.y - result[col, 2] = v.z - result[col, 3] = v.w + let + a00 = a[0, 0] + a01 = a[0, 1] + a02 = a[0, 2] + a03 = a[0, 3] + a10 = a[1, 0] + a11 = a[1, 1] + a12 = a[1, 2] + a13 = a[1, 3] + a20 = a[2, 0] + a21 = a[2, 1] + a22 = a[2, 2] + a23 = a[2, 3] + a30 = a[3, 0] + a31 = a[3, 1] + a32 = a[3, 2] + a33 = a[3, 3] + + let + b00 = b[0, 0] + b01 = b[0, 1] + b02 = b[0, 2] + b03 = b[0, 3] + b10 = b[1, 0] + b11 = b[1, 1] + b12 = b[1, 2] + b13 = b[1, 3] + b20 = b[2, 0] + b21 = b[2, 1] + b22 = b[2, 2] + b23 = b[2, 3] + b30 = b[3, 0] + b31 = b[3, 1] + b32 = b[3, 2] + b33 = b[3, 3] + + result[0, 0] = a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03 + result[0, 1] = a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03 + result[0, 2] = a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03 + result[0, 3] = a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03 + + result[1, 0] = a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13 + result[1, 1] = a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13 + result[1, 2] = a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13 + result[1, 3] = a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13 + + result[2, 0] = a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23 + result[2, 1] = a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23 + result[2, 2] = a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23 + result[2, 3] = a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23 + + result[3, 0] = a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33 + result[3, 1] = a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33 + result[3, 2] = a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33 + result[3, 3] = a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33 proc `*`*[T](a: GMat4[T], b: GVec3[T]): GVec3[T] = gvec3[T]( @@ -1670,13 +1747,27 @@ proc nlerp*(a: Quat, b: Quat, v: float32): Quat = proc quat*[T](m: GMat4[T]): GVec4[T] = ## Create a quaternion from matrix. let - fourXSquaredMinus1 = m[0, 0] - m[1, 1] - m[2, 2] - fourYSquaredMinus1 = m[1, 1] - m[0, 0] - m[2, 2] - fourZSquaredMinus1 = m[2, 2] - m[0, 0] - m[1, 1] - fourWSquaredMinus1 = m[0, 0] + m[1, 1] + m[2, 2] + m00 = m[0, 0] + m01 = m[0, 1] + m02 = m[0, 2] + + m10 = m[1, 0] + m11 = m[1, 1] + m12 = m[1, 2] - var biggestIndex = 0 - var fourBiggestSquaredMinus1 = fourWSquaredMinus1 + m20 = m[2, 0] + m21 = m[2, 1] + m22 = m[2, 2] + + fourXSquaredMinus1 = m00 - m11 - m22 + fourYSquaredMinus1 = m11 - m00 - m22 + fourZSquaredMinus1 = m22 - m00 - m11 + fourWSquaredMinus1 = m00 + m11 + m22 + + var + q: GVec4[T] + biggestIndex = 0 + fourBiggestSquaredMinus1 = fourWSquaredMinus1 if fourXSquaredMinus1 > fourBiggestSquaredMinus1: fourBiggestSquaredMinus1 = fourXSquaredMinus1 biggestIndex = 1 @@ -1692,25 +1783,27 @@ proc quat*[T](m: GMat4[T]): GVec4[T] = case biggestIndex of 0: - result.w = biggestVal - result.x = (m[1, 2] - m[2, 1]) * mult - result.y = (m[2, 0] - m[0, 2]) * mult - result.z = (m[0, 1] - m[1, 0]) * mult + q.w = biggestVal + q.x = (m12 - m21) * mult + q.y = (m20 - m02) * mult + q.z = (m01 - m10) * mult of 1: - result.w = (m[1, 2] - m[2, 1]) * mult - result.x = biggestVal - result.y = (m[0, 1] + m[1, 0]) * mult - result.z = (m[2, 0] + m[0, 2]) * mult + q.w = (m12 - m21) * mult + q.x = biggestVal + q.y = (m01 + m10) * mult + q.z = (m20 + m02) * mult of 2: - result.w = (m[2, 0] - m[0, 2]) * mult - result.x = (m[0, 1] + m[1, 0]) * mult - result.y = biggestVal - result.z = (m[1, 2] + m[2, 1]) * mult + q.w = (m20 - m02) * mult + q.x = (m01 + m10) * mult + q.y = biggestVal + q.z = (m12 + m21) * mult else: - result.w = (m[0, 1] - m[1, 0]) * mult - result.x = (m[2, 0] + m[0, 2]) * mult - result.y = (m[1, 2] + m[2, 1]) * mult - result.z = biggestVal + q.w = (m01 - m10) * mult + q.x = (m20 + m02) * mult + q.y = (m12 + m21) * mult + q.z = biggestVal + + result = q proc mat4*[T](q: GVec4[T]): GMat4[T] = let From a4ce837e043c6c2802d0202656e2d96e9a777034 Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 13:37:14 -0700 Subject: [PATCH 04/35] new math stuff --- .gitignore | 1 - README.md | 37 ++ experiments/dump_glm.nim | 13 + experiments/dump_glm.txt | 13 + experiments/dump_glmatrix.js | 256 ++++++++++++++ experiments/dump_glmatrix.txt | 13 + experiments/dump_jolt.nim | 17 + experiments/dump_jolt.txt | 13 + experiments/dump_vmath.nim | 46 ++- experiments/dump_vmath.txt | 12 + src/vmath.nim | 633 ++++++++++++++++------------------ tests/test.nim | 28 +- tests/test_quaternion.nim | 24 +- 13 files changed, 732 insertions(+), 374 deletions(-) create mode 100644 experiments/dump_glmatrix.js diff --git a/.gitignore b/.gitignore index ffc0d8a..1b48119 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,5 @@ !*.* # normal ignores: -*.js *.exe nimcache diff --git a/README.md b/README.md index 58b046e..97be098 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,43 @@ OpenGL/GLSL/vmath vs Math/Specification notation: ]) ``` +## How does vmath compare to other libraries? + +vmath follows the standard glTF / OpenGL conventions: right-handed coordinate system, column-major matrix storage, and standard math `[row, col]` indexing. These are the dominant conventions used across graphics and game engines. + +We run identical math operations across vmath, [nim-glm](https://github.com/nickelsworth/nim-glm), [gl-matrix](https://github.com/toji/gl-matrix), and [Jolt Physics](https://github.com/jrouwe/JoltPhysics) and compare all results. See [experiments/](experiments/) for the dump scripts. + +| Feature | GLM | gl-matrix | Jolt | +|----------------------------|:---:|:---------:|:----:| +| Vectors | ✅ | ✅ | ✅ | +| Matrix memory layout | ✅ | ✅ | ✅ | +| Matrix multiply | ✅ | ✅ | ✅ | +| Matrix-vector multiply | ✅ | ✅ | ✅ | +| Rotation matrices | ✅ | ✅ | ✅ | +| Translation matrices | ✅ | ✅ | ✅ | +| Scale matrices | ✅ | ✅ | ✅ | +| Quaternion constructors | ✅ | ✅ | ✅ | +| Quaternion multiply | ✅ | ✅ | ✅ | +| Quaternion vector rotation | ✅ | ✅ | ✅ | +| Quaternion-matrix roundtrip| ✅ | ✅ | ✅ | +| Element access `[row,col]` | ✅ | N/A | ✅ | +| Quat decomposition (sign) | ❌ | ❌ | ❌ | +| Scaled-matrix decomposition| ❌ | ❌ | ❌ | + +❌ **Quaternion decomposition sign**: When extracting a quaternion from a rotation matrix, different libraries may return quaternions that differ by sign (q and -q represent the same rotation). This is expected behavior, not a bug. + +❌ **Scaled-matrix decomposition**: Extracting rotation from a matrix that includes scale produces different results across libraries because there is no single correct answer. + +# 2.x.x to 3.0.0 vmath breaking changes: + +* **Matrix indexing convention changed from `[col, row]` to `[row, col]`** (standard math notation). Code using `m[0, 3]` to access the translation X component should change to `m[0, 3]` (same syntax, but the meaning of indices swapped). +* **`mat4(1..16)` no longer transposes** the input. Arguments are now stored directly to memory in column-major order, identical to `gmat4(1..16)`. If you were passing row-major values, you need to transpose them. +* **Matrix multiplication is now standard `A * B`** (apply B first, then A). Previously it was reversed. +* **`mat4 * vec3` now computes `M * v`** instead of `M^T * v`. +* **2D `rotate(angle)` now produces a standard CCW rotation matrix.** +* **`fromTwoVectors(a, b)` now correctly rotates `a` into `b`** (was reversed). +* **`toAngles` / `fromAngles` use standard Y-X-Z decomposition** with correct signs. + # 1.x.x to 2.0.0 vmath breaking changes: * New right-hand-Z-forward coordinate system and functions that care about coordinate system were moved there. diff --git a/experiments/dump_glm.nim b/experiments/dump_glm.nim index 665b67b..1e4a473 100644 --- a/experiments/dump_glm.nim +++ b/experiments/dump_glm.nim @@ -209,6 +209,19 @@ proc main() = lines.dumpVec3("quat_z.up", quatZ * basisUp) lines.dumpVec3("quat_z.forward", quatZ * basisForward) + lines.heading("element access [row,col]") + lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") + lines.appendLine("notes: GLM uses [col,row], so element (r,c) = value[c,r]") + lines.dumpScalar("transform[0,0]", transformM[0, 0]) + lines.dumpScalar("transform[0,1]", transformM[1, 0]) + lines.dumpScalar("transform[0,2]", transformM[2, 0]) + lines.dumpScalar("transform[0,3]", transformM[3, 0]) + lines.dumpScalar("transform[1,0]", transformM[0, 1]) + lines.dumpScalar("transform[1,3]", transformM[3, 1]) + lines.dumpScalar("transform[2,0]", transformM[0, 2]) + lines.dumpScalar("transform[2,3]", transformM[3, 2]) + lines.dumpScalar("transform[3,3]", transformM[3, 3]) + writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/experiments/dump_glm.txt b/experiments/dump_glm.txt index 80d59b3..411cada 100644 --- a/experiments/dump_glm.txt +++ b/experiments/dump_glm.txt @@ -196,3 +196,16 @@ canonical_forward: <+000.000, +000.000, +001.000> quat_z.right: <+000.326, +000.946, +000.000> quat_z.up: <-000.946, +000.326, +000.000> quat_z.forward: <+000.000, +000.000, +001.000> + +== element access [row,col] == +notes: [row,col] in math convention, element (i,j) = row i, col j +notes: GLM uses [col,row], so element (r,c) = value[c,r] +transform[0,0]: +000.599 +transform[0,1]: -002.495 +transform[0,2]: +001.870 +transform[0,3]: +010.000 +transform[1,0]: +001.741 +transform[1,3]: +020.000 +transform[2,0]: +000.781 +transform[2,3]: +030.000 +transform[3,3]: +001.000 diff --git a/experiments/dump_glmatrix.js b/experiments/dump_glmatrix.js new file mode 100644 index 0000000..76713fb --- /dev/null +++ b/experiments/dump_glmatrix.js @@ -0,0 +1,256 @@ +import * as mat4 from "/Users/me/p/gl-matrix/src/mat4.js"; +import * as mat3 from "/Users/me/p/gl-matrix/src/mat3.js"; +import * as vec3 from "/Users/me/p/gl-matrix/src/vec3.js"; +import * as vec4 from "/Users/me/p/gl-matrix/src/vec4.js"; +import * as quat from "/Users/me/p/gl-matrix/src/quat.js"; +import { writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const OutputPath = join(__dirname, "dump_glmatrix.txt"); + +function cleanFloat(value) { + if (Math.abs(value) < 0.0000005) return 0; + return value; +} + +function fmt(value) { + const cleaned = cleanFloat(value); + const sign = cleaned < 0 ? "-" : "+"; + let abs = Math.abs(cleaned); + let whole = Math.floor(abs); + let frac = Math.round((abs - whole) * 1000); + if (frac === 1000) { whole++; frac = 0; } + return sign + String(whole).padStart(3, "0") + "." + String(frac).padStart(3, "0"); +} + +const lines = []; +function appendLine(line = "") { lines.push(line); } +function heading(title) { + if (lines.length > 0) appendLine(); + appendLine("== " + title + " =="); +} +function dumpScalar(label, value) { appendLine(label + ": " + fmt(value)); } +function dumpVec3(label, v) { + appendLine(label + ": <" + fmt(v[0]) + ", " + fmt(v[1]) + ", " + fmt(v[2]) + ">"); +} +function dumpVec4(label, v) { + appendLine(label + ": <" + fmt(v[0]) + ", " + fmt(v[1]) + ", " + fmt(v[2]) + ", " + fmt(v[3]) + ">"); +} +function dumpQuat(label, q) { + appendLine(label + ": <" + fmt(q[0]) + ", " + fmt(q[1]) + ", " + fmt(q[2]) + ", " + fmt(q[3]) + ">"); +} +function dumpMat4(label, m) { + appendLine(label + ":"); + appendLine("["); + for (let col = 0; col < 4; col++) { + const off = col * 4; + appendLine(" " + fmt(m[off]) + " " + fmt(m[off+1]) + " " + fmt(m[off+2]) + " " + fmt(m[off+3])); + } + appendLine("]"); +} + +// Helper: create a mat4 from row-major input (math notation). +// Row-major element (r,c) goes to column-major arr[c*4+r]. +function mat4FromRows( + m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33 +) { + return mat4.fromValues( + m00, m10, m20, m30, + m01, m11, m21, m31, + m02, m12, m22, m32, + m03, m13, m23, m33 + ); +} + +// gl-matrix transforms take (out, matrix, param) — mutate out. +// Helpers to build fresh matrices from identity. +function scaleMat(sx, sy, sz) { + return mat4.scale(mat4.create(), mat4.create(), vec3.fromValues(sx, sy, sz)); +} +function translateMat(tx, ty, tz) { + return mat4.translate(mat4.create(), mat4.create(), vec3.fromValues(tx, ty, tz)); +} +function rotateXMat(angle) { + return mat4.rotateX(mat4.create(), mat4.create(), angle); +} +function rotateYMat(angle) { + return mat4.rotateY(mat4.create(), mat4.create(), angle); +} +function rotateZMat(angle) { + return mat4.rotateZ(mat4.create(), mat4.create(), angle); +} +function mul(a, b) { + return mat4.multiply(mat4.create(), a, b); +} +function transformVec3(m, v) { + return vec3.transformMat4(vec3.create(), v, m); +} +function transformVec4(m, v) { + return vec4.transformMat4(vec4.create(), v, m); +} +function quatFromAxisAngle(axis, angle) { + return quat.setAxisAngle(quat.create(), axis, angle); +} +function quatMul(a, b) { + return quat.multiply(quat.create(), a, b); +} +function quatRotateVec3(q, v) { + return vec3.transformQuat(vec3.create(), v, q); +} +function mat4FromQuat(q) { + return mat4.fromQuat(mat4.create(), q); +} +function rotationOnlyCopy(m) { + const out = mat4.clone(m); + out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; + return out; +} +function mat3FromMat4Upper(m) { + return mat3.fromMat4(mat3.create(), m); +} +function quatFromMat(m) { + const m3 = mat3FromMat4Upper(m); + return quat.fromMat3(quat.create(), m3); +} + +// --- Main --- + +const DEG2RAD = Math.PI / 180; +const angleA = 37 * DEG2RAD; +const angleB = -23 * DEG2RAD; +const angleC = 71 * DEG2RAD; + +const matA = mat4FromRows( + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 +); +const matB = mat4FromRows( + 0.5, -1.0, 2.0, 0.25, + 1.5, 0.75, -0.5, 2.0, + -3.0, 4.0, 1.25, -2.5, + 0.0, 1.0, -1.5, 3.0 +); +const vecA = vec3.fromValues(1.25, -2.5, 3.75); +const vecB = vec4.fromValues(1.25, -2.5, 3.75, 1.0); + +const scaleM = scaleMat(2.0, 3.0, 4.0); +const translateM = translateMat(10.0, 20.0, 30.0); +const rotateXM = rotateXMat(angleA); +const rotateYM = rotateYMat(angleB); +const rotateZM = rotateZMat(angleC); +const pureRotationM = mul(rotateZM, mul(rotateYM, rotateXM)); + +const axis = vec3.normalize(vec3.create(), vec3.fromValues(1.0, 2.0, -3.0)); +const axisAngle = 48 * DEG2RAD; +const axisQuat = quatFromAxisAngle(axis, axisAngle); +const axisMat = mat4FromQuat(axisQuat); +const transformM = mul(translateM, mul(rotateZM, mul(rotateYM, mul(rotateXM, scaleM)))); + +const quatX = quatFromAxisAngle(vec3.fromValues(1, 0, 0), angleA); +const quatY = quatFromAxisAngle(vec3.fromValues(0, 1, 0), angleB); +const quatZ = quatFromAxisAngle(vec3.fromValues(0, 0, 1), angleC); +const quatXY = quatMul(quatX, quatY); +const quatXYZ = quatMul(quatXY, quatZ); + +const rotationOnlyM = rotationOnlyCopy(transformM); +const basisRight = vec3.fromValues(1, 0, 0); +const basisUp = vec3.fromValues(0, 1, 0); +const basisForward = vec3.fromValues(0, 0, 1); + +heading("dump"); +appendLine("notes: matrices are printed in raw in-memory order, four scalars per line"); + +heading("matrix constructors and composition"); +dumpMat4("identity", mat4.create()); +dumpMat4("matrix_a", matA); +dumpMat4("matrix_b", matB); +dumpMat4("scale", scaleM); +dumpMat4("translate", translateM); +dumpMat4("rotate_x", rotateXM); +dumpMat4("rotate_y", rotateYM); +dumpMat4("rotate_z", rotateZM); +dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM); +dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM); + +heading("matrix multiply"); +dumpMat4("lhs", matA); +dumpMat4("rhs", matB); +dumpMat4("lhs * rhs", mul(matA, matB)); +dumpMat4("rhs * lhs", mul(matB, matA)); + +heading("matrix vector multiply"); +dumpVec3("vec3_input", vecA); +dumpVec4("vec4_input", vecB); +dumpVec3("transform * vec3", transformVec3(transformM, vecA)); +dumpVec4("transform * vec4", transformVec4(transformM, vecB)); +dumpVec3("rotate_z * vec3", transformVec3(rotateZM, vecA)); +dumpVec3("translate * vec3", transformVec3(translateM, vecA)); + +heading("quaternion constructors"); +dumpQuat("quat_identity", quat.create()); +dumpQuat("quat_rotate_x", quatX); +dumpQuat("quat_rotate_y", quatY); +dumpQuat("quat_rotate_z", quatZ); +dumpVec3("axis_normalized", axis); +dumpScalar("axis_angle_radians", axisAngle); +dumpQuat("from_axis_angle", axisQuat); +dumpMat4("from_axis_angle.mat4", axisMat); + +heading("quaternion multiply"); +dumpQuat("quat_x", quatX); +dumpQuat("quat_y", quatY); +dumpQuat("quat_z", quatZ); +dumpQuat("quat_multiply(quat_x, quat_y)", quatXY); +dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ); +dumpMat4("quat_xy.mat4", mat4FromQuat(quatXY)); +dumpMat4("quat_xyz.mat4", mat4FromQuat(quatXYZ)); + +heading("quaternion vector rotate"); +dumpVec3("input", vecA); +dumpVec3("quat_rotate(quat_x, input)", quatRotateVec3(quatX, vecA)); +dumpVec3("quat_rotate(quat_y, input)", quatRotateVec3(quatY, vecA)); +dumpVec3("quat_rotate(quat_z, input)", quatRotateVec3(quatZ, vecA)); +dumpVec3("quat_rotate(from_axis_angle, input)", quatRotateVec3(axisQuat, vecA)); +dumpVec3("quat_z * input", quatRotateVec3(quatZ, vecA)); + +heading("matrix quaternion roundtrip"); +const pureRotationQuat = quatFromMat(pureRotationM); +dumpQuat("pure_rotation.quat", pureRotationQuat); +dumpMat4("pure_rotation", pureRotationM); +dumpMat4("pure_rotation.quat.mat4", mat4FromQuat(pureRotationQuat)); +appendLine("transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library"); +dumpMat4("transform.rotation_only", rotationOnlyM); +const axisMatQuat = quatFromMat(axisMat); +dumpQuat("axis_mat.quat", axisMatQuat); +dumpMat4("axis_mat.quat.mat4", mat4FromQuat(axisMatQuat)); + +heading("basis directions"); +dumpVec3("canonical_right", basisRight); +dumpVec3("canonical_up", basisUp); +dumpVec3("canonical_forward", basisForward); +dumpVec3("quat_z.right", quatRotateVec3(quatZ, basisRight)); +dumpVec3("quat_z.up", quatRotateVec3(quatZ, basisUp)); +dumpVec3("quat_z.forward", quatRotateVec3(quatZ, basisForward)); + +heading("element access [row,col]"); +appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j"); +appendLine("notes: gl-matrix uses flat arr[16], so element (r,c) = arr[c*4+r]"); +dumpScalar("transform[0,0]", transformM[0*4+0]); +dumpScalar("transform[0,1]", transformM[1*4+0]); +dumpScalar("transform[0,2]", transformM[2*4+0]); +dumpScalar("transform[0,3]", transformM[3*4+0]); +dumpScalar("transform[1,0]", transformM[0*4+1]); +dumpScalar("transform[1,3]", transformM[3*4+1]); +dumpScalar("transform[2,0]", transformM[0*4+2]); +dumpScalar("transform[2,3]", transformM[3*4+2]); +dumpScalar("transform[3,3]", transformM[3*4+3]); + +writeFileSync(OutputPath, lines.join("\n") + "\n"); +console.log("Wrote", OutputPath); diff --git a/experiments/dump_glmatrix.txt b/experiments/dump_glmatrix.txt index 80d59b3..6b3d489 100644 --- a/experiments/dump_glmatrix.txt +++ b/experiments/dump_glmatrix.txt @@ -196,3 +196,16 @@ canonical_forward: <+000.000, +000.000, +001.000> quat_z.right: <+000.326, +000.946, +000.000> quat_z.up: <-000.946, +000.326, +000.000> quat_z.forward: <+000.000, +000.000, +001.000> + +== element access [row,col] == +notes: [row,col] in math convention, element (i,j) = row i, col j +notes: gl-matrix uses flat arr[16], so element (r,c) = arr[c*4+r] +transform[0,0]: +000.599 +transform[0,1]: -002.495 +transform[0,2]: +001.870 +transform[0,3]: +010.000 +transform[1,0]: +001.741 +transform[1,3]: +020.000 +transform[2,0]: +000.781 +transform[2,3]: +030.000 +transform[3,3]: +001.000 diff --git a/experiments/dump_jolt.nim b/experiments/dump_jolt.nim index ab6adcf..616c042 100644 --- a/experiments/dump_jolt.nim +++ b/experiments/dump_jolt.nim @@ -96,6 +96,10 @@ proc quatMultiply(a, b: JoltQuat): JoltQuat = proc quatRotateVec3(q: JoltQuat, v: JoltFloat3): JoltFloat3 = joltQuatRotateVec3(q.x, q.y, q.z, q.w, v.x, v.y, v.z) +proc get(m: JoltMat44, row, col: int32): float32 = + var mm = m + joltMat44Get(addr mm, row, col) + proc main() = let physics = newPhysicsSystem( maxBodies = 1024, @@ -224,6 +228,19 @@ proc main() = lines.dumpVec3("quat_z.up", quatRotateVec3(quatZ, basisUp)) lines.dumpVec3("quat_z.forward", quatRotateVec3(quatZ, basisForward)) + lines.heading("element access [row,col]") + lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") + lines.appendLine("notes: Jolt Mat44 operator()(row, col) via C++ wrapper") + lines.dumpScalar("transform[0,0]", transformM.get(0, 0)) + lines.dumpScalar("transform[0,1]", transformM.get(0, 1)) + lines.dumpScalar("transform[0,2]", transformM.get(0, 2)) + lines.dumpScalar("transform[0,3]", transformM.get(0, 3)) + lines.dumpScalar("transform[1,0]", transformM.get(1, 0)) + lines.dumpScalar("transform[1,3]", transformM.get(1, 3)) + lines.dumpScalar("transform[2,0]", transformM.get(2, 0)) + lines.dumpScalar("transform[2,3]", transformM.get(2, 3)) + lines.dumpScalar("transform[3,3]", transformM.get(3, 3)) + writeFile(OutputPath, lines.join("\n") & "\n") physics.destroy() echo "Wrote ", OutputPath diff --git a/experiments/dump_jolt.txt b/experiments/dump_jolt.txt index 80d59b3..976f0bf 100644 --- a/experiments/dump_jolt.txt +++ b/experiments/dump_jolt.txt @@ -196,3 +196,16 @@ canonical_forward: <+000.000, +000.000, +001.000> quat_z.right: <+000.326, +000.946, +000.000> quat_z.up: <-000.946, +000.326, +000.000> quat_z.forward: <+000.000, +000.000, +001.000> + +== element access [row,col] == +notes: [row,col] in math convention, element (i,j) = row i, col j +notes: Jolt Mat44 operator()(row, col) via C++ wrapper +transform[0,0]: +000.599 +transform[0,1]: -002.495 +transform[0,2]: +001.870 +transform[0,3]: +010.000 +transform[1,0]: +001.741 +transform[1,3]: +020.000 +transform[2,0]: +000.781 +transform[2,3]: +030.000 +transform[3,3]: +001.000 diff --git a/experiments/dump_vmath.nim b/experiments/dump_vmath.nim index 1b4a6ea..5d64952 100644 --- a/experiments/dump_vmath.nim +++ b/experiments/dump_vmath.nim @@ -51,13 +51,13 @@ proc dumpQuat(lines: var seq[string], label: string, value: Quat) = proc dumpMat4(lines: var seq[string], label: string, value: Mat4) = lines.appendLine(label & ":") lines.appendLine("[") - for row in 0 ..< 4: + for col in 0 ..< 4: lines.appendLine( " " & - fmt(value[row, 0]) & " " & - fmt(value[row, 1]) & " " & - fmt(value[row, 2]) & " " & - fmt(value[row, 3]) + fmt(value[0, col]) & " " & + fmt(value[1, col]) & " " & + fmt(value[2, col]) & " " & + fmt(value[3, col]) ) lines.appendLine("]") @@ -66,11 +66,25 @@ proc heading(lines: var seq[string], title: string) = lines.appendLine() lines.appendLine("== " & title & " ==") +proc mat4FromRows( + m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33: float32 +): Mat4 = + ## Create a mat4 from row-major input (matching math notation). + mat4( + m00, m10, m20, m30, + m01, m11, m21, m31, + m02, m12, m22, m32, + m03, m13, m23, m33 + ) + proc rotationOnlyCopy(value: Mat4): Mat4 = result = value - result[3, 0] = 0 - result[3, 1] = 0 - result[3, 2] = 0 + result[0, 3] = 0 + result[1, 3] = 0 + result[2, 3] = 0 proc main() = var lines: seq[string] @@ -80,13 +94,13 @@ proc main() = angleB = -23'f32.toRadians angleC = 71'f32.toRadians - matA = mat4( + matA = mat4FromRows( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0 ) - matB = mat4( + matB = mat4FromRows( 0.5, -1.0, 2.0, 0.25, 1.5, 0.75, -0.5, 2.0, -3.0, 4.0, 1.25, -2.5, @@ -191,6 +205,18 @@ proc main() = lines.dumpVec3("quat_z.up", quatRotate(quatZ, basisUp)) lines.dumpVec3("quat_z.forward", quatRotate(quatZ, basisForward)) + lines.heading("element access [row,col]") + lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") + lines.dumpScalar("transform[0,0]", transformM[0, 0]) + lines.dumpScalar("transform[0,1]", transformM[0, 1]) + lines.dumpScalar("transform[0,2]", transformM[0, 2]) + lines.dumpScalar("transform[0,3]", transformM[0, 3]) + lines.dumpScalar("transform[1,0]", transformM[1, 0]) + lines.dumpScalar("transform[1,3]", transformM[1, 3]) + lines.dumpScalar("transform[2,0]", transformM[2, 0]) + lines.dumpScalar("transform[2,3]", transformM[2, 3]) + lines.dumpScalar("transform[3,3]", transformM[3, 3]) + writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/experiments/dump_vmath.txt b/experiments/dump_vmath.txt index 80d59b3..2ebf34c 100644 --- a/experiments/dump_vmath.txt +++ b/experiments/dump_vmath.txt @@ -196,3 +196,15 @@ canonical_forward: <+000.000, +000.000, +001.000> quat_z.right: <+000.326, +000.946, +000.000> quat_z.up: <-000.946, +000.326, +000.000> quat_z.forward: <+000.000, +000.000, +001.000> + +== element access [row,col] == +notes: [row,col] in math convention, element (i,j) = row i, col j +transform[0,0]: +000.599 +transform[0,1]: -002.495 +transform[0,2]: +001.870 +transform[0,3]: +010.000 +transform[1,0]: +001.741 +transform[1,3]: +020.000 +transform[2,0]: +000.781 +transform[2,3]: +030.000 +transform[3,3]: +001.000 diff --git a/src/vmath.nim b/src/vmath.nim index 5297f79..d7ed298 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -121,16 +121,16 @@ when defined(vmathArrayBased): [m30, m31, m32, m33] ] - template `[]`*[T](a: GMat234[T], i, j: int): T = a[i][j] + template `[]`*[T](a: GMat234[T], i, j: int): T = a[j][i] template `[]=`*[T](a: var GMat2[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (i * 2 + j) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (j * 2 + i) * sizeof(T))[] = v template `[]=`*[T](a: var GMat3[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (i * 3 + j) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (j * 3 + i) * sizeof(T))[] = v template `[]=`*[T](a: var GMat4[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (i * 4 + j) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (j * 4 + i) * sizeof(T))[] = v elif defined(vmathObjBased): type @@ -207,42 +207,42 @@ elif defined(vmathObjBased): result.m30 = m30; result.m31 = m31; result.m32 = m32; result.m33 = m33 template `[]`*[T](a: GMat2[T], i, j: int): T = - cast[array[4, T]](a)[i * 2 + j] + cast[array[4, T]](a)[j * 2 + i] template `[]`*[T](a: GMat3[T], i, j: int): T = - cast[array[9, T]](a)[i * 3 + j] + cast[array[9, T]](a)[j * 3 + i] template `[]`*[T](a: GMat4[T], i, j: int): T = - cast[array[16, T]](a)[i * 4 + j] + cast[array[16, T]](a)[j * 4 + i] template `[]=`*[T](a: var GMat2[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (i * 2 + j) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (j * 2 + i) * sizeof(T))[] = v template `[]=`*[T](a: var GMat3[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (i * 3 + j) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (j * 3 + i) * sizeof(T))[] = v template `[]=`*[T](a: var GMat4[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (i * 4 + j) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (j * 4 + i) * sizeof(T))[] = v template `[]`*[T](a: GMat2[T], i: int): GVec2[T] = gvec2[T]( - a[i, 0], - a[i, 1] + a[0, i], + a[1, i] ) template `[]`*[T](a: GMat3[T], i: int): GVec3[T] = gvec3[T]( - a[i, 0], - a[i, 1], - a[i, 2] + a[0, i], + a[1, i], + a[2, i] ) template `[]`*[T](a: GMat4[T], i: int): GVec4[T] = gvec4[T]( - a[i, 0], - a[i, 1], - a[i, 2], - a[i, 3] + a[0, i], + a[1, i], + a[2, i], + a[3, i] ) elif true or defined(vmathObjArrayBased): @@ -338,33 +338,33 @@ elif true or defined(vmathObjArrayBased): m30, m31, m32, m33 ]) - template `[]`*[T](a: GMat2[T], i, j: int): T = a.arr[i * 2 + j] - template `[]`*[T](a: GMat3[T], i, j: int): T = a.arr[i * 3 + j] - template `[]`*[T](a: GMat4[T], i, j: int): T = a.arr[i * 4 + j] + template `[]`*[T](a: GMat2[T], i, j: int): T = a.arr[j * 2 + i] + template `[]`*[T](a: GMat3[T], i, j: int): T = a.arr[j * 3 + i] + template `[]`*[T](a: GMat4[T], i, j: int): T = a.arr[j * 4 + i] - template `[]=`*[T](a: var GMat2[T], i, j: int, v: T) = a.arr[i * 2 + j] = v - template `[]=`*[T](a: var GMat3[T], i, j: int, v: T) = a.arr[i * 3 + j] = v - template `[]=`*[T](a: var GMat4[T], i, j: int, v: T) = a.arr[i * 4 + j] = v + template `[]=`*[T](a: var GMat2[T], i, j: int, v: T) = a.arr[j * 2 + i] = v + template `[]=`*[T](a: var GMat3[T], i, j: int, v: T) = a.arr[j * 3 + i] = v + template `[]=`*[T](a: var GMat4[T], i, j: int, v: T) = a.arr[j * 4 + i] = v template `[]`*[T](a: GMat2[T], i: int): GVec2[T] = gvec2[T]( - a[i, 0], - a[i, 1] + a[0, i], + a[1, i] ) template `[]`*[T](a: GMat3[T], i: int): GVec3[T] = gvec3[T]( - a[i, 0], - a[i, 1], - a[i, 2] + a[0, i], + a[1, i], + a[2, i] ) template `[]`*[T](a: GMat4[T], i: int): GVec4[T] = gvec4[T]( - a[i, 0], - a[i, 1], - a[i, 2], - a[i, 3] + a[0, i], + a[1, i], + a[2, i], + a[3, i] ) type @@ -913,7 +913,7 @@ proc matToString[T](a: T, n: int): string = for x in 0 ..< n: result.add " " for y in 0 ..< n: - result.add $a[x, y] & ", " + result.add $a[y, x] & ", " result.setLen(result.len - 1) result.add "\n" result.setLen(result.len - 2) @@ -925,17 +925,14 @@ template genMatConstructor*(lower, upper, T: untyped) = m00, m01, m10, m11: T ): `upper 2` = - result[0, 0] = m00; result[1, 0] = m01 - result[0, 1] = m10; result[1, 1] = m11 + gmat2[T](m00, m01, m10, m11) proc `lower 3`*( m00, m01, m02, m10, m11, m12, m20, m21, m22: T ): `upper 3` = - result[0, 0] = m00; result[1, 0] = m01; result[2, 0] = m02 - result[0, 1] = m10; result[1, 1] = m11; result[2, 1] = m12 - result[0, 2] = m20; result[1, 2] = m21; result[2, 2] = m22 + gmat3[T](m00, m01, m02, m10, m11, m12, m20, m21, m22) proc `lower 4`*( m00, m01, m02, m03, @@ -943,35 +940,25 @@ template genMatConstructor*(lower, upper, T: untyped) = m20, m21, m22, m23, m30, m31, m32, m33: T ): `upper 4` = - result[0, 0] = m00; result[1, 0] = m01 - result[2, 0] = m02; result[3, 0] = m03 - - result[0, 1] = m10; result[1, 1] = m11 - result[2, 1] = m12; result[3, 1] = m13 - - result[0, 2] = m20; result[1, 2] = m21 - result[2, 2] = m22; result[3, 2] = m23 - - result[0, 3] = m30; result[1, 3] = m31 - result[2, 3] = m32; result[3, 3] = m33 + gmat4[T](m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) proc `lower 2`*(a, b: GVec2[T]): `upper 2` = gmat2[T]( - a.x, b.x, - a.y, b.y + a.x, a.y, + b.x, b.y ) proc `lower 3`*(a, b, c: GVec3[T]): `upper 3` = gmat3[T]( - a.x, b.x, c.x, - a.y, b.y, c.y, - a.z, b.z, c.z, + a.x, a.y, a.z, + b.x, b.y, b.z, + c.x, c.y, c.z, ) proc `lower 4`*(a, b, c, d: GVec4[T]): `upper 4` = gmat4[T]( - a.x, b.x, c.x, d.x, - a.y, b.y, c.y, d.y, - a.z, b.z, c.z, d.z, - a.w, b.w, c.w, d.w, + a.x, a.y, a.z, a.w, + b.x, b.y, b.z, b.w, + c.x, c.y, c.z, c.w, + d.x, d.y, d.z, d.w, ) proc `lower 2`*(): `upper 2` = @@ -1010,16 +997,16 @@ proc `~=`*[T](a, b: GMat4[T]): bool = a[0] ~= b[0] and a[1] ~= b[1] and a[2] ~= b[2] and a[3] ~= b[3] proc pos*[T](a: GMat3[T]): GVec2[T] = - gvec2[T](a[2, 0], a[2, 1]) + gvec2[T](a[0, 2], a[1, 2]) proc `pos=`*[T](a: var GMat3[T], pos: GVec2[T]) = - a[2, 0] = pos.x - a[2, 1] = pos.y + a[0, 2] = pos.x + a[1, 2] = 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.x = a[0, 2] + result.y = a[1, 2] result.z = a[2, 2] proc back*[T](a: GMat4[T]): GVec3[T] {.inline.} = @@ -1033,14 +1020,14 @@ proc left*[T](a: GMat4[T]): GVec3[T] {.inline.} = proc right*[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] + result.y = a[1, 0] + result.z = a[2, 0] proc up*[T](a: GMat4[T]): GVec3[T] {.inline.} = ## Vector facing +Y. - result.x = a[1, 0] + result.x = a[0, 1] result.y = a[1, 1] - result.z = a[1, 2] + result.z = a[2, 1] proc down*[T](a: GMat4[T]): GVec3[T] {.inline.} = ## Vector facing -X. @@ -1048,183 +1035,183 @@ proc down*[T](a: GMat4[T]): GVec3[T] {.inline.} = proc pos*[T](a: GMat4[T]): GVec3[T] = ## Position of the matrix. - gvec3[T](a[3, 0], a[3, 1], a[3, 2]) + gvec3[T](a[0, 3], a[1, 3], a[2, 3]) 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 + a[0, 3] = pos.x + a[1, 3] = pos.y + a[2, 3] = pos.z proc `*`*[T](a, b: GMat3[T]): GMat3[T] = let a00 = a[0, 0] - a01 = a[0, 1] - a02 = a[0, 2] a10 = a[1, 0] - a11 = a[1, 1] - a12 = a[1, 2] a20 = a[2, 0] + a01 = a[0, 1] + a11 = a[1, 1] a21 = a[2, 1] + a02 = a[0, 2] + a12 = a[1, 2] a22 = a[2, 2] let b00 = b[0, 0] - b01 = b[0, 1] - b02 = b[0, 2] b10 = b[1, 0] - b11 = b[1, 1] - b12 = b[1, 2] b20 = b[2, 0] + b01 = b[0, 1] + b11 = b[1, 1] b21 = b[2, 1] + b02 = b[0, 2] + b12 = b[1, 2] b22 = b[2, 2] - result[0, 0] = a00 * b00 + a10 * b01 + a20 * b02 - result[0, 1] = a01 * b00 + a11 * b01 + a21 * b02 - result[0, 2] = a02 * b00 + a12 * b01 + a22 * b02 + result[0, 0] = a00 * b00 + a01 * b10 + a02 * b20 + result[1, 0] = a10 * b00 + a11 * b10 + a12 * b20 + result[2, 0] = a20 * b00 + a21 * b10 + a22 * b20 - result[1, 0] = a00 * b10 + a10 * b11 + a20 * b12 - result[1, 1] = a01 * b10 + a11 * b11 + a21 * b12 - result[1, 2] = a02 * b10 + a12 * b11 + a22 * b12 + result[0, 1] = a00 * b01 + a01 * b11 + a02 * b21 + result[1, 1] = a10 * b01 + a11 * b11 + a12 * b21 + result[2, 1] = a20 * b01 + a21 * b11 + a22 * b21 - result[2, 0] = a00 * b20 + a10 * b21 + a20 * b22 - result[2, 1] = a01 * b20 + a11 * b21 + a21 * b22 - result[2, 2] = a02 * b20 + a12 * b21 + a22 * b22 + result[0, 2] = a00 * b02 + a01 * b12 + a02 * b22 + result[1, 2] = a10 * b02 + a11 * b12 + a12 * b22 + result[2, 2] = a20 * b02 + a21 * b12 + a22 * b22 proc `*`*[T](a: GMat2[T], b: GVec2[T]): GVec2[T] = gvec2[T]( - a[0, 0] * b.x + a[1, 0] * b.y, - a[0, 1] * b.x + a[1, 1] * b.y + a[0, 0] * b.x + a[0, 1] * b.y, + a[1, 0] * b.x + a[1, 1] * b.y ) proc `*`*[T](a: GMat3[T], b: GVec2[T]): GVec2[T] = gvec2[T]( - a[0, 0] * b.x + a[1, 0] * b.y + a[2, 0], - a[0, 1] * b.x + a[1, 1] * b.y + a[2, 1] + a[0, 0] * b.x + a[0, 1] * b.y + a[0, 2], + a[1, 0] * b.x + a[1, 1] * b.y + a[1, 2] ) proc `*`*[T](a: GMat3[T], b: GVec3[T]): GVec3[T] = gvec3[T]( - a[0, 0] * b.x + a[1, 0] * b.y + a[2, 0] * b.z, - a[0, 1] * b.x + a[1, 1] * b.y + a[2, 1] * b.z, - a[0, 2] * b.x + a[1, 2] * b.y + a[2, 2] * b.z, + a[0, 0] * b.x + a[0, 1] * b.y + a[0, 2] * b.z, + a[1, 0] * b.x + a[1, 1] * b.y + a[1, 2] * b.z, + a[2, 0] * b.x + a[2, 1] * b.y + a[2, 2] * b.z, ) proc `*`*[T](a, b: GMat4[T]): GMat4[T] = let a00 = a[0, 0] - a01 = a[0, 1] - a02 = a[0, 2] - a03 = a[0, 3] a10 = a[1, 0] - a11 = a[1, 1] - a12 = a[1, 2] - a13 = a[1, 3] a20 = a[2, 0] - a21 = a[2, 1] - a22 = a[2, 2] - a23 = a[2, 3] a30 = a[3, 0] + a01 = a[0, 1] + a11 = a[1, 1] + a21 = a[2, 1] a31 = a[3, 1] + a02 = a[0, 2] + a12 = a[1, 2] + a22 = a[2, 2] a32 = a[3, 2] + a03 = a[0, 3] + a13 = a[1, 3] + a23 = a[2, 3] a33 = a[3, 3] let b00 = b[0, 0] - b01 = b[0, 1] - b02 = b[0, 2] - b03 = b[0, 3] b10 = b[1, 0] - b11 = b[1, 1] - b12 = b[1, 2] - b13 = b[1, 3] b20 = b[2, 0] - b21 = b[2, 1] - b22 = b[2, 2] - b23 = b[2, 3] b30 = b[3, 0] + b01 = b[0, 1] + b11 = b[1, 1] + b21 = b[2, 1] b31 = b[3, 1] + b02 = b[0, 2] + b12 = b[1, 2] + b22 = b[2, 2] b32 = b[3, 2] + b03 = b[0, 3] + b13 = b[1, 3] + b23 = b[2, 3] b33 = b[3, 3] - result[0, 0] = a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03 - result[0, 1] = a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03 - result[0, 2] = a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03 - result[0, 3] = a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03 + result[0, 0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30 + result[1, 0] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30 + result[2, 0] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30 + result[3, 0] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30 - result[1, 0] = a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13 - result[1, 1] = a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13 - result[1, 2] = a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13 - result[1, 3] = a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13 + result[0, 1] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31 + result[1, 1] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31 + result[2, 1] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31 + result[3, 1] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31 - result[2, 0] = a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23 - result[2, 1] = a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23 - result[2, 2] = a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23 - result[2, 3] = a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23 + result[0, 2] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32 + result[1, 2] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32 + result[2, 2] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32 + result[3, 2] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32 - result[3, 0] = a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33 - result[3, 1] = a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33 - result[3, 2] = a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33 - result[3, 3] = a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33 + result[0, 3] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33 + result[1, 3] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33 + result[2, 3] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33 + result[3, 3] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33 proc `*`*[T](a: GMat4[T], b: GVec3[T]): GVec3[T] = gvec3[T]( - a[0, 0] * b.x + a[1, 0] * b.y + a[2, 0] * b.z + a[3, 0], - a[0, 1] * b.x + a[1, 1] * b.y + a[2, 1] * b.z + a[3, 1], - a[0, 2] * b.x + a[1, 2] * b.y + a[2, 2] * b.z + a[3, 2] + a[0, 0] * b.x + a[0, 1] * b.y + a[0, 2] * b.z + a[0, 3], + a[1, 0] * b.x + a[1, 1] * b.y + a[1, 2] * b.z + a[1, 3], + a[2, 0] * b.x + a[2, 1] * b.y + a[2, 2] * b.z + a[2, 3] ) proc `*`*[T](a: GMat4[T], b: GVec4[T]): GVec4[T] = gvec4[T]( - a[0, 0] * b.x + a[1, 0] * b.y + a[2, 0] * b.z + a[3, 0] * b.w, - a[0, 1] * b.x + a[1, 1] * b.y + a[2, 1] * b.z + a[3, 1] * b.w, - a[0, 2] * b.x + a[1, 2] * b.y + a[2, 2] * b.z + a[3, 2] * b.w, - a[0, 3] * b.x + a[1, 3] * b.y + a[2, 3] * b.z + a[3, 3] * b.w + a[0, 0] * b.x + a[0, 1] * b.y + a[0, 2] * b.z + a[0, 3] * b.w, + a[1, 0] * b.x + a[1, 1] * b.y + a[1, 2] * b.z + a[1, 3] * b.w, + a[2, 0] * b.x + a[2, 1] * b.y + a[2, 2] * b.z + a[2, 3] * b.w, + a[3, 0] * b.x + a[3, 1] * b.y + a[3, 2] * b.z + a[3, 3] * b.w ) proc transpose*[T](a: GMat3[T]): GMat3[T] = - ## Return an transpose of the matrix. + ## Return a transpose of the matrix. gmat3[T]( - a[0, 0], a[1, 0], a[2, 0], - a[0, 1], a[1, 1], a[2, 1], - a[0, 2], a[1, 2], a[2, 2] + a[0, 0], a[0, 1], a[0, 2], + a[1, 0], a[1, 1], a[1, 2], + a[2, 0], a[2, 1], a[2, 2] ) proc transpose*[T](a: GMat4[T]): GMat4[T] = - ## Return an transpose of the matrix. + ## Return a transpose of the matrix. gmat4[T]( - a[0, 0], a[1, 0], a[2, 0], a[3, 0], - a[0, 1], a[1, 1], a[2, 1], a[3, 1], - a[0, 2], a[1, 2], a[2, 2], a[3, 2], - a[0, 3], a[1, 3], a[2, 3], a[3, 3] + a[0, 0], a[0, 1], a[0, 2], a[0, 3], + a[1, 0], a[1, 1], a[1, 2], a[1, 3], + a[2, 0], a[2, 1], a[2, 2], a[2, 3], + a[3, 0], a[3, 1], a[3, 2], a[3, 3] ) proc determinant*[T](a: GMat3[T]): T = ## Compute a determinant of the matrix. ( - a[0, 0] * (a[1, 1] * a[2, 2] - a[2, 1] * a[1, 2]) - - a[0, 1] * (a[1, 0] * a[2, 2] - a[1, 2] * a[2, 0]) + - a[0, 2] * (a[1, 0] * a[2, 1] - a[1, 1] * a[2, 0]) + a[0, 0] * (a[1, 1] * a[2, 2] - a[1, 2] * a[2, 1]) - + a[1, 0] * (a[0, 1] * a[2, 2] - a[2, 1] * a[0, 2]) + + a[2, 0] * (a[0, 1] * a[1, 2] - a[1, 1] * a[0, 2]) ) proc determinant*[T](a: GMat4[T]): T = ## Compute a determinant of the matrix. let a00 = a[0, 0] - a01 = a[0, 1] - a02 = a[0, 2] - a03 = a[0, 3] - a10 = a[1, 0] + a01 = a[1, 0] + a02 = a[2, 0] + a03 = a[3, 0] + a10 = a[0, 1] a11 = a[1, 1] - a12 = a[1, 2] - a13 = a[1, 3] - a20 = a[2, 0] - a21 = a[2, 1] + a12 = a[2, 1] + a13 = a[3, 1] + a20 = a[0, 2] + a21 = a[1, 2] a22 = a[2, 2] - a23 = a[2, 3] - a30 = a[3, 0] - a31 = a[3, 1] - a32 = a[3, 2] + a23 = a[3, 2] + a30 = a[0, 3] + a31 = a[1, 3] + a32 = a[2, 3] a33 = a[3, 3] ( a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + @@ -1240,36 +1227,36 @@ proc inverse*[T](a: GMat3[T]): GMat3[T] = let invDet = 1 / a.determinant - result[0, 0] = +(a[1, 1] * a[2, 2] - a[2, 1] * a[1, 2]) * invDet + result[0, 0] = +(a[1, 1] * a[2, 2] - a[1, 2] * a[2, 1]) * invDet result[0, 1] = -(a[0, 1] * a[2, 2] - a[0, 2] * a[2, 1]) * invDet result[0, 2] = +(a[0, 1] * a[1, 2] - a[0, 2] * a[1, 1]) * invDet result[1, 0] = -(a[1, 0] * a[2, 2] - a[1, 2] * a[2, 0]) * invDet result[1, 1] = +(a[0, 0] * a[2, 2] - a[0, 2] * a[2, 0]) * invDet - result[1, 2] = -(a[0, 0] * a[1, 2] - a[1, 0] * a[0, 2]) * invDet + result[1, 2] = -(a[0, 0] * a[1, 2] - a[0, 2] * a[1, 0]) * invDet result[2, 0] = +(a[1, 0] * a[2, 1] - a[2, 0] * a[1, 1]) * invDet - result[2, 1] = -(a[0, 0] * a[2, 1] - a[2, 0] * a[0, 1]) * invDet - result[2, 2] = +(a[0, 0] * a[1, 1] - a[1, 0] * a[0, 1]) * invDet + result[2, 1] = -(a[0, 0] * a[2, 1] - a[0, 1] * a[2, 0]) * invDet + result[2, 2] = +(a[0, 0] * a[1, 1] - a[0, 1] * a[1, 0]) * invDet proc inverse*[T](a: GMat4[T]): GMat4[T] = ## Return an inverse of the matrix. let a00 = a[0, 0] - a01 = a[0, 1] - a02 = a[0, 2] - a03 = a[0, 3] - a10 = a[1, 0] + a01 = a[1, 0] + a02 = a[2, 0] + a03 = a[3, 0] + a10 = a[0, 1] a11 = a[1, 1] - a12 = a[1, 2] - a13 = a[1, 3] - a20 = a[2, 0] - a21 = a[2, 1] + a12 = a[2, 1] + a13 = a[3, 1] + a20 = a[0, 2] + a21 = a[1, 2] a22 = a[2, 2] - a23 = a[2, 3] - a30 = a[3, 0] - a31 = a[3, 1] - a32 = a[3, 2] + a23 = a[3, 2] + a30 = a[0, 3] + a31 = a[1, 3] + a32 = a[2, 3] a33 = a[3, 3] let @@ -1290,23 +1277,23 @@ proc inverse*[T](a: GMat4[T]): GMat4[T] = let invDet = 1 / a.determinant result[0, 0] = (+a11 * b11 - a12 * b10 + a13 * b09) * invDet - result[0, 1] = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet - result[0, 2] = (+a31 * b05 - a32 * b04 + a33 * b03) * invDet - result[0, 3] = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet + result[1, 0] = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet + result[2, 0] = (+a31 * b05 - a32 * b04 + a33 * b03) * invDet + result[3, 0] = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet - result[1, 0] = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet + result[0, 1] = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet result[1, 1] = (+a00 * b11 - a02 * b08 + a03 * b07) * invDet - result[1, 2] = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet - result[1, 3] = (+a20 * b05 - a22 * b02 + a23 * b01) * invDet + result[2, 1] = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet + result[3, 1] = (+a20 * b05 - a22 * b02 + a23 * b01) * invDet - result[2, 0] = (+a10 * b10 - a11 * b08 + a13 * b06) * invDet - result[2, 1] = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet + result[0, 2] = (+a10 * b10 - a11 * b08 + a13 * b06) * invDet + result[1, 2] = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet result[2, 2] = (+a30 * b04 - a31 * b02 + a33 * b00) * invDet - result[2, 3] = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet + result[3, 2] = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet - result[3, 0] = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet - result[3, 1] = (+a00 * b09 - a01 * b07 + a02 * b06) * invDet - result[3, 2] = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet + result[0, 3] = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet + result[1, 3] = (+a00 * b09 - a01 * b07 + a02 * b06) * invDet + result[2, 3] = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet result[3, 3] = (+a20 * b03 - a21 * b01 + a22 * b00) * invDet proc scale*[T](v: GVec2[T]): GMat3[T] = @@ -1337,23 +1324,23 @@ proc translate*[T](v: GVec2[T]): GMat3[T] = proc translate*[T](v: GVec3[T]): GMat4[T] = ## Create translation matrix. result[0, 0] = 1 - result[1, 0] = 0 - result[2, 0] = 0 - result[3, 0] = v.x - result[0, 1] = 0 - result[1, 1] = 1 - result[2, 1] = 0 - result[3, 1] = v.y - result[0, 2] = 0 + result[0, 3] = v.x + + result[1, 0] = 0 + result[1, 1] = 1 result[1, 2] = 0 + result[1, 3] = v.y + + result[2, 0] = 0 + result[2, 1] = 0 result[2, 2] = 1 - result[3, 2] = v.z + result[2, 3] = v.z - result[0, 3] = 0 - result[1, 3] = 0 - result[2, 3] = 0 + result[3, 0] = 0 + result[3, 1] = 0 + result[3, 2] = 0 result[3, 3] = 1 proc rotate*[T](angle: T): GMat3[T] = @@ -1363,8 +1350,8 @@ proc rotate*[T](angle: T): GMat3[T] = sin = sin(angle) cos = cos(angle) gmat3[T]( - cos, -sin, 0, - sin, cos, 0, + cos, sin, 0, + -sin, cos, 0, 0, 0, 1 ) @@ -1378,69 +1365,69 @@ 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[0, 1] = 0 + result[1, 1] = cos(angle) + result[2, 1] = sin(angle) result[3, 1] = 0 + + result[0, 2] = 0 + result[1, 2] = -sin(angle) + result[2, 2] = cos(angle) result[3, 2] = 0 + + result[0, 3] = 0 + result[1, 3] = 0 + result[2, 3] = 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[3, 0] = 0 - result[2, 0] = sin(angle) + result[0, 1] = 0 + result[1, 1] = 1 result[2, 1] = 0 - result[2, 2] = cos(angle) - result[2, 3] = 0 - - result[3, 0] = 0 result[3, 1] = 0 + + result[0, 2] = sin(angle) + result[1, 2] = 0 + result[2, 2] = cos(angle) result[3, 2] = 0 + + result[0, 3] = 0 + result[1, 3] = 0 + result[2, 3] = 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[2, 0] = 0 + result[3, 0] = 0 - result[1, 0] = -sin(angle) + result[0, 1] = -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[0, 2] = 0 + result[1, 2] = 0 + result[2, 2] = 1 result[3, 2] = 0 + + result[0, 3] = 0 + result[1, 3] = 0 + result[2, 3] = 0 result[3, 3] = 1 proc toAngles*[T](a: GVec3[T]): GVec3[T] = @@ -1472,17 +1459,15 @@ proc toAngles*[T](m: GMat4[T]): GVec3[T] = ## 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]) + let sy = clamp(-m[1, 2], T(-1), T(1)) + result.x = arcsin(sy) + if abs(sy) > T(0.9999999): + # Degenerate case (gimbal lock). + result.y = arctan2(-m[2, 0], m[0, 0]) else: # Normal case. - result.y = -arctan2(m[2, 0], m[2, 2]) - result.z = -arctan2(m[0, 1], m[1, 1]) + result.y = arctan2(m[0, 2], m[2, 2]) + result.z = arctan2(m[1, 0], m[1, 1]) proc fromAngles*[T](a: GVec3[T]): GMat4[T] = ## Takes a vector containing Euler angles and returns a matrix. @@ -1497,23 +1482,23 @@ proc frustum*[T](left, right, bottom, top, near, far: T): GMat4[T] = fn = (far - near) result[0, 0] = (near * 2) / rl - result[0, 1] = 0 - result[0, 2] = 0 - result[0, 3] = 0 - result[1, 0] = 0 + result[2, 0] = 0 + result[3, 0] = 0 + + result[0, 1] = 0 result[1, 1] = (near * 2) / tb - result[1, 2] = 0 - result[1, 3] = 0 + result[2, 1] = 0 + result[3, 1] = 0 - result[2, 0] = (right + left) / rl - result[2, 1] = (top + bottom) / tb + result[0, 2] = (right + left) / rl + result[1, 2] = (top + bottom) / tb result[2, 2] = -(far + near) / fn - result[2, 3] = -1 + result[3, 2] = -1 - result[3, 0] = 0 - result[3, 1] = 0 - result[3, 2] = -(far * near * 2) / fn + result[0, 3] = 0 + result[1, 3] = 0 + result[2, 3] = -(far * near * 2) / fn result[3, 3] = 0 proc perspective*[T](fovy, aspect, near, far: T): GMat4[T] = @@ -1531,23 +1516,23 @@ proc ortho*[T](left, right, bottom, top, near, far: T): GMat4[T] = fn: T = (far - near) result[0, 0] = T(2 / rl) - result[0, 1] = 0 - result[0, 2] = 0 - result[0, 3] = 0 - result[1, 0] = 0 - result[1, 1] = T(2 / tb) - result[1, 2] = 0 - result[1, 3] = 0 - result[2, 0] = 0 + result[3, 0] = 0 + + result[0, 1] = 0 + result[1, 1] = T(2 / tb) result[2, 1] = 0 + result[3, 1] = 0 + + result[0, 2] = 0 + result[1, 2] = 0 result[2, 2] = T(-2 / fn) - result[2, 3] = 0 + result[3, 2] = 0 - result[3, 0] = T(-(left + right) / rl) - result[3, 1] = T(-(top + bottom) / tb) - result[3, 2] = T(-(far + near) / fn) + result[0, 3] = T(-(left + right) / rl) + result[1, 3] = T(-(top + bottom) / tb) + result[2, 3] = T(-(far + near) / fn) result[3, 3] = 1 proc lookAt*[T](eye, center, up: GVec3[T]): GMat4[T] @@ -1615,23 +1600,23 @@ proc lookAt*[T](eye, center, up: GVec3[T]): GMat4[T] y2 *= len result[0, 0] = x0 - result[0, 1] = y0 - result[0, 2] = z0 - result[0, 3] = 0 + result[1, 0] = y0 + result[2, 0] = z0 + result[3, 0] = 0 - result[1, 0] = x1 + result[0, 1] = x1 result[1, 1] = y1 - result[1, 2] = z1 - result[1, 3] = 0 + result[2, 1] = z1 + result[3, 1] = 0 - result[2, 0] = x2 - result[2, 1] = y2 + result[0, 2] = x2 + result[1, 2] = y2 result[2, 2] = z2 - result[2, 3] = 0 + result[3, 2] = 0 - result[3, 0] = -(x0 * eyex + x1 * eyey + x2 * eyez) - result[3, 1] = -(y0 * eyex + y1 * eyey + y2 * eyez) - result[3, 2] = -(z0 * eyex + z1 * eyey + z2 * eyez) + result[0, 3] = -(x0 * eyex + x1 * eyey + x2 * eyez) + result[1, 3] = -(y0 * eyex + y1 * eyey + y2 * eyez) + result[2, 3] = -(z0 * eyex + z1 * eyey + z2 * eyez) result[3, 3] = 1 proc lookAt*[T](eye, center: GVec3[T]): GMat4[T] @@ -1734,8 +1719,8 @@ proc fromTwoVectors*[T](a, b: GVec3[T]): GVec4[T] = let half = normalize(u + v) - q = cross(u, half) - w = dot(u, half) + q = cross(v, half) + w = dot(v, half) return gvec4(q.x, q.y, q.z, w) proc nlerp*(a: Quat, b: Quat, v: float32): Quat = @@ -1748,15 +1733,15 @@ proc quat*[T](m: GMat4[T]): GVec4[T] = ## Create a quaternion from matrix. let m00 = m[0, 0] - m01 = m[0, 1] - m02 = m[0, 2] + m01 = m[1, 0] + m02 = m[2, 0] - m10 = m[1, 0] + m10 = m[0, 1] m11 = m[1, 1] - m12 = m[1, 2] + m12 = m[2, 1] - m20 = m[2, 0] - m21 = m[2, 1] + m20 = m[0, 2] + m21 = m[1, 2] m22 = m[2, 2] fourXSquaredMinus1 = m00 - m11 - m22 @@ -1820,44 +1805,31 @@ proc mat4*[T](q: GVec4[T]): GMat4[T] = zw = q.z * q.w result[0, 0] = 1 - 2 * (yy + zz) - result[0, 1] = 0 + 2 * (xy + zw) - result[0, 2] = 0 + 2 * (xz - yw) - result[0, 3] = 0 + result[1, 0] = 0 + 2 * (xy + zw) + result[2, 0] = 0 + 2 * (xz - yw) + result[3, 0] = 0 - result[1, 0] = 0 + 2 * (xy - zw) + result[0, 1] = 0 + 2 * (xy - zw) result[1, 1] = 1 - 2 * (xx + zz) - result[1, 2] = 0 + 2 * (yz + xw) - result[1, 3] = 0 + result[2, 1] = 0 + 2 * (yz + xw) + result[3, 1] = 0 - result[2, 0] = 0 + 2 * (xz + yw) - result[2, 1] = 0 + 2 * (yz - xw) + result[0, 2] = 0 + 2 * (xz + yw) + result[1, 2] = 0 + 2 * (yz - xw) result[2, 2] = 1 - 2 * (xx + yy) - result[2, 3] = 0 - - result[3, 0] = 0 - result[3, 1] = 0 result[3, 2] = 0 + + result[0, 3] = 0 + result[1, 3] = 0 + result[2, 3] = 0 result[3, 3] = 1.0 proc mat4*(m: DMat4): Mat4 {.inline.} = ## Convert a double precision matrix to a single precision matrix. - result[0, 0] = float32(m[0, 0]) - result[0, 1] = float32(m[0, 1]) - result[0, 2] = float32(m[0, 2]) - result[0, 3] = float32(m[0, 3]) - result[1, 0] = float32(m[1, 0]) - result[1, 1] = float32(m[1, 1]) - result[1, 2] = float32(m[1, 2]) - result[1, 3] = float32(m[1, 3]) - result[2, 0] = float32(m[2, 0]) - result[2, 1] = float32(m[2, 1]) - result[2, 2] = float32(m[2, 2]) - result[2, 3] = float32(m[2, 3]) - result[3, 0] = float32(m[3, 0]) - result[3, 1] = float32(m[3, 1]) - result[3, 2] = float32(m[3, 2]) - result[3, 3] = float32(m[3, 3]) + for r in 0 ..< 4: + for c in 0 ..< 4: + result[r, c] = float32(m[r, c]) proc mat4*(m: Mat4): Mat4 {.inline.} = ## Convert a double precision matrix to a single precision matrix. @@ -1865,22 +1837,9 @@ proc mat4*(m: Mat4): Mat4 {.inline.} = proc dmat4*(m: Mat4): DMat4 {.inline.} = ## Convert a single precision matrix to a double precision matrix. - result[0, 0] = float64(m[0, 0]) - result[0, 1] = float64(m[0, 1]) - result[0, 2] = float64(m[0, 2]) - result[0, 3] = float64(m[0, 3]) - result[1, 0] = float64(m[1, 0]) - result[1, 1] = float64(m[1, 1]) - result[1, 2] = float64(m[1, 2]) - result[1, 3] = float64(m[1, 3]) - result[2, 0] = float64(m[2, 0]) - result[2, 1] = float64(m[2, 1]) - result[2, 2] = float64(m[2, 2]) - result[2, 3] = float64(m[2, 3]) - result[3, 0] = float64(m[3, 0]) - result[3, 1] = float64(m[3, 1]) - result[3, 2] = float64(m[3, 2]) - result[3, 3] = float64(m[3, 3]) + for r in 0 ..< 4: + for c in 0 ..< 4: + result[r, c] = float64(m[r, c]) proc dmat4*(m: DMat4): DMat4 {.inline.} = ## Convert a double precision matrix to a double precision matrix. diff --git a/tests/test.nim b/tests/test.nim index 08ebbf5..5cf449c 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -667,8 +667,8 @@ block: ) doAssert rotate(1.0) ~= dmat3( - 0.5403023058681398, -0.8414709848078965, 0.0, - 0.8414709848078965, 0.5403023058681398, 0.0, + 0.5403023058681398, 0.8414709848078965, 0.0, + -0.8414709848078965, 0.5403023058681398, 0.0, 0.0, 0.0, 1.0 ) @@ -697,15 +697,15 @@ block: ) doAssert rotate(1.0).inverse() ~= dmat3( - 0.5403023058681398, 0.8414709848078965, -0.0, - -0.8414709848078965, 0.5403023058681398, -0.0, + 0.5403023058681398, -0.8414709848078965, -0.0, + 0.8414709848078965, 0.5403023058681398, -0.0, 0.0, -0.0, 1.0 ) doAssert rotate(1.0, dvec3(1, 0, 0)).inverse() ~= dmat4( 1.0, 0.0, 0.0, 0.0, - 0.0, 0.5403023058681398, 0.8414709848078965, 0.0, - 0.0, -0.8414709848078965, 0.5403023058681398, -0.0, - 0.0, 0.0, 0.0, 1.0 + 0.0, 0.5403023058681398, -0.8414709848078965, 0.0, + 0.0, 0.8414709848078965, 0.5403023058681398, 0.0, + 0.0, 0.0, -0.0, 1.0 ) @@ -911,14 +911,14 @@ block: let mat3d = translate(vec3(10, 20, 0)) * rotateZ(45.toRadians) * scale(vec3(2)) doAssert mat2d ~= mat3( - 1.414213538169861, -1.414213538169861, 0.0, 1.414213538169861, 1.414213538169861, 0.0, + -1.414213538169861, 1.414213538169861, 0.0, 10.0, 20.0, 1.0 ) doAssert mat3d ~= mat4( - 1.414213418960571, -1.41421365737915, 0.0, 0.0, - 1.41421365737915, 1.414213418960571, 0.0, 0.0, + 1.414213418960571, 1.41421365737915, 0.0, 0.0, + -1.41421365737915, 1.414213418960571, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 10.0, 20.0, 0.0, 1.0 ) @@ -1102,8 +1102,8 @@ block: 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.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) @@ -1153,9 +1153,9 @@ block: 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 + else: + doAssert abs(angleBetween(a.y, b.y + b.z)) < 0.001 block: # Test for https://github.com/treeform/vmath/issues/73 diff --git a/tests/test_quaternion.nim b/tests/test_quaternion.nim index 5a454b8..271fdec 100644 --- a/tests/test_quaternion.nim +++ b/tests/test_quaternion.nim @@ -43,9 +43,9 @@ block: qy = quatRotateY(-0.91) qz = quatRotateZ(1.24) - mxy = rotateY(-0.91) * rotateX(0.37) - myz = rotateZ(1.24) * rotateY(-0.91) - mxyz = rotateZ(1.24) * rotateY(-0.91) * rotateX(0.37) + mxy = rotateX(0.37) * rotateY(-0.91) + myz = rotateY(-0.91) * rotateZ(1.24) + mxyz = rotateX(0.37) * rotateY(-0.91) * rotateZ(1.24) doAssert quatMultiply(qx, qy).mat4() ~= mxy doAssert quatMultiply(qy, qz).mat4() ~= myz @@ -58,15 +58,15 @@ block: y = dvec3(0, 1, 0) z = dvec3(0, 0, 1) - doAssert quatRotateY(PI / 2).mat4() * x ~= dvec3(0, 0, 1) - doAssert quatRotateX(PI / 2).mat4() * y ~= dvec3(0, 0, -1) - doAssert quatRotateZ(PI / 2).mat4() * x ~= dvec3(0, -1, 0) - doAssert quatRotate(quatRotateY(PI / 2), x) ~= dvec3(0, 0, 1) - doAssert quatRotate(quatRotateX(PI / 2), y) ~= dvec3(0, 0, -1) - doAssert quatRotate(quatRotateZ(PI / 2), x) ~= dvec3(0, -1, 0) - doAssert quatRotateY(PI / 2) * x ~= dvec3(0, 0, 1) - doAssert quatRotateX(PI / 2) * y ~= dvec3(0, 0, -1) - doAssert quatRotateZ(PI / 2) * x ~= dvec3(0, -1, 0) + doAssert quatRotateY(PI / 2).mat4() * x ~= dvec3(0, 0, -1) + doAssert quatRotateX(PI / 2).mat4() * y ~= dvec3(0, 0, 1) + doAssert quatRotateZ(PI / 2).mat4() * x ~= dvec3(0, 1, 0) + doAssert quatRotate(quatRotateY(PI / 2), x) ~= dvec3(0, 0, -1) + doAssert quatRotate(quatRotateX(PI / 2), y) ~= dvec3(0, 0, 1) + doAssert quatRotate(quatRotateZ(PI / 2), x) ~= dvec3(0, 1, 0) + doAssert quatRotateY(PI / 2) * x ~= dvec3(0, 0, -1) + doAssert quatRotateX(PI / 2) * y ~= dvec3(0, 0, 1) + doAssert quatRotateZ(PI / 2) * x ~= dvec3(0, 1, 0) doAssert fromTwoVectors(x, y).mat4() * x ~= y doAssert fromTwoVectors(y, z).mat4() * y ~= z From 03097cf3ea22abe88b41b1a3253fc713392625ff Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 14:14:09 -0700 Subject: [PATCH 05/35] Fix the quats. --- README.md | 30 ++++++----------- experiments/dump_glm.nim | 42 ++++++++++++++++++------ experiments/dump_glm.txt | 61 +++++++++++++++++++++++++++++++---- experiments/dump_glmatrix.js | 37 +++++++++++++++++---- experiments/dump_glmatrix.txt | 59 +++++++++++++++++++++++++++++---- experiments/dump_jolt.nim | 44 +++++++++++++++++++------ experiments/dump_jolt.txt | 55 +++++++++++++++++++++++++++---- experiments/dump_vmath.nim | 33 ++++++++++++++++++- experiments/dump_vmath.txt | 55 ++++++++++++++++++++++++++++++- src/vmath.nim | 12 ++----- 10 files changed, 348 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 97be098..8b4980a 100644 --- a/README.md +++ b/README.md @@ -106,22 +106,6 @@ This is the same system used in the GLTF file format. [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 | - ]) -``` - ## How does vmath compare to other libraries? vmath follows the standard glTF / OpenGL conventions: right-handed coordinate system, column-major matrix storage, and standard math `[row, col]` indexing. These are the dominant conventions used across graphics and game engines. @@ -141,16 +125,20 @@ We run identical math operations across vmath, [nim-glm](https://github.com/nick | Quaternion multiply | ✅ | ✅ | ✅ | | Quaternion vector rotation | ✅ | ✅ | ✅ | | Quaternion-matrix roundtrip| ✅ | ✅ | ✅ | +| Perspective matrix | ✅ | ✅ | ❌ | +| Ortho matrix | ✅ | ✅ | N/A | +| LookAt matrix | ✅ | ✅ | ✅ | +| Euler angle decomposition | ✅ | N/A | ✅ | | Element access `[row,col]` | ✅ | N/A | ✅ | -| Quat decomposition (sign) | ❌ | ❌ | ❌ | -| Scaled-matrix decomposition| ❌ | ❌ | ❌ | - -❌ **Quaternion decomposition sign**: When extracting a quaternion from a rotation matrix, different libraries may return quaternions that differ by sign (q and -q represent the same rotation). This is expected behavior, not a bug. +| Quat decomposition (sign) | ✅ | ✅ | ✅ | +| Scaled-matrix decomposition| ✅ | ✅ | ✅ | -❌ **Scaled-matrix decomposition**: Extracting rotation from a matrix that includes scale produces different results across libraries because there is no single correct answer. +❌ **Perspective matrix**: Jolt uses Z range [0, 1] (Vulkan/DirectX convention) while vmath uses Z range [-1, 1] (OpenGL convention). The X and Y scaling match, but Z-related elements differ. # 2.x.x to 3.0.0 vmath breaking changes: +Significant work went into aligning vmath with the dominant conventions used across the game and graphics industry. vmath now matches GLM, gl-matrix, and Jolt on all core operations (see comparison table above). + * **Matrix indexing convention changed from `[col, row]` to `[row, col]`** (standard math notation). Code using `m[0, 3]` to access the translation X component should change to `m[0, 3]` (same syntax, but the meaning of indices swapped). * **`mat4(1..16)` no longer transposes** the input. Arguments are now stored directly to memory in column-major order, identical to `gmat4(1..16)`. If you were passing row-major values, you need to transpose them. * **Matrix multiplication is now standard `A * B`** (apply B first, then A). Previously it was reversed. diff --git a/experiments/dump_glm.nim b/experiments/dump_glm.nim index 1e4a473..906b2fc 100644 --- a/experiments/dump_glm.nim +++ b/experiments/dump_glm.nim @@ -129,10 +129,12 @@ proc main() = quatXY = quatX * quatY quatXYZ = quatXY * quatZ + hardAxis = normalize(vec3f(1.0, -2.0, 3.0)) + hardAngle = 170'f32 * PI.float32 / 180'f32 + hardQuat = quatf(hardAxis, hardAngle) + hardMat = hardQuat.mat4() + rotationOnlyM = rotationOnlyCopy(transformM) - basisRight = vec3f(1'f32, 0'f32, 0'f32) - basisUp = vec3f(0'f32, 1'f32, 0'f32) - basisForward = vec3f(0'f32, 0'f32, 1'f32) lines.heading("dump") lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") @@ -195,19 +197,39 @@ proc main() = lines.dumpQuat("pure_rotation.quat", pureRotationQuat) lines.dumpMat4("pure_rotation", pureRotationM) lines.dumpMat4("pure_rotation.quat.mat4", pureRotationQuat.mat4()) - lines.appendLine("transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library") lines.dumpMat4("transform.rotation_only", rotationOnlyM) + lines.dumpQuat("transform.rotation_only.quat", quat(rotationOnlyM)) let axisMatQuat = quat(axisMat) lines.dumpQuat("axis_mat.quat", axisMatQuat) lines.dumpMat4("axis_mat.quat.mat4", axisMatQuat.mat4()) + lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero") + lines.dumpQuat("hard_decomp.quat_original", hardQuat) + lines.dumpQuat("hard_decomp.quat_from_mat", quat(hardMat)) + lines.dumpMat4("hard_decomp.mat4", hardMat) + lines.dumpMat4("hard_decomp.quat_from_mat.mat4", quat(hardMat).mat4()) + + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + let fovyRad = 60'f32 * PI.float32 / 180'f32 + lines.dumpMat4("perspective", perspectiveRH(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) + + lines.heading("ortho matrix") + lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") + lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) + + lines.heading("lookAt matrix") + lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") + lines.dumpMat4("lookAt", lookAtRH(vec3f(5, 5, 5), vec3f(0, 0, 0), vec3f(0, 1, 0))) + + lines.heading("euler angle decomposition") + lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") + let pureRotAngles = eulerAngles(quat(pureRotationM)) + let axisAngles = eulerAngles(axisQuat) + lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) + lines.dumpVec3("from_axis_angle.euler", axisAngles) lines.heading("basis directions") - lines.dumpVec3("canonical_right", basisRight) - lines.dumpVec3("canonical_up", basisUp) - lines.dumpVec3("canonical_forward", basisForward) - lines.dumpVec3("quat_z.right", quatZ * basisRight) - lines.dumpVec3("quat_z.up", quatZ * basisUp) - lines.dumpVec3("quat_z.forward", quatZ * basisForward) + lines.appendLine("N/A") lines.heading("element access [row,col]") lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") diff --git a/experiments/dump_glm.txt b/experiments/dump_glm.txt index 411cada..4d35995 100644 --- a/experiments/dump_glm.txt +++ b/experiments/dump_glm.txt @@ -172,7 +172,6 @@ pure_rotation.quat.mat4: +000.467 -000.491 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] -transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library transform.rotation_only: [ +000.599 +001.741 +000.781 +000.000 @@ -180,6 +179,7 @@ transform.rotation_only: +001.870 -001.964 +002.941 +000.000 +000.000 +000.000 +000.000 +001.000 ] +transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> axis_mat.quat.mat4: [ @@ -188,14 +188,61 @@ axis_mat.quat.mat4: +000.326 -000.340 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] +hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero +hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.quat_from_mat.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.002 -001.000 + +000.000 +000.000 -000.200 +000.000 +] + +== ortho matrix == +notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 +ortho: +[ + +000.100 +000.000 +000.000 +000.000 + +000.000 +000.133 +000.000 +000.000 + +000.000 +000.000 -000.020 +000.000 + +000.000 +000.000 -001.002 +001.000 +] + +== lookAt matrix == +notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) +lookAt: +[ + +000.707 -000.408 +000.577 +000.000 + +000.000 +000.816 +000.577 +000.000 + -000.707 -000.408 +000.577 +000.000 + +000.000 +000.000 -008.660 +001.000 +] + +== euler angle decomposition == +notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians +pure_rotation.quat.euler: <+000.646, -000.401, +001.239> +from_axis_angle.euler: <+000.064, +000.487, -000.670> == basis directions == -canonical_right: <+001.000, +000.000, +000.000> -canonical_up: <+000.000, +001.000, +000.000> -canonical_forward: <+000.000, +000.000, +001.000> -quat_z.right: <+000.326, +000.946, +000.000> -quat_z.up: <-000.946, +000.326, +000.000> -quat_z.forward: <+000.000, +000.000, +001.000> +N/A == element access [row,col] == notes: [row,col] in math convention, element (i,j) = row i, col j diff --git a/experiments/dump_glmatrix.js b/experiments/dump_glmatrix.js index 76713fb..aec2cf0 100644 --- a/experiments/dump_glmatrix.js +++ b/experiments/dump_glmatrix.js @@ -159,6 +159,11 @@ const quatZ = quatFromAxisAngle(vec3.fromValues(0, 0, 1), angleC); const quatXY = quatMul(quatX, quatY); const quatXYZ = quatMul(quatXY, quatZ); +const hardAxis = vec3.normalize(vec3.create(), vec3.fromValues(1.0, -2.0, 3.0)); +const hardAngle = 170 * DEG2RAD; +const hardQuat = quatFromAxisAngle(hardAxis, hardAngle); +const hardMat = mat4FromQuat(hardQuat); + const rotationOnlyM = rotationOnlyCopy(transformM); const basisRight = vec3.fromValues(1, 0, 0); const basisUp = vec3.fromValues(0, 1, 0); @@ -225,19 +230,37 @@ const pureRotationQuat = quatFromMat(pureRotationM); dumpQuat("pure_rotation.quat", pureRotationQuat); dumpMat4("pure_rotation", pureRotationM); dumpMat4("pure_rotation.quat.mat4", mat4FromQuat(pureRotationQuat)); -appendLine("transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library"); dumpMat4("transform.rotation_only", rotationOnlyM); +dumpQuat("transform.rotation_only.quat", quatFromMat(rotationOnlyM)); const axisMatQuat = quatFromMat(axisMat); dumpQuat("axis_mat.quat", axisMatQuat); dumpMat4("axis_mat.quat.mat4", mat4FromQuat(axisMatQuat)); +appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero"); +dumpQuat("hard_decomp.quat_original", hardQuat); +dumpQuat("hard_decomp.quat_from_mat", quatFromMat(hardMat)); +dumpMat4("hard_decomp.mat4", hardMat); +dumpMat4("hard_decomp.quat_from_mat.mat4", mat4FromQuat(quatFromMat(hardMat))); + +const DEG2RAD_60 = 60 * Math.PI / 180; + +heading("perspective matrix"); +appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0"); +dumpMat4("perspective", mat4.perspective(mat4.create(), DEG2RAD_60, 1.5, 0.1, 100.0)); + +heading("ortho matrix"); +appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0"); +dumpMat4("ortho", mat4.ortho(mat4.create(), -10, 10, -7.5, 7.5, 0.1, 100.0)); + +heading("lookAt matrix"); +appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)"); +dumpMat4("lookAt", mat4.lookAt(mat4.create(), + vec3.fromValues(5, 5, 5), vec3.fromValues(0, 0, 0), vec3.fromValues(0, 1, 0))); + +heading("euler angle decomposition"); +appendLine("N/A"); heading("basis directions"); -dumpVec3("canonical_right", basisRight); -dumpVec3("canonical_up", basisUp); -dumpVec3("canonical_forward", basisForward); -dumpVec3("quat_z.right", quatRotateVec3(quatZ, basisRight)); -dumpVec3("quat_z.up", quatRotateVec3(quatZ, basisUp)); -dumpVec3("quat_z.forward", quatRotateVec3(quatZ, basisForward)); +appendLine("N/A"); heading("element access [row,col]"); appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j"); diff --git a/experiments/dump_glmatrix.txt b/experiments/dump_glmatrix.txt index 6b3d489..901bbd4 100644 --- a/experiments/dump_glmatrix.txt +++ b/experiments/dump_glmatrix.txt @@ -172,7 +172,6 @@ pure_rotation.quat.mat4: +000.467 -000.491 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] -transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library transform.rotation_only: [ +000.599 +001.741 +000.781 +000.000 @@ -180,6 +179,7 @@ transform.rotation_only: +001.870 -001.964 +002.941 +000.000 +000.000 +000.000 +000.000 +001.000 ] +transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> axis_mat.quat.mat4: [ @@ -188,14 +188,59 @@ axis_mat.quat.mat4: +000.326 -000.340 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] +hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero +hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.quat_from_mat.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.002 -001.000 + +000.000 +000.000 -000.200 +000.000 +] + +== ortho matrix == +notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 +ortho: +[ + +000.100 +000.000 +000.000 +000.000 + +000.000 +000.133 +000.000 +000.000 + +000.000 +000.000 -000.020 +000.000 + +000.000 +000.000 -001.002 +001.000 +] + +== lookAt matrix == +notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) +lookAt: +[ + +000.707 -000.408 +000.577 +000.000 + +000.000 +000.816 +000.577 +000.000 + -000.707 -000.408 +000.577 +000.000 + +000.000 +000.000 -008.660 +001.000 +] + +== euler angle decomposition == +N/A == basis directions == -canonical_right: <+001.000, +000.000, +000.000> -canonical_up: <+000.000, +001.000, +000.000> -canonical_forward: <+000.000, +000.000, +001.000> -quat_z.right: <+000.326, +000.946, +000.000> -quat_z.up: <-000.946, +000.326, +000.000> -quat_z.forward: <+000.000, +000.000, +001.000> +N/A == element access [row,col] == notes: [row,col] in math convention, element (i,j) = row i, col j diff --git a/experiments/dump_jolt.nim b/experiments/dump_jolt.nim index 616c042..4e1d4d1 100644 --- a/experiments/dump_jolt.nim +++ b/experiments/dump_jolt.nim @@ -147,11 +147,13 @@ proc main() = quatZ = quatRotate(0.0, 0.0, 1.0, angleC) quatXY = quatMultiply(quatX, quatY) quatXYZ = quatMultiply(quatXY, quatZ) + let + hardAxis = joltVec3Normalize(1.0, -2.0, 3.0) + hardAngle = 170'f32 * PI.float32 / 180'f32 + hardQuat = quatRotate(hardAxis.x, hardAxis.y, hardAxis.z, hardAngle) + hardMat = joltMat44FromQuat(hardQuat.x, hardQuat.y, hardQuat.z, hardQuat.w) let rotationOnlyM = getRotationSafe(transformM) - basisRight = JoltFloat3(x: 1.0, y: 0.0, z: 0.0) - basisUp = JoltFloat3(x: 0.0, y: 1.0, z: 0.0) - basisForward = JoltFloat3(x: 0.0, y: 0.0, z: 1.0) lines.heading("dump") lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") @@ -214,19 +216,41 @@ proc main() = lines.dumpQuat("pure_rotation.quat", pureRotationQuat) lines.dumpMat4("pure_rotation", pureRotationM) lines.dumpMat4("pure_rotation.quat.mat4", joltMat44FromQuat(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w)) - lines.appendLine("transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library") lines.dumpMat4("transform.rotation_only", rotationOnlyM) + lines.dumpQuat("transform.rotation_only.quat", getQuaternion(rotationOnlyM)) let axisMatQuat = axisQuat lines.dumpQuat("axis_mat.quat", axisMatQuat) lines.dumpMat4("axis_mat.quat.mat4", joltMat44FromQuat(axisMatQuat.x, axisMatQuat.y, axisMatQuat.z, axisMatQuat.w)) + lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero") + lines.dumpQuat("hard_decomp.quat_original", hardQuat) + let hardMatQuat = getQuaternion(hardMat) + lines.dumpQuat("hard_decomp.quat_from_mat", hardMatQuat) + lines.dumpMat4("hard_decomp.mat4", hardMat) + lines.dumpMat4("hard_decomp.quat_from_mat.mat4", joltMat44FromQuat(hardMatQuat.x, hardMatQuat.y, hardMatQuat.z, hardMatQuat.w)) + + let fovyRad = 60'f32 * PI.float32 / 180'f32 + + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + lines.appendLine("notes: Jolt uses Z range [0,1] (Vulkan/DirectX convention), values will differ from OpenGL [-1,1]") + lines.dumpMat4("perspective", joltMat44Perspective(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) + + lines.heading("ortho matrix") + lines.appendLine("N/A") + + lines.heading("lookAt matrix") + lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") + lines.dumpMat4("lookAt", joltMat44LookAt(5, 5, 5, 0, 0, 0, 0, 1, 0)) + + lines.heading("euler angle decomposition") + lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") + let pureRotEuler = joltQuatGetEulerAngles(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w) + let axisEuler = joltQuatGetEulerAngles(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + lines.dumpVec3("pure_rotation.quat.euler", pureRotEuler) + lines.dumpVec3("from_axis_angle.euler", axisEuler) lines.heading("basis directions") - lines.dumpVec3("canonical_right", basisRight) - lines.dumpVec3("canonical_up", basisUp) - lines.dumpVec3("canonical_forward", basisForward) - lines.dumpVec3("quat_z.right", quatRotateVec3(quatZ, basisRight)) - lines.dumpVec3("quat_z.up", quatRotateVec3(quatZ, basisUp)) - lines.dumpVec3("quat_z.forward", quatRotateVec3(quatZ, basisForward)) + lines.appendLine("N/A") lines.heading("element access [row,col]") lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") diff --git a/experiments/dump_jolt.txt b/experiments/dump_jolt.txt index 976f0bf..5d45c20 100644 --- a/experiments/dump_jolt.txt +++ b/experiments/dump_jolt.txt @@ -172,7 +172,6 @@ pure_rotation.quat.mat4: +000.467 -000.491 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] -transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library transform.rotation_only: [ +000.599 +001.741 +000.781 +000.000 @@ -180,6 +179,7 @@ transform.rotation_only: +001.870 -001.964 +002.941 +000.000 +000.000 +000.000 +000.000 +001.000 ] +transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> axis_mat.quat.mat4: [ @@ -188,14 +188,55 @@ axis_mat.quat.mat4: +000.326 -000.340 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] +hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero +hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.quat_from_mat.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +notes: Jolt uses Z range [0,1] (Vulkan/DirectX convention), values will differ from OpenGL [-1,1] +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.001 -001.000 + +000.000 +000.000 -000.100 +000.000 +] + +== ortho matrix == +N/A + +== lookAt matrix == +notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) +lookAt: +[ + +000.707 -000.408 +000.577 +000.000 + +000.000 +000.816 +000.577 +000.000 + -000.707 -000.408 +000.577 +000.000 + +000.000 +000.000 -008.660 +001.000 +] + +== euler angle decomposition == +notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians +pure_rotation.quat.euler: <+000.646, -000.401, +001.239> +from_axis_angle.euler: <+000.064, +000.487, -000.670> == basis directions == -canonical_right: <+001.000, +000.000, +000.000> -canonical_up: <+000.000, +001.000, +000.000> -canonical_forward: <+000.000, +000.000, +001.000> -quat_z.right: <+000.326, +000.946, +000.000> -quat_z.up: <-000.946, +000.326, +000.000> -quat_z.forward: <+000.000, +000.000, +001.000> +N/A == element access [row,col] == notes: [row,col] in math convention, element (i,j) = row i, col j diff --git a/experiments/dump_vmath.nim b/experiments/dump_vmath.nim index 5d64952..032feb8 100644 --- a/experiments/dump_vmath.nim +++ b/experiments/dump_vmath.nim @@ -127,6 +127,12 @@ proc main() = quatXY = quatMultiply(quatX, quatY) quatXYZ = quatMultiply(quatXY, quatZ) + # Hard quat decomposition case: 170° around arbitrary axis (w near zero) + hardAxis = normalize(vec3(1.0, -2.0, 3.0)) + hardAngle = 170'f32.toRadians + hardQuat = fromAxisAngle(hardAxis, hardAngle) + hardMat = hardQuat.mat4() + rotationOnlyM = rotationOnlyCopy(transformM) basisRight = vec3(1.0, 0.0, 0.0) basisUp = vec3(0.0, 1.0, 0.0) @@ -192,12 +198,37 @@ proc main() = lines.dumpQuat("pure_rotation.quat", pureRotationM.quat()) lines.dumpMat4("pure_rotation", pureRotationM) lines.dumpMat4("pure_rotation.quat.mat4", pureRotationM.quat().mat4()) - lines.appendLine("transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library") lines.dumpMat4("transform.rotation_only", rotationOnlyM) + lines.dumpQuat("transform.rotation_only.quat", rotationOnlyM.quat()) lines.dumpQuat("axis_mat.quat", axisMat.quat()) lines.dumpMat4("axis_mat.quat.mat4", axisMat.quat().mat4()) + lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero") + lines.dumpQuat("hard_decomp.quat_original", hardQuat) + lines.dumpQuat("hard_decomp.quat_from_mat", hardMat.quat()) + lines.dumpMat4("hard_decomp.mat4", hardMat) + lines.dumpMat4("hard_decomp.quat_from_mat.mat4", hardMat.quat().mat4()) + + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + lines.dumpMat4("perspective", perspective(60'f32, 1.5'f32, 0.1'f32, 100'f32)) + + lines.heading("ortho matrix") + lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") + lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) + + lines.heading("lookAt matrix") + lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") + lines.dumpMat4("lookAt", lookAt(vec3(5'f32, 5'f32, 5'f32), vec3(0'f32, 0'f32, 0'f32), vec3(0'f32, 1'f32, 0'f32))) + + lines.heading("euler angle decomposition") + lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") + let pureRotAngles = pureRotationM.quat().toAngles() + let axisAngles = axisQuat.toAngles() + lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) + lines.dumpVec3("from_axis_angle.euler", axisAngles) lines.heading("basis directions") + lines.appendLine("notes: library-specific, N/A for libraries without canonical basis helpers") lines.dumpVec3("canonical_right", basisRight) lines.dumpVec3("canonical_up", basisUp) lines.dumpVec3("canonical_forward", basisForward) diff --git a/experiments/dump_vmath.txt b/experiments/dump_vmath.txt index 2ebf34c..af360be 100644 --- a/experiments/dump_vmath.txt +++ b/experiments/dump_vmath.txt @@ -172,7 +172,6 @@ pure_rotation.quat.mat4: +000.467 -000.491 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] -transform.rotation_only.note: skipped exact quaternion/matrix roundtrip comparison because scaled-matrix decomposition differs by library transform.rotation_only: [ +000.599 +001.741 +000.781 +000.000 @@ -180,6 +179,7 @@ transform.rotation_only: +001.870 -001.964 +002.941 +000.000 +000.000 +000.000 +000.000 +001.000 ] +transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> axis_mat.quat.mat4: [ @@ -188,8 +188,61 @@ axis_mat.quat.mat4: +000.326 -000.340 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] +hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero +hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.quat_from_mat.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.002 -001.000 + +000.000 +000.000 -000.200 +000.000 +] + +== ortho matrix == +notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 +ortho: +[ + +000.100 +000.000 +000.000 +000.000 + +000.000 +000.133 +000.000 +000.000 + +000.000 +000.000 -000.020 +000.000 + +000.000 +000.000 -001.002 +001.000 +] + +== lookAt matrix == +notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) +lookAt: +[ + +000.707 -000.408 +000.577 +000.000 + +000.000 +000.816 +000.577 +000.000 + -000.707 -000.408 +000.577 +000.000 + +000.000 +000.000 -008.660 +001.000 +] + +== euler angle decomposition == +notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians +pure_rotation.quat.euler: <+000.646, -000.401, +001.239> +from_axis_angle.euler: <+000.064, +000.487, -000.670> == basis directions == +notes: library-specific, N/A for libraries without canonical basis helpers canonical_right: <+001.000, +000.000, +000.000> canonical_up: <+000.000, +001.000, +000.000> canonical_forward: <+000.000, +000.000, +001.000> diff --git a/src/vmath.nim b/src/vmath.nim index d7ed298..9534f15 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -1535,10 +1535,7 @@ proc ortho*[T](left, right, bottom, top, near, far: T): GMat4[T] = result[2, 3] = T(-(far + near) / fn) result[3, 3] = 1 -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".} = +proc lookAt*[T](eye, center, up: GVec3[T]): GMat4[T] = ## Create a matrix that would convert eye pos to looking at center. let eyex = eye[0] @@ -1619,11 +1616,8 @@ proc lookAt*[T](eye, center, up: GVec3[T]): GMat4[T] result[2, 3] = -(z0 * eyex + z1 * eyey + z2 * eyez) result[3, 3] = 1 -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. +proc lookAt*[T](eye, center: GVec3[T]): GMat4[T] = + ## Look at center from eye with default UP vector. lookAt(eye, center, gvec3(T(0), 0, 1)) proc angle*[T](a: GVec2[T]): T = From 74a3d59aee1828c22cad06b1caa07d009027e49b Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 14:25:54 -0700 Subject: [PATCH 06/35] add slerp test more things. --- README.md | 4 ++++ experiments/dump_glm.nim | 23 +++++++++++++++++++++++ experiments/dump_glm.txt | 31 +++++++++++++++++++++++++++++++ experiments/dump_glmatrix.js | 31 +++++++++++++++++++++++++++++++ experiments/dump_glmatrix.txt | 35 +++++++++++++++++++++++++++++++++++ experiments/dump_jolt.nim | 33 +++++++++++++++++++++++++++++++++ experiments/dump_jolt.txt | 35 +++++++++++++++++++++++++++++++++++ experiments/dump_vmath.nim | 31 +++++++++++++++++++++++++++++++ experiments/dump_vmath.txt | 35 +++++++++++++++++++++++++++++++++++ src/vmath.nim | 22 ++++++++++++++++++++++ 10 files changed, 280 insertions(+) diff --git a/README.md b/README.md index 8b4980a..2b5bd87 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,10 @@ We run identical math operations across vmath, [nim-glm](https://github.com/nick | Quaternion multiply | ✅ | ✅ | ✅ | | Quaternion vector rotation | ✅ | ✅ | ✅ | | Quaternion-matrix roundtrip| ✅ | ✅ | ✅ | +| Matrix inverse | ✅ | ✅ | ✅ | +| Cross product | ✅ | ✅ | ✅ | +| Slerp | ✅ | ✅ | ✅ | +| fromTwoVectors | N/A | ✅ | ✅ | | Perspective matrix | ✅ | ✅ | ❌ | | Ortho matrix | ✅ | ✅ | N/A | | LookAt matrix | ✅ | ✅ | ✅ | diff --git a/experiments/dump_glm.nim b/experiments/dump_glm.nim index 906b2fc..f110fa3 100644 --- a/experiments/dump_glm.nim +++ b/experiments/dump_glm.nim @@ -228,6 +228,29 @@ proc main() = lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) lines.dumpVec3("from_axis_angle.euler", axisAngles) + lines.heading("matrix inverse") + lines.dumpMat4("transform.inverse", inverse(transformM)) + lines.dumpMat4("pure_rotation.inverse", inverse(pureRotationM)) + + lines.heading("cross product") + lines.appendLine("notes: cross(a, b) where a and b are vec3") + let crossA = vec3f(1, 0, 0) + let crossB = vec3f(0, 1, 0) + let crossC = normalize(vec3f(1, 2, 3)) + let crossD = normalize(vec3f(-1, 0.5, 2)) + lines.dumpVec3("cross(x_axis, y_axis)", cross(crossA, crossB)) + lines.dumpVec3("cross(y_axis, x_axis)", cross(crossB, crossA)) + lines.dumpVec3("cross(c, d)", cross(crossC, crossD)) + + lines.heading("slerp") + lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") + lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", slerp(quatX, quatZ, 0.25'f32)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", slerp(quatX, quatZ, 0.5'f32)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) + + lines.heading("fromTwoVectors") + lines.appendLine("N/A") + lines.heading("basis directions") lines.appendLine("N/A") diff --git a/experiments/dump_glm.txt b/experiments/dump_glm.txt index 4d35995..43dd667 100644 --- a/experiments/dump_glm.txt +++ b/experiments/dump_glm.txt @@ -241,6 +241,37 @@ notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians pure_rotation.quat.euler: <+000.646, -000.401, +001.239> from_axis_angle.euler: <+000.064, +000.487, -000.670> +== matrix inverse == +transform.inverse: +[ + +000.150 -000.277 +000.117 +000.000 + +000.435 +000.013 -000.123 +000.000 + +000.195 +000.185 +000.184 +000.000 + -016.063 -003.019 -004.227 +001.000 +] +pure_rotation.inverse: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== cross product == +notes: cross(a, b) where a and b are vec3 +cross(x_axis, y_axis): <+000.000, +000.000, +001.000> +cross(y_axis, x_axis): <+000.000, +000.000, -001.000> +cross(c, d): <+000.292, -000.583, +000.292> + +== slerp == +notes: slerp(a, b, t) between quat_x and quat_z +slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> +slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> +slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> + +== fromTwoVectors == +N/A + == basis directions == N/A diff --git a/experiments/dump_glmatrix.js b/experiments/dump_glmatrix.js index aec2cf0..8f7483d 100644 --- a/experiments/dump_glmatrix.js +++ b/experiments/dump_glmatrix.js @@ -259,6 +259,37 @@ dumpMat4("lookAt", mat4.lookAt(mat4.create(), heading("euler angle decomposition"); appendLine("N/A"); +heading("matrix inverse"); +dumpMat4("transform.inverse", mat4.invert(mat4.create(), transformM)); +dumpMat4("pure_rotation.inverse", mat4.invert(mat4.create(), pureRotationM)); + +heading("cross product"); +appendLine("notes: cross(a, b) where a and b are vec3"); +const crossA = vec3.fromValues(1, 0, 0); +const crossB = vec3.fromValues(0, 1, 0); +const crossC = vec3.normalize(vec3.create(), vec3.fromValues(1, 2, 3)); +const crossD = vec3.normalize(vec3.create(), vec3.fromValues(-1, 0.5, 2)); +dumpVec3("cross(x_axis, y_axis)", vec3.cross(vec3.create(), crossA, crossB)); +dumpVec3("cross(y_axis, x_axis)", vec3.cross(vec3.create(), crossB, crossA)); +dumpVec3("cross(c, d)", vec3.cross(vec3.create(), crossC, crossD)); + +heading("slerp"); +appendLine("notes: slerp(a, b, t) between quat_x and quat_z"); +dumpQuat("slerp(quat_x, quat_z, 0.25)", quat.slerp(quat.create(), quatX, quatZ, 0.25)); +dumpQuat("slerp(quat_x, quat_z, 0.5)", quat.slerp(quat.create(), quatX, quatZ, 0.5)); +dumpQuat("slerp(quat_x, quat_z, 0.75)", quat.slerp(quat.create(), quatX, quatZ, 0.75)); + +heading("fromTwoVectors"); +appendLine("notes: quaternion that rotates vector a to vector b"); +const fromA = vec3.fromValues(1, 0, 0); +const fromB = vec3.fromValues(0, 1, 0); +const fromC = vec3.normalize(vec3.create(), vec3.fromValues(1, 2, -1)); +const fromD = vec3.normalize(vec3.create(), vec3.fromValues(-1, 0.5, 2)); +dumpQuat("from_x_to_y", quat.rotationTo(quat.create(), fromA, fromB)); +dumpQuat("from_c_to_d", quat.rotationTo(quat.create(), fromC, fromD)); +dumpVec3("verify_x_to_y", vec3.transformQuat(vec3.create(), fromA, quat.rotationTo(quat.create(), fromA, fromB))); +dumpVec3("verify_c_to_d", vec3.transformQuat(vec3.create(), fromC, quat.rotationTo(quat.create(), fromC, fromD))); + heading("basis directions"); appendLine("N/A"); diff --git a/experiments/dump_glmatrix.txt b/experiments/dump_glmatrix.txt index 901bbd4..be2e362 100644 --- a/experiments/dump_glmatrix.txt +++ b/experiments/dump_glmatrix.txt @@ -239,6 +239,41 @@ lookAt: == euler angle decomposition == N/A +== matrix inverse == +transform.inverse: +[ + +000.150 -000.277 +000.117 +000.000 + +000.435 +000.013 -000.123 +000.000 + +000.195 +000.185 +000.184 +000.000 + -016.063 -003.019 -004.227 +001.000 +] +pure_rotation.inverse: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== cross product == +notes: cross(a, b) where a and b are vec3 +cross(x_axis, y_axis): <+000.000, +000.000, +001.000> +cross(y_axis, x_axis): <+000.000, +000.000, -001.000> +cross(c, d): <+000.292, -000.583, +000.292> + +== slerp == +notes: slerp(a, b, t) between quat_x and quat_z +slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> +slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> +slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> + +== fromTwoVectors == +notes: quaternion that rotates vector a to vector b +from_x_to_y: <+000.000, +000.000, +000.707, +000.707> +from_c_to_d: <+000.707, -000.157, +000.393, +000.567> +verify_x_to_y: <+000.000, +001.000, +000.000> +verify_c_to_d: <-000.436, +000.218, +000.873> + == basis directions == N/A diff --git a/experiments/dump_jolt.nim b/experiments/dump_jolt.nim index 4e1d4d1..b3525cd 100644 --- a/experiments/dump_jolt.nim +++ b/experiments/dump_jolt.nim @@ -249,6 +249,39 @@ proc main() = lines.dumpVec3("pure_rotation.quat.euler", pureRotEuler) lines.dumpVec3("from_axis_angle.euler", axisEuler) + lines.heading("matrix inverse") + var transformInv = transformM + lines.dumpMat4("transform.inverse", joltMat44Inverse(addr transformInv)) + var pureRotInv = pureRotationM + lines.dumpMat4("pure_rotation.inverse", joltMat44Inverse(addr pureRotInv)) + + lines.heading("cross product") + lines.appendLine("notes: cross(a, b) where a and b are vec3") + lines.dumpVec3("cross(x_axis, y_axis)", joltVec3Cross(1, 0, 0, 0, 1, 0)) + lines.dumpVec3("cross(y_axis, x_axis)", joltVec3Cross(0, 1, 0, 1, 0, 0)) + let crossC = joltVec3Normalize(1, 2, 3) + let crossD = joltVec3Normalize(-1, 0.5, 2) + lines.dumpVec3("cross(c, d)", joltVec3Cross(crossC.x, crossC.y, crossC.z, crossD.x, crossD.y, crossD.z)) + + lines.heading("slerp") + lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") + lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.25)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.5)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.75)) + + lines.heading("fromTwoVectors") + lines.appendLine("notes: quaternion that rotates vector a to vector b") + let ftFromA = JoltFloat3(x: 1, y: 0, z: 0) + let ftFromB = JoltFloat3(x: 0, y: 1, z: 0) + let ftFromC = joltVec3Normalize(1, 2, -1) + let ftFromD = joltVec3Normalize(-1, 0.5, 2) + let ftQAB = joltQuatFromTo(ftFromA.x, ftFromA.y, ftFromA.z, ftFromB.x, ftFromB.y, ftFromB.z) + let ftQCD = joltQuatFromTo(ftFromC.x, ftFromC.y, ftFromC.z, ftFromD.x, ftFromD.y, ftFromD.z) + lines.dumpQuat("from_x_to_y", ftQAB) + lines.dumpQuat("from_c_to_d", ftQCD) + lines.dumpVec3("verify_x_to_y", quatRotateVec3(ftQAB, ftFromA)) + lines.dumpVec3("verify_c_to_d", quatRotateVec3(ftQCD, ftFromC)) + lines.heading("basis directions") lines.appendLine("N/A") diff --git a/experiments/dump_jolt.txt b/experiments/dump_jolt.txt index 5d45c20..0cdface 100644 --- a/experiments/dump_jolt.txt +++ b/experiments/dump_jolt.txt @@ -235,6 +235,41 @@ notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians pure_rotation.quat.euler: <+000.646, -000.401, +001.239> from_axis_angle.euler: <+000.064, +000.487, -000.670> +== matrix inverse == +transform.inverse: +[ + +000.150 -000.277 +000.117 +000.000 + +000.435 +000.013 -000.123 +000.000 + +000.195 +000.185 +000.184 +000.000 + -016.063 -003.019 -004.227 +001.000 +] +pure_rotation.inverse: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== cross product == +notes: cross(a, b) where a and b are vec3 +cross(x_axis, y_axis): <+000.000, +000.000, +001.000> +cross(y_axis, x_axis): <+000.000, +000.000, -001.000> +cross(c, d): <+000.292, -000.583, +000.292> + +== slerp == +notes: slerp(a, b, t) between quat_x and quat_z +slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> +slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> +slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> + +== fromTwoVectors == +notes: quaternion that rotates vector a to vector b +from_x_to_y: <+000.000, +000.000, +000.707, +000.707> +from_c_to_d: <+000.707, -000.157, +000.393, +000.567> +verify_x_to_y: <+000.000, +001.000, +000.000> +verify_c_to_d: <-000.436, +000.218, +000.873> + == basis directions == N/A diff --git a/experiments/dump_vmath.nim b/experiments/dump_vmath.nim index 032feb8..9b4d1f5 100644 --- a/experiments/dump_vmath.nim +++ b/experiments/dump_vmath.nim @@ -236,6 +236,37 @@ proc main() = lines.dumpVec3("quat_z.up", quatRotate(quatZ, basisUp)) lines.dumpVec3("quat_z.forward", quatRotate(quatZ, basisForward)) + lines.heading("matrix inverse") + lines.dumpMat4("transform.inverse", transformM.inverse()) + lines.dumpMat4("pure_rotation.inverse", pureRotationM.inverse()) + + lines.heading("cross product") + lines.appendLine("notes: cross(a, b) where a and b are vec3") + let crossA = vec3(1'f32, 0'f32, 0'f32) + let crossB = vec3(0'f32, 1'f32, 0'f32) + let crossC = normalize(vec3(1'f32, 2'f32, 3'f32)) + let crossD = normalize(vec3(-1'f32, 0.5'f32, 2'f32)) + lines.dumpVec3("cross(x_axis, y_axis)", cross(crossA, crossB)) + lines.dumpVec3("cross(y_axis, x_axis)", cross(crossB, crossA)) + lines.dumpVec3("cross(c, d)", cross(crossC, crossD)) + + lines.heading("slerp") + lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") + lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", slerp(quatX, quatZ, 0.25'f32)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", slerp(quatX, quatZ, 0.5'f32)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) + + lines.heading("fromTwoVectors") + lines.appendLine("notes: quaternion that rotates vector a to vector b") + let fromA = normalize(vec3(1'f32, 0'f32, 0'f32)) + let fromB = normalize(vec3(0'f32, 1'f32, 0'f32)) + let fromC = normalize(vec3(1'f32, 2'f32, -1'f32)) + let fromD = normalize(vec3(-1'f32, 0.5'f32, 2'f32)) + lines.dumpQuat("from_x_to_y", fromTwoVectors(fromA, fromB)) + lines.dumpQuat("from_c_to_d", fromTwoVectors(fromC, fromD)) + lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) + lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) + lines.heading("element access [row,col]") lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") lines.dumpScalar("transform[0,0]", transformM[0, 0]) diff --git a/experiments/dump_vmath.txt b/experiments/dump_vmath.txt index af360be..9ca9779 100644 --- a/experiments/dump_vmath.txt +++ b/experiments/dump_vmath.txt @@ -250,6 +250,41 @@ quat_z.right: <+000.326, +000.946, +000.000> quat_z.up: <-000.946, +000.326, +000.000> quat_z.forward: <+000.000, +000.000, +001.000> +== matrix inverse == +transform.inverse: +[ + +000.150 -000.277 +000.117 +000.000 + +000.435 +000.013 -000.123 +000.000 + +000.195 +000.185 +000.184 +000.000 + -016.063 -003.019 -004.227 +001.000 +] +pure_rotation.inverse: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== cross product == +notes: cross(a, b) where a and b are vec3 +cross(x_axis, y_axis): <+000.000, +000.000, +001.000> +cross(y_axis, x_axis): <+000.000, +000.000, -001.000> +cross(c, d): <+000.292, -000.583, +000.292> + +== slerp == +notes: slerp(a, b, t) between quat_x and quat_z +slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> +slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> +slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> + +== fromTwoVectors == +notes: quaternion that rotates vector a to vector b +from_x_to_y: <+000.000, +000.000, +000.707, +000.707> +from_c_to_d: <+000.707, -000.157, +000.393, +000.567> +verify_x_to_y: <+000.000, +001.000, +000.000> +verify_c_to_d: <-000.436, +000.218, +000.873> + == element access [row,col] == notes: [row,col] in math convention, element (i,j) = row i, col j transform[0,0]: +000.599 diff --git a/src/vmath.nim b/src/vmath.nim index 9534f15..8e35ac2 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -1723,6 +1723,28 @@ proc nlerp*(a: Quat, b: Quat, v: float32): Quat = else: (a * (1.0 - v) + b * v).normalize() +proc slerp*[T](a, b: GVec4[T], t: T): GVec4[T] = + ## Spherical linear interpolation between two quaternions. + var z = b + var cosTheta = dot(a, b) + + # Take short path. + if cosTheta < 0: + z = -b + cosTheta = -cosTheta + + # Linear interpolation when nearly parallel to avoid division by zero. + if cosTheta > 1 - T(1e-6): + return gvec4( + a.x + (z.x - a.x) * t, + a.y + (z.y - a.y) * t, + a.z + (z.z - a.z) * t, + a.w + (z.w - a.w) * t, + ) + else: + let angle = arccos(cosTheta) + return (sin((1 - t) * angle) * a + sin(t * angle) * z) / sin(angle) + proc quat*[T](m: GMat4[T]): GVec4[T] = ## Create a quaternion from matrix. let From 58e1e2b74755eb150455b40aa5e205eb88543dc2 Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 14:54:07 -0700 Subject: [PATCH 07/35] Test the new things. --- README.md | 54 +++++++++--------- experiments/dump_glm.nim | 27 ++++++++- experiments/dump_glm.txt | 17 +++++- experiments/dump_glmatrix.js | 26 +++++---- experiments/dump_glmatrix.txt | 22 +++---- experiments/dump_jolt.nim | 15 ++++- experiments/dump_jolt.txt | 11 +++- experiments/dump_vmath.nim | 12 ++++ experiments/dump_vmath.txt | 10 ++++ src/vmath.nim | 27 ++++----- tests/test_quaternion.nim | 104 ++++++++++++++++++++++++++++++++++ 11 files changed, 258 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 2b5bd87..6632583 100644 --- a/README.md +++ b/README.md @@ -112,32 +112,34 @@ vmath follows the standard glTF / OpenGL conventions: right-handed coordinate sy We run identical math operations across vmath, [nim-glm](https://github.com/nickelsworth/nim-glm), [gl-matrix](https://github.com/toji/gl-matrix), and [Jolt Physics](https://github.com/jrouwe/JoltPhysics) and compare all results. See [experiments/](experiments/) for the dump scripts. -| Feature | GLM | gl-matrix | Jolt | -|----------------------------|:---:|:---------:|:----:| -| Vectors | ✅ | ✅ | ✅ | -| Matrix memory layout | ✅ | ✅ | ✅ | -| Matrix multiply | ✅ | ✅ | ✅ | -| Matrix-vector multiply | ✅ | ✅ | ✅ | -| Rotation matrices | ✅ | ✅ | ✅ | -| Translation matrices | ✅ | ✅ | ✅ | -| Scale matrices | ✅ | ✅ | ✅ | -| Quaternion constructors | ✅ | ✅ | ✅ | -| Quaternion multiply | ✅ | ✅ | ✅ | -| Quaternion vector rotation | ✅ | ✅ | ✅ | -| Quaternion-matrix roundtrip| ✅ | ✅ | ✅ | -| Matrix inverse | ✅ | ✅ | ✅ | -| Cross product | ✅ | ✅ | ✅ | -| Slerp | ✅ | ✅ | ✅ | -| fromTwoVectors | N/A | ✅ | ✅ | -| Perspective matrix | ✅ | ✅ | ❌ | -| Ortho matrix | ✅ | ✅ | N/A | -| LookAt matrix | ✅ | ✅ | ✅ | -| Euler angle decomposition | ✅ | N/A | ✅ | -| Element access `[row,col]` | ✅ | N/A | ✅ | -| Quat decomposition (sign) | ✅ | ✅ | ✅ | -| Scaled-matrix decomposition| ✅ | ✅ | ✅ | - -❌ **Perspective matrix**: Jolt uses Z range [0, 1] (Vulkan/DirectX convention) while vmath uses Z range [-1, 1] (OpenGL convention). The X and Y scaling match, but Z-related elements differ. +| Feature | vmath | nim-GLM | gl-matrix | Jolt Physics | +|----------------------------|:-----:|:-------:|:---------:|:------------:| +| Vectors | ✅ | ✅ | ✅ | ✅ | +| Matrix memory layout | ✅ | ✅ | ✅ | ✅ | +| Matrix multiply | ✅ | ✅ | ✅ | ✅ | +| Matrix-vector multiply | ✅ | ✅ | ✅ | ✅ | +| Rotation matrices | ✅ | ✅ | ✅ | ✅ | +| Translation matrices | ✅ | ✅ | ✅ | ✅ | +| Scale matrices | ✅ | ✅ | ✅ | ✅ | +| Quaternion constructors | ✅ | ✅ | ✅ | ✅ | +| Quaternion multiply | ✅ | ✅ | ✅ | ✅ | +| Quaternion vector rotation | ✅ | ✅ | ✅ | ✅ | +| Quaternion-matrix roundtrip| ✅ | ✅ | ✅ | ✅ | +| Quaternion inverse | ✅ | ✅ | ✅ | ✅ | +| Quaternion to axis-angle | ✅ | ✅ | ✅ | ✅ | +| Matrix inverse | ✅ | ✅ | ✅ | ✅ | +| Cross product | ✅ | ✅ | ✅ | ✅ | +| Slerp | ✅ | ✅ | ✅ | ✅ | +| fromTwoVectors | ✅ | ✅ | ✅ | ✅ | +| Perspective matrix | ✅ | ✅ | ✅ | ❌ | +| Ortho matrix | ✅ | ✅ | ✅ | N/A | +| LookAt matrix | ✅ | ✅ | ✅ | ✅ | +| Euler angle decomposition | ✅ | ✅ | N/A | ✅ | +| Element access `[row,col]` | ✅ | ✅ | N/A | ✅ | +| Quat decomposition (sign) | ✅ | ✅ | ✅ | ✅ | +| Scaled-matrix decomposition| ✅ | ✅ | ✅ | ✅ | + +❌ **Perspective matrix**: Jolt Physics uses Z range [0, 1] (Vulkan/DirectX convention) while vmath uses Z range [-1, 1] (OpenGL convention). The X and Y scaling match, but Z-related elements differ. # 2.x.x to 3.0.0 vmath breaking changes: diff --git a/experiments/dump_glm.nim b/experiments/dump_glm.nim index f110fa3..6eaaf10 100644 --- a/experiments/dump_glm.nim +++ b/experiments/dump_glm.nim @@ -249,7 +249,32 @@ proc main() = lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) lines.heading("fromTwoVectors") - lines.appendLine("N/A") + lines.appendLine("notes: quaternion that rotates vector a to vector b") + lines.appendLine("notes: GLM has no native fromTwoVectors, implemented inline") + proc fromTwoVectors(a, b: Vec3f): Quatf = + let d = dot(a, b) + let c = cross(a, b) + let w = sqrt(dot(a, a) * dot(b, b)) + d + result = quatf(c.x, c.y, c.z, w) + result = normalize(result) + let fromA = vec3f(1, 0, 0) + let fromB = vec3f(0, 1, 0) + let fromC = normalize(vec3f(1, 2, -1)) + let fromD = normalize(vec3f(-1, 0.5, 2)) + lines.dumpQuat("from_x_to_y", fromTwoVectors(fromA, fromB)) + lines.dumpQuat("from_c_to_d", fromTwoVectors(fromC, fromD)) + lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) + lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) + + lines.heading("quaternion inverse") + lines.dumpQuat("from_axis_angle.inverse", inverse(axisQuat)) + lines.dumpQuat("verify_q_mul_qinv", axisQuat * inverse(axisQuat)) + + lines.heading("quaternion to axis-angle") + lines.dumpVec3("from_axis_angle.axis", axis(axisQuat)) + lines.dumpScalar("from_axis_angle.angle", angle(axisQuat)) + lines.dumpVec3("quat_xyz.axis", axis(quatXYZ)) + lines.dumpScalar("quat_xyz.angle", angle(quatXYZ)) lines.heading("basis directions") lines.appendLine("N/A") diff --git a/experiments/dump_glm.txt b/experiments/dump_glm.txt index 43dd667..b2bd1d5 100644 --- a/experiments/dump_glm.txt +++ b/experiments/dump_glm.txt @@ -270,7 +270,22 @@ slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> == fromTwoVectors == -N/A +notes: quaternion that rotates vector a to vector b +notes: GLM has no native fromTwoVectors, implemented inline +from_x_to_y: <+000.000, +000.000, +000.707, +000.707> +from_c_to_d: <+000.707, -000.157, +000.393, +000.567> +verify_x_to_y: <+000.000, +001.000, +000.000> +verify_c_to_d: <-000.436, +000.218, +000.873> + +== quaternion inverse == +from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> +verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> + +== quaternion to axis-angle == +from_axis_angle.axis: <+000.267, +000.535, -000.802> +from_axis_angle.angle: +000.838 +quat_xyz.axis: <+000.235, -000.549, +000.802> +quat_xyz.angle: +001.309 == basis directions == N/A diff --git a/experiments/dump_glmatrix.js b/experiments/dump_glmatrix.js index 8f7483d..3e48fef 100644 --- a/experiments/dump_glmatrix.js +++ b/experiments/dump_glmatrix.js @@ -290,21 +290,25 @@ dumpQuat("from_c_to_d", quat.rotationTo(quat.create(), fromC, fromD)); dumpVec3("verify_x_to_y", vec3.transformQuat(vec3.create(), fromA, quat.rotationTo(quat.create(), fromA, fromB))); dumpVec3("verify_c_to_d", vec3.transformQuat(vec3.create(), fromC, quat.rotationTo(quat.create(), fromC, fromD))); +heading("quaternion inverse"); +dumpQuat("from_axis_angle.inverse", quat.invert(quat.create(), axisQuat)); +dumpQuat("verify_q_mul_qinv", quat.multiply(quat.create(), axisQuat, quat.invert(quat.create(), axisQuat))); + +heading("quaternion to axis-angle"); +const aaAxis1 = vec3.create(); +const aaAngle1 = quat.getAxisAngle(aaAxis1, axisQuat); +const aaAxis2 = vec3.create(); +const aaAngle2 = quat.getAxisAngle(aaAxis2, quatXYZ); +dumpVec3("from_axis_angle.axis", aaAxis1); +dumpScalar("from_axis_angle.angle", aaAngle1); +dumpVec3("quat_xyz.axis", aaAxis2); +dumpScalar("quat_xyz.angle", aaAngle2); + heading("basis directions"); appendLine("N/A"); heading("element access [row,col]"); -appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j"); -appendLine("notes: gl-matrix uses flat arr[16], so element (r,c) = arr[c*4+r]"); -dumpScalar("transform[0,0]", transformM[0*4+0]); -dumpScalar("transform[0,1]", transformM[1*4+0]); -dumpScalar("transform[0,2]", transformM[2*4+0]); -dumpScalar("transform[0,3]", transformM[3*4+0]); -dumpScalar("transform[1,0]", transformM[0*4+1]); -dumpScalar("transform[1,3]", transformM[3*4+1]); -dumpScalar("transform[2,0]", transformM[0*4+2]); -dumpScalar("transform[2,3]", transformM[3*4+2]); -dumpScalar("transform[3,3]", transformM[3*4+3]); +appendLine("N/A"); writeFileSync(OutputPath, lines.join("\n") + "\n"); console.log("Wrote", OutputPath); diff --git a/experiments/dump_glmatrix.txt b/experiments/dump_glmatrix.txt index be2e362..d65fb20 100644 --- a/experiments/dump_glmatrix.txt +++ b/experiments/dump_glmatrix.txt @@ -274,18 +274,18 @@ from_c_to_d: <+000.707, -000.157, +000.393, +000.567> verify_x_to_y: <+000.000, +001.000, +000.000> verify_c_to_d: <-000.436, +000.218, +000.873> +== quaternion inverse == +from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> +verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> + +== quaternion to axis-angle == +from_axis_angle.axis: <+000.267, +000.535, -000.802> +from_axis_angle.angle: +000.838 +quat_xyz.axis: <+000.235, -000.549, +000.802> +quat_xyz.angle: +001.309 + == basis directions == N/A == element access [row,col] == -notes: [row,col] in math convention, element (i,j) = row i, col j -notes: gl-matrix uses flat arr[16], so element (r,c) = arr[c*4+r] -transform[0,0]: +000.599 -transform[0,1]: -002.495 -transform[0,2]: +001.870 -transform[0,3]: +010.000 -transform[1,0]: +001.741 -transform[1,3]: +020.000 -transform[2,0]: +000.781 -transform[2,3]: +030.000 -transform[3,3]: +001.000 +N/A diff --git a/experiments/dump_jolt.nim b/experiments/dump_jolt.nim index b3525cd..3b799e9 100644 --- a/experiments/dump_jolt.nim +++ b/experiments/dump_jolt.nim @@ -282,12 +282,25 @@ proc main() = lines.dumpVec3("verify_x_to_y", quatRotateVec3(ftQAB, ftFromA)) lines.dumpVec3("verify_c_to_d", quatRotateVec3(ftQCD, ftFromC)) + lines.heading("quaternion inverse") + let axisQuatInv = joltQuatInverse(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + lines.dumpQuat("from_axis_angle.inverse", axisQuatInv) + let verifyInv = quatMultiply(axisQuat, axisQuatInv) + lines.dumpQuat("verify_q_mul_qinv", verifyInv) + + lines.heading("quaternion to axis-angle") + let aa1 = joltQuatGetAxisAngle(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + let aa2 = joltQuatGetAxisAngle(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w) + lines.dumpVec3("from_axis_angle.axis", JoltFloat3(x: aa1.x, y: aa1.y, z: aa1.z)) + lines.dumpScalar("from_axis_angle.angle", aa1.w) + lines.dumpVec3("quat_xyz.axis", JoltFloat3(x: aa2.x, y: aa2.y, z: aa2.z)) + lines.dumpScalar("quat_xyz.angle", aa2.w) + lines.heading("basis directions") lines.appendLine("N/A") lines.heading("element access [row,col]") lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") - lines.appendLine("notes: Jolt Mat44 operator()(row, col) via C++ wrapper") lines.dumpScalar("transform[0,0]", transformM.get(0, 0)) lines.dumpScalar("transform[0,1]", transformM.get(0, 1)) lines.dumpScalar("transform[0,2]", transformM.get(0, 2)) diff --git a/experiments/dump_jolt.txt b/experiments/dump_jolt.txt index 0cdface..2996fc6 100644 --- a/experiments/dump_jolt.txt +++ b/experiments/dump_jolt.txt @@ -270,12 +270,21 @@ from_c_to_d: <+000.707, -000.157, +000.393, +000.567> verify_x_to_y: <+000.000, +001.000, +000.000> verify_c_to_d: <-000.436, +000.218, +000.873> +== quaternion inverse == +from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> +verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> + +== quaternion to axis-angle == +from_axis_angle.axis: <+000.267, +000.535, -000.802> +from_axis_angle.angle: +000.838 +quat_xyz.axis: <+000.235, -000.549, +000.802> +quat_xyz.angle: +001.309 + == basis directions == N/A == element access [row,col] == notes: [row,col] in math convention, element (i,j) = row i, col j -notes: Jolt Mat44 operator()(row, col) via C++ wrapper transform[0,0]: +000.599 transform[0,1]: -002.495 transform[0,2]: +001.870 diff --git a/experiments/dump_vmath.nim b/experiments/dump_vmath.nim index 9b4d1f5..f5b125b 100644 --- a/experiments/dump_vmath.nim +++ b/experiments/dump_vmath.nim @@ -267,6 +267,18 @@ proc main() = lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) + lines.heading("quaternion inverse") + lines.dumpQuat("from_axis_angle.inverse", quatInverse(axisQuat)) + lines.dumpQuat("verify_q_mul_qinv", quatMultiply(axisQuat, quatInverse(axisQuat))) + + lines.heading("quaternion to axis-angle") + let (aaAxis1, aaAngle1) = toAxisAngle(axisQuat) + let (aaAxis2, aaAngle2) = toAxisAngle(quatXYZ) + lines.dumpVec3("from_axis_angle.axis", aaAxis1) + lines.dumpScalar("from_axis_angle.angle", aaAngle1) + lines.dumpVec3("quat_xyz.axis", aaAxis2) + lines.dumpScalar("quat_xyz.angle", aaAngle2) + lines.heading("element access [row,col]") lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") lines.dumpScalar("transform[0,0]", transformM[0, 0]) diff --git a/experiments/dump_vmath.txt b/experiments/dump_vmath.txt index 9ca9779..92014e3 100644 --- a/experiments/dump_vmath.txt +++ b/experiments/dump_vmath.txt @@ -285,6 +285,16 @@ from_c_to_d: <+000.707, -000.157, +000.393, +000.567> verify_x_to_y: <+000.000, +001.000, +000.000> verify_c_to_d: <-000.436, +000.218, +000.873> +== quaternion inverse == +from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> +verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> + +== quaternion to axis-angle == +from_axis_angle.axis: <+000.267, +000.535, -000.802> +from_axis_angle.angle: +000.838 +quat_xyz.axis: <+000.235, -000.549, +000.802> +quat_xyz.angle: +001.309 + == element access [row,col] == notes: [row,col] in math convention, element (i,j) = row i, col j transform[0,0]: +000.599 diff --git a/src/vmath.nim b/src/vmath.nim index 8e35ac2..601cfbb 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -1663,21 +1663,18 @@ proc fromAxisAngle*[T](axis: GVec3[T], angle: T): GVec4[T] = proc toAxisAngle*[T](q: GVec4[T]): (GVec3[T], T) = ## Convert a quaternion to axis and angle. - let cosAngle = q.w - let angle = arccos(cosAngle) * 2.0 - var - sinAngle = sqrt(1.0 - cosAngle * cosAngle) - axis: GVec4[T] - - if abs(sinAngle) < 0.0005: - sinAngle = 1.0 - - axis.x = [ - q.x / sinAngle, - q.y / sinAngle, - q.z / sinAngle - ] - return (axis, angle) + let w = clamp(q.w, T(-1), T(1)) + let angle = arccos(w) * 2 + let sinAngle = sqrt(1 - w * w) + if abs(sinAngle) < T(0.0005): + return (gvec3[T](1, 0, 0), angle) + return (gvec3[T](q.x / sinAngle, q.y / sinAngle, q.z / sinAngle), angle) + +proc quatInverse*[T](q: GVec4[T]): GVec4[T] = + ## Return the inverse of a quaternion. + ## For unit quaternions this is the conjugate. + let d = dot(q, q) + gvec4[T](-q.x / d, -q.y / d, -q.z / d, q.w / d) proc orthogonal*[T](v: GVec3[T]): GVec3[T] = ## Returns orthogonal vector to given vector. diff --git a/tests/test_quaternion.nim b/tests/test_quaternion.nim index 271fdec..da799a5 100644 --- a/tests/test_quaternion.nim +++ b/tests/test_quaternion.nim @@ -89,3 +89,107 @@ block: doAssert a - b ~= dquat(1.5, 1.75, 1.0, 7.0) doAssert a * 2.0 ~= dquat(2, 4, 6, 8) doAssert b / 2.0 ~= dquat(-0.25, 0.125, 1.0, -1.5) + +block: + # toAxisAngle should roundtrip with fromAxisAngle. + let + axis = dvec3(1, 2, -3).normalize() + angle = 48.0.toRadians + q = fromAxisAngle(axis, angle) + (extractedAxis, extractedAngle) = toAxisAngle(q) + doAssert extractedAxis ~= axis + doAssert abs(extractedAngle - angle) < 1e-5 + + # Identity quaternion should give 0 angle. + let (_, identityAngle) = toAxisAngle(dquat(0, 0, 0, 1)) + doAssert abs(identityAngle) < 1e-5 + + # 90 degree rotations around each axis. + for axisVec in [dvec3(1, 0, 0), dvec3(0, 1, 0), dvec3(0, 0, 1)]: + let + q = fromAxisAngle(axisVec, PI / 2) + (a, ang) = toAxisAngle(q) + doAssert a ~= axisVec + doAssert abs(ang - PI / 2) < 1e-5 + + # Random roundtrip fuzz test. + for _ in 0 ..< 2000: + let + axis = randomAxis() + angle = rand(0.001 .. PI) # positive angles, avoid 0 and 2*PI degeneracy + q = fromAxisAngle(axis, angle) + (a, ang) = toAxisAngle(q) + doAssert a ~= axis + doAssert abs(ang - angle) < 1e-4 + +block: + # quatInverse: q * q^-1 should be identity. + let + axis = dvec3(1, 2, -3).normalize() + angle = 48.0.toRadians + q = fromAxisAngle(axis, angle) + qInv = quatInverse(q) + product = quatMultiply(q, qInv) + doAssert quatEquivalent(product, dquat(0, 0, 0, 1)) + + # Inverse should undo rotation. + let + v = dvec3(1.25, -2.5, 3.75) + rotated = quatRotate(q, v) + unrotated = quatRotate(qInv, rotated) + doAssert unrotated ~= v + + # For unit quaternions, inverse == conjugate. + let conjugate = dquat(-q.x, -q.y, -q.z, q.w) + doAssert qInv ~= conjugate + + # Random fuzz test. + for _ in 0 ..< 2000: + let + q = fromAxisAngle(randomAxis(), rand(-PI .. PI)) + qInv = quatInverse(q) + product = quatMultiply(q, qInv) + doAssert quatEquivalent(product, dquat(0, 0, 0, 1)) + +block: + # slerp at endpoints should return the inputs. + let + qx = quatRotateX(0.37) + qz = quatRotateZ(1.24) + doAssert slerp(qx, qz, 0.0) ~= qx + doAssert quatEquivalent(slerp(qx, qz, 1.0), qz) + + # slerp at t=0.5 should be halfway between. + let mid = slerp(qx, qz, 0.5) + # The midpoint should be equidistant in angle from both endpoints. + let angleTo0 = arccos(clamp(dot(qx, mid), -1.0, 1.0)) + let angleTo1 = arccos(clamp(dot(mid, qz), -1.0, 1.0)) + doAssert abs(angleTo0 - angleTo1) < 1e-5 + + # slerp should produce unit quaternions. + for i in 0 .. 10: + let t = i.float64 / 10.0 + let q = slerp(qx, qz, t) + doAssert abs(q.length - 1.0) < 1e-5 + + # slerp with identical quaternions should return the same quaternion. + doAssert slerp(qx, qx, 0.5) ~= qx + + # slerp should handle opposite quaternions (q and -q represent same rotation). + let qNeg = -qz + let result = slerp(qx, qNeg, 0.5) + doAssert abs(result.length - 1.0) < 1e-5 + + # Random fuzz: slerp result should always be unit length. + for _ in 0 ..< 2000: + let + a = fromAxisAngle(randomAxis(), rand(-PI .. PI)) + b = fromAxisAngle(randomAxis(), rand(-PI .. PI)) + t = rand(0.0 .. 1.0) + q = slerp(a, b, t) + doAssert abs(q.length - 1.0) < 1e-5 + # The slerp rotation should produce the same result as its matrix form. + let v = randomAxis() + doAssert quatRotate(q, v) ~= q.mat4() * v + +echo "test_quaternion finished successfully" From d9ed4566764ed591ae110fd158327002bcaf5075 Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 14:56:39 -0700 Subject: [PATCH 08/35] f --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6632583..2b84ab6 100644 --- a/README.md +++ b/README.md @@ -131,13 +131,14 @@ We run identical math operations across vmath, [nim-glm](https://github.com/nick | Cross product | ✅ | ✅ | ✅ | ✅ | | Slerp | ✅ | ✅ | ✅ | ✅ | | fromTwoVectors | ✅ | ✅ | ✅ | ✅ | +| Quat decomposition (sign) | ✅ | ✅ | ✅ | ✅ | +| Scaled-matrix decomposition| ✅ | ✅ | ✅ | ✅ | | Perspective matrix | ✅ | ✅ | ✅ | ❌ | | Ortho matrix | ✅ | ✅ | ✅ | N/A | | LookAt matrix | ✅ | ✅ | ✅ | ✅ | | Euler angle decomposition | ✅ | ✅ | N/A | ✅ | | Element access `[row,col]` | ✅ | ✅ | N/A | ✅ | -| Quat decomposition (sign) | ✅ | ✅ | ✅ | ✅ | -| Scaled-matrix decomposition| ✅ | ✅ | ✅ | ✅ | + ❌ **Perspective matrix**: Jolt Physics uses Z range [0, 1] (Vulkan/DirectX convention) while vmath uses Z range [-1, 1] (OpenGL convention). The X and Y scaling match, but Z-related elements differ. From c5cd218d721c968f8d53e001c78a5eef419c7ff3 Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 14:56:53 -0700 Subject: [PATCH 09/35] f --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b84ab6..591ad28 100644 --- a/README.md +++ b/README.md @@ -133,9 +133,9 @@ We run identical math operations across vmath, [nim-glm](https://github.com/nick | fromTwoVectors | ✅ | ✅ | ✅ | ✅ | | Quat decomposition (sign) | ✅ | ✅ | ✅ | ✅ | | Scaled-matrix decomposition| ✅ | ✅ | ✅ | ✅ | +| LookAt matrix | ✅ | ✅ | ✅ | ✅ | | Perspective matrix | ✅ | ✅ | ✅ | ❌ | | Ortho matrix | ✅ | ✅ | ✅ | N/A | -| LookAt matrix | ✅ | ✅ | ✅ | ✅ | | Euler angle decomposition | ✅ | ✅ | N/A | ✅ | | Element access `[row,col]` | ✅ | ✅ | N/A | ✅ | From 353d6b96a2a2e255292427d2315d8ff9e6331a63 Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 17:28:39 -0700 Subject: [PATCH 10/35] relative path --- experiments/dump_glmatrix.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/experiments/dump_glmatrix.js b/experiments/dump_glmatrix.js index 3e48fef..59b8126 100644 --- a/experiments/dump_glmatrix.js +++ b/experiments/dump_glmatrix.js @@ -1,8 +1,8 @@ -import * as mat4 from "/Users/me/p/gl-matrix/src/mat4.js"; -import * as mat3 from "/Users/me/p/gl-matrix/src/mat3.js"; -import * as vec3 from "/Users/me/p/gl-matrix/src/vec3.js"; -import * as vec4 from "/Users/me/p/gl-matrix/src/vec4.js"; -import * as quat from "/Users/me/p/gl-matrix/src/quat.js"; +import * as mat4 from "../../gl-matrix/src/mat4.js"; +import * as mat3 from "../../gl-matrix/src/mat3.js"; +import * as vec3 from "../../gl-matrix/src/vec3.js"; +import * as vec4 from "../../gl-matrix/src/vec4.js"; +import * as quat from "../../gl-matrix/src/quat.js"; import { writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; From 0f6b3487fae65aea5a0bcabc5d28229f0922811a Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 13 Apr 2026 18:55:27 -0700 Subject: [PATCH 11/35] Add GLSL --- README.md | 57 ++- experiments/dump_glm.nim | 2 +- experiments/dump_glm.txt | 2 +- experiments/dump_glmatrix.js | 2 +- experiments/dump_glmatrix.txt | 2 +- experiments/dump_glsl.comp | 372 +++++++++++++++++++ experiments/dump_glsl.nim | 654 ++++++++++++++++++++++++++++++++++ experiments/dump_glsl.txt | 308 ++++++++++++++++ experiments/dump_jolt.nim | 2 +- experiments/dump_jolt.txt | 2 +- experiments/dump_vmath.nim | 2 +- experiments/dump_vmath.txt | 2 +- 12 files changed, 1370 insertions(+), 37 deletions(-) create mode 100644 experiments/dump_glsl.comp create mode 100644 experiments/dump_glsl.nim create mode 100644 experiments/dump_glsl.txt diff --git a/README.md b/README.md index 591ad28..b4dae26 100644 --- a/README.md +++ b/README.md @@ -110,35 +110,34 @@ This is the same system used in the GLTF file format. vmath follows the standard glTF / OpenGL conventions: right-handed coordinate system, column-major matrix storage, and standard math `[row, col]` indexing. These are the dominant conventions used across graphics and game engines. -We run identical math operations across vmath, [nim-glm](https://github.com/nickelsworth/nim-glm), [gl-matrix](https://github.com/toji/gl-matrix), and [Jolt Physics](https://github.com/jrouwe/JoltPhysics) and compare all results. See [experiments/](experiments/) for the dump scripts. - -| Feature | vmath | nim-GLM | gl-matrix | Jolt Physics | -|----------------------------|:-----:|:-------:|:---------:|:------------:| -| Vectors | ✅ | ✅ | ✅ | ✅ | -| Matrix memory layout | ✅ | ✅ | ✅ | ✅ | -| Matrix multiply | ✅ | ✅ | ✅ | ✅ | -| Matrix-vector multiply | ✅ | ✅ | ✅ | ✅ | -| Rotation matrices | ✅ | ✅ | ✅ | ✅ | -| Translation matrices | ✅ | ✅ | ✅ | ✅ | -| Scale matrices | ✅ | ✅ | ✅ | ✅ | -| Quaternion constructors | ✅ | ✅ | ✅ | ✅ | -| Quaternion multiply | ✅ | ✅ | ✅ | ✅ | -| Quaternion vector rotation | ✅ | ✅ | ✅ | ✅ | -| Quaternion-matrix roundtrip| ✅ | ✅ | ✅ | ✅ | -| Quaternion inverse | ✅ | ✅ | ✅ | ✅ | -| Quaternion to axis-angle | ✅ | ✅ | ✅ | ✅ | -| Matrix inverse | ✅ | ✅ | ✅ | ✅ | -| Cross product | ✅ | ✅ | ✅ | ✅ | -| Slerp | ✅ | ✅ | ✅ | ✅ | -| fromTwoVectors | ✅ | ✅ | ✅ | ✅ | -| Quat decomposition (sign) | ✅ | ✅ | ✅ | ✅ | -| Scaled-matrix decomposition| ✅ | ✅ | ✅ | ✅ | -| LookAt matrix | ✅ | ✅ | ✅ | ✅ | -| Perspective matrix | ✅ | ✅ | ✅ | ❌ | -| Ortho matrix | ✅ | ✅ | ✅ | N/A | -| Euler angle decomposition | ✅ | ✅ | N/A | ✅ | -| Element access `[row,col]` | ✅ | ✅ | N/A | ✅ | - +We run identical math operations across vmath, GLSL, [nim-glm](https://github.com/nickelsworth/nim-glm), [gl-matrix](https://github.com/toji/gl-matrix), and [Jolt Physics](https://github.com/jrouwe/JoltPhysics) and compare all results. See [experiments/](experiments/) for the dump scripts. + +| Feature | vmath | GLSL | nim-GLM | gl-matrix | Jolt Physics | +|----------------------------|:-----:|:----:|:-------:|:---------:|:------------:| +| Vectors | ✅ | ✅ | ✅ | ✅ | ✅ | +| Matrix memory layout | ✅ | ✅ | ✅ | ✅ | ✅ | +| Matrix multiply | ✅ | ✅ | ✅ | ✅ | ✅ | +| Matrix-vector multiply | ✅ | ✅ | ✅ | ✅ | ✅ | +| Rotation matrices | ✅ | ✅ | ✅ | ✅ | ✅ | +| Translation matrices | ✅ | ✅ | ✅ | ✅ | ✅ | +| Scale matrices | ✅ | ✅ | ✅ | ✅ | ✅ | +| Quaternion constructors | ✅ | ✅ | ✅ | ✅ | ✅ | +| Quaternion multiply | ✅ | ✅ | ✅ | ✅ | ✅ | +| Quaternion vector rotation | ✅ | ✅ | ✅ | ✅ | ✅ | +| Quaternion-matrix roundtrip| ✅ | ✅ | ✅ | ✅ | ✅ | +| Quaternion inverse | ✅ | ✅ | ✅ | ✅ | ✅ | +| Quaternion to axis-angle | ✅ | ✅ | ✅ | ✅ | ✅ | +| Matrix inverse | ✅ | ✅ | ✅ | ✅ | ✅ | +| Cross product | ✅ | ✅ | ✅ | ✅ | ✅ | +| Slerp | ✅ | ✅ | ✅ | ✅ | ✅ | +| fromTwoVectors | ✅ | ✅ | ✅ | ✅ | ✅ | +| Quat decomposition (sign) | ✅ | ✅ | ✅ | ✅ | ✅ | +| Scaled-matrix decomposition| ✅ | ✅ | ✅ | ✅ | ✅ | +| LookAt matrix | ✅ | ✅ | ✅ | ✅ | ✅ | +| Perspective matrix | ✅ | ✅ | ✅ | ✅ | ❌ | +| Ortho matrix | ✅ | ✅ | ✅ | ✅ | N/A | +| Euler angle decomposition | ✅ | ✅ | ✅ | N/A | ✅ | +| Element access `[row,col]` | ✅ | ✅ | ✅ | N/A | ✅ | ❌ **Perspective matrix**: Jolt Physics uses Z range [0, 1] (Vulkan/DirectX convention) while vmath uses Z range [-1, 1] (OpenGL convention). The X and Y scaling match, but Z-related elements differ. diff --git a/experiments/dump_glm.nim b/experiments/dump_glm.nim index 6eaaf10..9625ab5 100644 --- a/experiments/dump_glm.nim +++ b/experiments/dump_glm.nim @@ -202,7 +202,7 @@ proc main() = let axisMatQuat = quat(axisMat) lines.dumpQuat("axis_mat.quat", axisMatQuat) lines.dumpMat4("axis_mat.quat.mat4", axisMatQuat.mat4()) - lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero") + lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") lines.dumpQuat("hard_decomp.quat_original", hardQuat) lines.dumpQuat("hard_decomp.quat_from_mat", quat(hardMat)) lines.dumpMat4("hard_decomp.mat4", hardMat) diff --git a/experiments/dump_glm.txt b/experiments/dump_glm.txt index b2bd1d5..945d0bd 100644 --- a/experiments/dump_glm.txt +++ b/experiments/dump_glm.txt @@ -188,7 +188,7 @@ axis_mat.quat.mat4: +000.326 -000.340 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] -hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero +hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> hard_decomp.mat4: diff --git a/experiments/dump_glmatrix.js b/experiments/dump_glmatrix.js index 59b8126..6449646 100644 --- a/experiments/dump_glmatrix.js +++ b/experiments/dump_glmatrix.js @@ -235,7 +235,7 @@ dumpQuat("transform.rotation_only.quat", quatFromMat(rotationOnlyM)); const axisMatQuat = quatFromMat(axisMat); dumpQuat("axis_mat.quat", axisMatQuat); dumpMat4("axis_mat.quat.mat4", mat4FromQuat(axisMatQuat)); -appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero"); +appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero"); dumpQuat("hard_decomp.quat_original", hardQuat); dumpQuat("hard_decomp.quat_from_mat", quatFromMat(hardMat)); dumpMat4("hard_decomp.mat4", hardMat); diff --git a/experiments/dump_glmatrix.txt b/experiments/dump_glmatrix.txt index d65fb20..a50df5d 100644 --- a/experiments/dump_glmatrix.txt +++ b/experiments/dump_glmatrix.txt @@ -188,7 +188,7 @@ axis_mat.quat.mat4: +000.326 -000.340 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] -hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero +hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> hard_decomp.mat4: diff --git a/experiments/dump_glsl.comp b/experiments/dump_glsl.comp new file mode 100644 index 0000000..1776e19 --- /dev/null +++ b/experiments/dump_glsl.comp @@ -0,0 +1,372 @@ +#version 430 +layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +layout (rgba32f, binding = 0) uniform writeonly imageBuffer outputBuffer; + +const float angleA = radians(37.0); +const float angleB = radians(-23.0); +const float angleC = radians(71.0); +const float axisAngle = radians(48.0); + +mat4 identityM() { + return mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +mat4 matA() { + return mat4( + vec4(1.0, 5.0, 9.0, 13.0), + vec4(2.0, 6.0, 10.0, 14.0), + vec4(3.0, 7.0, 11.0, 15.0), + vec4(4.0, 8.0, 12.0, 16.0) + ); +} + +mat4 matB() { + return mat4( + vec4(0.5, 1.5, -3.0, 0.0), + vec4(-1.0, 0.75, 4.0, 1.0), + vec4(2.0, -0.5, 1.25, -1.5), + vec4(0.25, 2.0, -2.5, 3.0) + ); +} + +vec3 vecA() { + return vec3(1.25, -2.5, 3.75); +} + +vec4 vecB() { + return vec4(1.25, -2.5, 3.75, 1.0); +} + +mat4 scaleM() { + return mat4( + vec4(2.0, 0.0, 0.0, 0.0), + vec4(0.0, 3.0, 0.0, 0.0), + vec4(0.0, 0.0, 4.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +mat4 translateM() { + return mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(10.0, 20.0, 30.0, 1.0) + ); +} + +mat4 rotateXM() { + float s = sin(angleA); + float c = cos(angleA); + return mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, c, s, 0.0), + vec4(0.0, -s, c, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +mat4 rotateYM() { + float s = sin(angleB); + float c = cos(angleB); + return mat4( + vec4(c, 0.0, -s, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(s, 0.0, c, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +mat4 rotateZM() { + float s = sin(angleC); + float c = cos(angleC); + return mat4( + vec4(c, s, 0.0, 0.0), + vec4(-s, c, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +mat4 pureRotationM() { + return rotateZM() * rotateYM() * rotateXM(); +} + +vec3 axisNormalized() { + return normalize(vec3(1.0, 2.0, -3.0)); +} + +vec4 quatIdentity() { + return vec4(0.0, 0.0, 0.0, 1.0); +} + +vec4 quatAxisAngle(vec3 axis, float angle) { + vec3 a = normalize(axis); + float s = sin(angle * 0.5); + return vec4(a * s, cos(angle * 0.5)); +} + +vec4 quatX() { + return quatAxisAngle(vec3(1.0, 0.0, 0.0), angleA); +} + +vec4 quatY() { + return quatAxisAngle(vec3(0.0, 1.0, 0.0), angleB); +} + +vec4 quatZ() { + return quatAxisAngle(vec3(0.0, 0.0, 1.0), angleC); +} + +vec4 axisQuat() { + return quatAxisAngle(axisNormalized(), axisAngle); +} + +vec4 quatMultiply(vec4 a, vec4 b) { + return vec4( + a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, + a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z, + a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x, + a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z + ); +} + +vec3 quatRotate(vec4 q, vec3 v) { + vec3 uv = cross(q.xyz, v); + vec3 uuv = cross(q.xyz, uv); + return v + ((uv * q.w) + uuv) * 2.0; +} + +mat4 quatMat4(vec4 q) { + float xx = q.x * q.x; + float xy = q.x * q.y; + float xz = q.x * q.z; + float xw = q.x * q.w; + float yy = q.y * q.y; + float yz = q.y * q.z; + float yw = q.y * q.w; + float zz = q.z * q.z; + float zw = q.z * q.w; + + return mat4( + vec4(1.0 - 2.0 * (yy + zz), 2.0 * (xy + zw), 2.0 * (xz - yw), 0.0), + vec4(2.0 * (xy - zw), 1.0 - 2.0 * (xx + zz), 2.0 * (yz + xw), 0.0), + vec4(2.0 * (xz + yw), 2.0 * (yz - xw), 1.0 - 2.0 * (xx + yy), 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +vec4 mat4Quat(mat4 m) { + float m00 = m[0][0]; + float m11 = m[1][1]; + float m22 = m[2][2]; + + float fourXSquaredMinus1 = m00 - m11 - m22; + float fourYSquaredMinus1 = m11 - m00 - m22; + float fourZSquaredMinus1 = m22 - m00 - m11; + float fourWSquaredMinus1 = m00 + m11 + m22; + + int biggestIndex = 0; + float fourBiggestSquaredMinus1 = fourWSquaredMinus1; + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; + } + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourYSquaredMinus1; + biggestIndex = 2; + } + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourZSquaredMinus1; + biggestIndex = 3; + } + + float biggestVal = sqrt(fourBiggestSquaredMinus1 + 1.0) * 0.5; + float mult = 0.25 / biggestVal; + vec4 q = vec4(0.0); + + if (biggestIndex == 0) { + q.w = biggestVal; + q.x = (m[1][2] - m[2][1]) * mult; + q.y = (m[2][0] - m[0][2]) * mult; + q.z = (m[0][1] - m[1][0]) * mult; + } else if (biggestIndex == 1) { + q.w = (m[1][2] - m[2][1]) * mult; + q.x = biggestVal; + q.y = (m[0][1] + m[1][0]) * mult; + q.z = (m[2][0] + m[0][2]) * mult; + } else if (biggestIndex == 2) { + q.w = (m[2][0] - m[0][2]) * mult; + q.x = (m[0][1] + m[1][0]) * mult; + q.y = biggestVal; + q.z = (m[1][2] + m[2][1]) * mult; + } else { + q.w = (m[0][1] - m[1][0]) * mult; + q.x = (m[2][0] + m[0][2]) * mult; + q.y = (m[1][2] + m[2][1]) * mult; + q.z = biggestVal; + } + + return q; +} + +mat4 axisMat() { + return quatMat4(axisQuat()); +} + +mat4 transformM() { + return translateM() * rotateZM() * rotateYM() * rotateXM() * scaleM(); +} + +mat4 rotationOnly(mat4 m) { + m[3] = vec4(0.0, 0.0, 0.0, 1.0); + return m; +} + +vec3 hardAxis() { + return normalize(vec3(1.0, -2.0, 3.0)); +} + +vec4 hardQuat() { + return quatAxisAngle(hardAxis(), radians(170.0)); +} + +mat4 hardMat() { + return quatMat4(hardQuat()); +} + +mat4 frustumM(float left, float right, float bottom, float top, float nearV, float farV) { + float rl = right - left; + float tb = top - bottom; + float fn = farV - nearV; + return mat4( + vec4((nearV * 2.0) / rl, 0.0, 0.0, 0.0), + vec4(0.0, (nearV * 2.0) / tb, 0.0, 0.0), + vec4((right + left) / rl, (top + bottom) / tb, -(farV + nearV) / fn, -1.0), + vec4(0.0, 0.0, -(farV * nearV * 2.0) / fn, 0.0) + ); +} + +mat4 perspectiveM(float fovyDeg, float aspect, float nearV, float farV) { + float top = nearV * tan(fovyDeg * 3.14159265359 / 360.0); + float right = top * aspect; + return frustumM(-right, right, -top, top, nearV, farV); +} + +mat4 orthoM(float left, float right, float bottom, float top, float nearV, float farV) { + float rl = right - left; + float tb = top - bottom; + float fn = farV - nearV; + return mat4( + vec4(2.0 / rl, 0.0, 0.0, 0.0), + vec4(0.0, 2.0 / tb, 0.0, 0.0), + vec4(0.0, 0.0, -2.0 / fn, 0.0), + vec4(-(left + right) / rl, -(top + bottom) / tb, -(farV + nearV) / fn, 1.0) + ); +} + +mat4 lookAtM(vec3 eye, vec3 center, vec3 up) { + if (all(equal(eye, center))) { + return identityM(); + } + + vec3 z = normalize(eye - center); + vec3 x = cross(up, z); + if (length(x) == 0.0) { + x = vec3(0.0); + } else { + x = normalize(x); + } + vec3 y = cross(z, x); + + return mat4( + vec4(x.x, y.x, z.x, 0.0), + vec4(x.y, y.y, z.y, 0.0), + vec4(x.z, y.z, z.z, 0.0), + vec4(-dot(x, eye), -dot(y, eye), -dot(z, eye), 1.0) + ); +} + +vec4 quatInverse(vec4 q) { + float d = dot(q, q); + return vec4(-q.x / d, -q.y / d, -q.z / d, q.w / d); +} + +vec3 orthogonalV(vec3 v) { + vec3 av = abs(v); + vec3 other; + if (av.x < av.y) { + other = av.x < av.z ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 0.0, 1.0); + } else { + other = av.y < av.z ? vec3(0.0, 1.0, 0.0) : vec3(0.0, 0.0, 1.0); + } + return cross(av, other); +} + +vec4 fromTwoVectorsQ(vec3 a, vec3 b) { + vec3 u = normalize(b); + vec3 v = normalize(a); + if (all(equal(u, -v))) { + vec3 q = normalize(orthogonalV(u)); + return vec4(q.x, q.y, q.z, 0.0); + } + vec3 halfV = normalize(u + v); + vec3 q = cross(v, halfV); + float w = dot(v, halfV); + return vec4(q.x, q.y, q.z, w); +} + +vec4 slerpQ(vec4 a, vec4 b, float t) { + vec4 z = b; + float cosTheta = dot(a, b); + if (cosTheta < 0.0) { + z = -b; + cosTheta = -cosTheta; + } + if (cosTheta > 1.0 - 1e-6) { + return vec4( + a.x + (z.x - a.x) * t, + a.y + (z.y - a.y) * t, + a.z + (z.z - a.z) * t, + a.w + (z.w - a.w) * t + ); + } else { + float angle = acos(cosTheta); + return (sin((1.0 - t) * angle) * a + sin(t * angle) * z) / sin(angle); + } +} + +vec3 quatAxisOnly(vec4 q) { + float cosAngle = q.w; + float sinAngle = sqrt(max(0.0, 1.0 - cosAngle * cosAngle)); + if (abs(sinAngle) < 0.0005) { + sinAngle = 1.0; + } + return vec3(q.x / sinAngle, q.y / sinAngle, q.z / sinAngle); +} + +float quatAngleOnly(vec4 q) { + return acos(q.w) * 2.0; +} + +vec3 quatToAngles(vec4 q) { + float x = q.x; + float y = q.y; + float z = q.z; + float w = q.w; + return vec3( + atan(2.0 * (w * x + y * z), 1.0 - 2.0 * (x * x + y * y)), + asin(2.0 * (w * y - z * x)), + atan(2.0 * (w * z + x * y), 1.0 - 2.0 * (y * y + z * z)) + ); +} + +void main() { + vec4 value = vec4(transformM()[3][3], 0.0, 0.0, 0.0); + imageStore(outputBuffer, 0, value); +} diff --git a/experiments/dump_glsl.nim b/experiments/dump_glsl.nim new file mode 100644 index 0000000..d6a606d --- /dev/null +++ b/experiments/dump_glsl.nim @@ -0,0 +1,654 @@ +import + std/[math, os, strformat, strutils], + opengl, + shady/compute + +const + OutputPath = parentDir(currentSourcePath()) / "dump_glsl.txt" + ShaderPath = parentDir(currentSourcePath()) / "dump_glsl.comp" + +proc cleanFloat(value: float32): float32 = + if abs(value) < 0.0000005'f32: + 0'f32 + else: + value + +proc fmt(value: float32): string = + let cleaned = cleanFloat(value) + let sign = if cleaned < 0'f32: "-" else: "+" + + var whole = int(floor(abs(cleaned).float64)) + var frac = int(round((abs(cleaned).float64 - whole.float64) * 1000.0)) + if frac == 1000: + inc whole + frac = 0 + + result = sign + let wholeText = $whole + for _ in wholeText.len ..< 3: + result.add '0' + result.add wholeText + result.add '.' + + let fracText = $frac + for _ in fracText.len ..< 3: + result.add '0' + result.add fracText + +proc appendLine(lines: var seq[string], line = "") = + lines.add line + +proc dumpScalar(lines: var seq[string], label: string, value: float32) = + lines.appendLine(label & ": " & fmt(value)) + +proc dumpVec3(lines: var seq[string], label: string, value: openArray[float32]) = + lines.appendLine(label & ": <" & fmt(value[0]) & ", " & fmt(value[1]) & ", " & fmt(value[2]) & ">") + +proc dumpVec4(lines: var seq[string], label: string, value: openArray[float32]) = + lines.appendLine(label & ": <" & fmt(value[0]) & ", " & fmt(value[1]) & ", " & fmt(value[2]) & ", " & fmt(value[3]) & ">") + +proc dumpQuat(lines: var seq[string], label: string, value: openArray[float32]) = + lines.appendLine(label & ": <" & fmt(value[0]) & ", " & fmt(value[1]) & ", " & fmt(value[2]) & ", " & fmt(value[3]) & ">") + +proc dumpMat4(lines: var seq[string], label: string, value: openArray[float32]) = + lines.appendLine(label & ":") + lines.appendLine("[") + for offset in countup(0, 12, 4): + lines.appendLine( + " " & + fmt(value[offset + 0]) & " " & + fmt(value[offset + 1]) & " " & + fmt(value[offset + 2]) & " " & + fmt(value[offset + 3]) + ) + lines.appendLine("]") + +proc heading(lines: var seq[string], title: string) = + if lines.len > 0: + lines.appendLine() + lines.appendLine("== " & title & " ==") + +proc glslPrelude(): string = + result = """ +#version 430 +layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +layout (rgba32f, binding = 0) uniform writeonly imageBuffer outputBuffer; + +const float angleA = radians(37.0); +const float angleB = radians(-23.0); +const float angleC = radians(71.0); +const float axisAngle = radians(48.0); + +mat4 identityM() { + return mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +mat4 matA() { + return mat4( + vec4(1.0, 5.0, 9.0, 13.0), + vec4(2.0, 6.0, 10.0, 14.0), + vec4(3.0, 7.0, 11.0, 15.0), + vec4(4.0, 8.0, 12.0, 16.0) + ); +} + +mat4 matB() { + return mat4( + vec4(0.5, 1.5, -3.0, 0.0), + vec4(-1.0, 0.75, 4.0, 1.0), + vec4(2.0, -0.5, 1.25, -1.5), + vec4(0.25, 2.0, -2.5, 3.0) + ); +} + +vec3 vecA() { + return vec3(1.25, -2.5, 3.75); +} + +vec4 vecB() { + return vec4(1.25, -2.5, 3.75, 1.0); +} + +mat4 scaleM() { + return mat4( + vec4(2.0, 0.0, 0.0, 0.0), + vec4(0.0, 3.0, 0.0, 0.0), + vec4(0.0, 0.0, 4.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +mat4 translateM() { + return mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(10.0, 20.0, 30.0, 1.0) + ); +} + +mat4 rotateXM() { + float s = sin(angleA); + float c = cos(angleA); + return mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, c, s, 0.0), + vec4(0.0, -s, c, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +mat4 rotateYM() { + float s = sin(angleB); + float c = cos(angleB); + return mat4( + vec4(c, 0.0, -s, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(s, 0.0, c, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +mat4 rotateZM() { + float s = sin(angleC); + float c = cos(angleC); + return mat4( + vec4(c, s, 0.0, 0.0), + vec4(-s, c, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +mat4 pureRotationM() { + return rotateZM() * rotateYM() * rotateXM(); +} + +vec3 axisNormalized() { + return normalize(vec3(1.0, 2.0, -3.0)); +} + +vec4 quatIdentity() { + return vec4(0.0, 0.0, 0.0, 1.0); +} + +vec4 quatAxisAngle(vec3 axis, float angle) { + vec3 a = normalize(axis); + float s = sin(angle * 0.5); + return vec4(a * s, cos(angle * 0.5)); +} + +vec4 quatX() { + return quatAxisAngle(vec3(1.0, 0.0, 0.0), angleA); +} + +vec4 quatY() { + return quatAxisAngle(vec3(0.0, 1.0, 0.0), angleB); +} + +vec4 quatZ() { + return quatAxisAngle(vec3(0.0, 0.0, 1.0), angleC); +} + +vec4 axisQuat() { + return quatAxisAngle(axisNormalized(), axisAngle); +} + +vec4 quatMultiply(vec4 a, vec4 b) { + return vec4( + a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, + a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z, + a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x, + a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z + ); +} + +vec3 quatRotate(vec4 q, vec3 v) { + vec3 uv = cross(q.xyz, v); + vec3 uuv = cross(q.xyz, uv); + return v + ((uv * q.w) + uuv) * 2.0; +} + +mat4 quatMat4(vec4 q) { + float xx = q.x * q.x; + float xy = q.x * q.y; + float xz = q.x * q.z; + float xw = q.x * q.w; + float yy = q.y * q.y; + float yz = q.y * q.z; + float yw = q.y * q.w; + float zz = q.z * q.z; + float zw = q.z * q.w; + + return mat4( + vec4(1.0 - 2.0 * (yy + zz), 2.0 * (xy + zw), 2.0 * (xz - yw), 0.0), + vec4(2.0 * (xy - zw), 1.0 - 2.0 * (xx + zz), 2.0 * (yz + xw), 0.0), + vec4(2.0 * (xz + yw), 2.0 * (yz - xw), 1.0 - 2.0 * (xx + yy), 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); +} + +vec4 mat4Quat(mat4 m) { + float m00 = m[0][0]; + float m11 = m[1][1]; + float m22 = m[2][2]; + + float fourXSquaredMinus1 = m00 - m11 - m22; + float fourYSquaredMinus1 = m11 - m00 - m22; + float fourZSquaredMinus1 = m22 - m00 - m11; + float fourWSquaredMinus1 = m00 + m11 + m22; + + int biggestIndex = 0; + float fourBiggestSquaredMinus1 = fourWSquaredMinus1; + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; + } + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourYSquaredMinus1; + biggestIndex = 2; + } + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourZSquaredMinus1; + biggestIndex = 3; + } + + float biggestVal = sqrt(fourBiggestSquaredMinus1 + 1.0) * 0.5; + float mult = 0.25 / biggestVal; + vec4 q = vec4(0.0); + + if (biggestIndex == 0) { + q.w = biggestVal; + q.x = (m[1][2] - m[2][1]) * mult; + q.y = (m[2][0] - m[0][2]) * mult; + q.z = (m[0][1] - m[1][0]) * mult; + } else if (biggestIndex == 1) { + q.w = (m[1][2] - m[2][1]) * mult; + q.x = biggestVal; + q.y = (m[0][1] + m[1][0]) * mult; + q.z = (m[2][0] + m[0][2]) * mult; + } else if (biggestIndex == 2) { + q.w = (m[2][0] - m[0][2]) * mult; + q.x = (m[0][1] + m[1][0]) * mult; + q.y = biggestVal; + q.z = (m[1][2] + m[2][1]) * mult; + } else { + q.w = (m[0][1] - m[1][0]) * mult; + q.x = (m[2][0] + m[0][2]) * mult; + q.y = (m[1][2] + m[2][1]) * mult; + q.z = biggestVal; + } + + return q; +} + +mat4 axisMat() { + return quatMat4(axisQuat()); +} + +mat4 transformM() { + return translateM() * rotateZM() * rotateYM() * rotateXM() * scaleM(); +} + +mat4 rotationOnly(mat4 m) { + m[3] = vec4(0.0, 0.0, 0.0, 1.0); + return m; +} + +vec3 hardAxis() { + return normalize(vec3(1.0, -2.0, 3.0)); +} + +vec4 hardQuat() { + return quatAxisAngle(hardAxis(), radians(170.0)); +} + +mat4 hardMat() { + return quatMat4(hardQuat()); +} + +mat4 frustumM(float left, float right, float bottom, float top, float nearV, float farV) { + float rl = right - left; + float tb = top - bottom; + float fn = farV - nearV; + return mat4( + vec4((nearV * 2.0) / rl, 0.0, 0.0, 0.0), + vec4(0.0, (nearV * 2.0) / tb, 0.0, 0.0), + vec4((right + left) / rl, (top + bottom) / tb, -(farV + nearV) / fn, -1.0), + vec4(0.0, 0.0, -(farV * nearV * 2.0) / fn, 0.0) + ); +} + +mat4 perspectiveM(float fovyDeg, float aspect, float nearV, float farV) { + float top = nearV * tan(fovyDeg * 3.14159265359 / 360.0); + float right = top * aspect; + return frustumM(-right, right, -top, top, nearV, farV); +} + +mat4 orthoM(float left, float right, float bottom, float top, float nearV, float farV) { + float rl = right - left; + float tb = top - bottom; + float fn = farV - nearV; + return mat4( + vec4(2.0 / rl, 0.0, 0.0, 0.0), + vec4(0.0, 2.0 / tb, 0.0, 0.0), + vec4(0.0, 0.0, -2.0 / fn, 0.0), + vec4(-(left + right) / rl, -(top + bottom) / tb, -(farV + nearV) / fn, 1.0) + ); +} + +mat4 lookAtM(vec3 eye, vec3 center, vec3 up) { + if (all(equal(eye, center))) { + return identityM(); + } + + vec3 z = normalize(eye - center); + vec3 x = cross(up, z); + if (length(x) == 0.0) { + x = vec3(0.0); + } else { + x = normalize(x); + } + vec3 y = cross(z, x); + + return mat4( + vec4(x.x, y.x, z.x, 0.0), + vec4(x.y, y.y, z.y, 0.0), + vec4(x.z, y.z, z.z, 0.0), + vec4(-dot(x, eye), -dot(y, eye), -dot(z, eye), 1.0) + ); +} + +vec4 quatInverse(vec4 q) { + float d = dot(q, q); + return vec4(-q.x / d, -q.y / d, -q.z / d, q.w / d); +} + +vec3 orthogonalV(vec3 v) { + vec3 av = abs(v); + vec3 other; + if (av.x < av.y) { + other = av.x < av.z ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 0.0, 1.0); + } else { + other = av.y < av.z ? vec3(0.0, 1.0, 0.0) : vec3(0.0, 0.0, 1.0); + } + return cross(av, other); +} + +vec4 fromTwoVectorsQ(vec3 a, vec3 b) { + vec3 u = normalize(b); + vec3 v = normalize(a); + if (all(equal(u, -v))) { + vec3 q = normalize(orthogonalV(u)); + return vec4(q.x, q.y, q.z, 0.0); + } + vec3 halfV = normalize(u + v); + vec3 q = cross(v, halfV); + float w = dot(v, halfV); + return vec4(q.x, q.y, q.z, w); +} + +vec4 slerpQ(vec4 a, vec4 b, float t) { + vec4 z = b; + float cosTheta = dot(a, b); + if (cosTheta < 0.0) { + z = -b; + cosTheta = -cosTheta; + } + if (cosTheta > 1.0 - 1e-6) { + return vec4( + a.x + (z.x - a.x) * t, + a.y + (z.y - a.y) * t, + a.z + (z.z - a.z) * t, + a.w + (z.w - a.w) * t + ); + } else { + float angle = acos(cosTheta); + return (sin((1.0 - t) * angle) * a + sin(t * angle) * z) / sin(angle); + } +} + +vec3 quatAxisOnly(vec4 q) { + float cosAngle = q.w; + float sinAngle = sqrt(max(0.0, 1.0 - cosAngle * cosAngle)); + if (abs(sinAngle) < 0.0005) { + sinAngle = 1.0; + } + return vec3(q.x / sinAngle, q.y / sinAngle, q.z / sinAngle); +} + +float quatAngleOnly(vec4 q) { + return acos(q.w) * 2.0; +} + +vec3 quatToAngles(vec4 q) { + float x = q.x; + float y = q.y; + float z = q.z; + float w = q.w; + return vec3( + atan(2.0 * (w * x + y * z), 1.0 - 2.0 * (x * x + y * y)), + asin(2.0 * (w * y - z * x)), + atan(2.0 * (w * z + x * y), 1.0 - 2.0 * (y * y + z * z)) + ); +} +""" + +proc runComputeShader(shaderSrc: string, invocationCount: int): seq[float32] = + writeFile(ShaderPath, shaderSrc) + initOffscreenWindow() + + let shaderId = compileComputeShader((ShaderPath, shaderSrc)) + glUseProgram(shaderId) + + var + outputBufferId: GLuint + outputTextureId: GLuint + glGenBuffers(1, outputBufferId.addr) + glBindBuffer(GL_TEXTURE_BUFFER, outputBufferId) + glBufferData(GL_TEXTURE_BUFFER, invocationCount * 4 * sizeof(float32), nil, GL_STATIC_DRAW) + + glGenTextures(1, outputTextureId.addr) + glActiveTexture(GL_TEXTURE0) + glBindTexture(GL_TEXTURE_BUFFER, outputTextureId) + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, outputBufferId) + glBindImageTexture(0, outputTextureId, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F) + + glDispatchCompute(invocationCount.GLuint, 1, 1) + glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT or GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) + + result.setLen(invocationCount * 4) + let mapped = cast[ptr UncheckedArray[float32]](glMapNamedBuffer(outputBufferId, GL_READ_ONLY)) + copyMem(result[0].addr, mapped, result.len * sizeof(float32)) + discard glUnmapNamedBuffer(outputBufferId) + + glBindTexture(GL_TEXTURE_BUFFER, 0) + glBindBuffer(GL_TEXTURE_BUFFER, 0) + glDeleteTextures(1, outputTextureId.addr) + glDeleteBuffers(1, outputBufferId.addr) + glDeleteProgram(shaderId) + +proc runMatrix(expr: string): seq[float32] = + let shaderSrc = glslPrelude() & "\n" & fmt""" +void main() {{ + uint index = gl_GlobalInvocationID.x; + mat4 value = {expr}; + imageStore(outputBuffer, int(index), value[int(index)]); +}} +""" + runComputeShader(shaderSrc, 4) + +proc runVec3(expr: string): seq[float32] = + let shaderSrc = glslPrelude() & "\n" & fmt""" +void main() {{ + vec3 value = {expr}; + imageStore(outputBuffer, 0, vec4(value, 0.0)); +}} +""" + runComputeShader(shaderSrc, 1) + +proc runVec4(expr: string): seq[float32] = + let shaderSrc = glslPrelude() & "\n" & fmt""" +void main() {{ + vec4 value = {expr}; + imageStore(outputBuffer, 0, value); +}} +""" + runComputeShader(shaderSrc, 1) + +proc runScalar(expr: string): float32 = + runVec4(expr).toOpenArray(0, 3)[0] + +proc main() = + var lines: seq[string] + + lines.heading("dump") + lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") + + lines.heading("matrix constructors and composition") + lines.dumpMat4("identity", runMatrix("identityM()")) + lines.dumpMat4("matrix_a", runMatrix("matA()")) + lines.dumpMat4("matrix_b", runMatrix("matB()")) + lines.dumpMat4("scale", runMatrix("scaleM()")) + lines.dumpMat4("translate", runMatrix("translateM()")) + lines.dumpMat4("rotate_x", runMatrix("rotateXM()")) + lines.dumpMat4("rotate_y", runMatrix("rotateYM()")) + lines.dumpMat4("rotate_z", runMatrix("rotateZM()")) + lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", runMatrix("pureRotationM()")) + lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", runMatrix("transformM()")) + + lines.heading("matrix multiply") + lines.dumpMat4("lhs", runMatrix("matA()")) + lines.dumpMat4("rhs", runMatrix("matB()")) + lines.dumpMat4("lhs * rhs", runMatrix("matA() * matB()")) + lines.dumpMat4("rhs * lhs", runMatrix("matB() * matA()")) + + lines.heading("matrix vector multiply") + lines.dumpVec3("vec3_input", runVec3("vecA()")) + lines.dumpVec4("vec4_input", runVec4("vecB()")) + lines.dumpVec3("transform * vec3", runVec3("(transformM() * vec4(vecA(), 1.0)).xyz")) + lines.dumpVec4("transform * vec4", runVec4("transformM() * vecB()")) + lines.dumpVec3("rotate_z * vec3", runVec3("(rotateZM() * vec4(vecA(), 1.0)).xyz")) + lines.dumpVec3("translate * vec3", runVec3("(translateM() * vec4(vecA(), 1.0)).xyz")) + + lines.heading("quaternion constructors") + lines.dumpQuat("quat_identity", runVec4("quatIdentity()")) + lines.dumpQuat("quat_rotate_x", runVec4("quatX()")) + lines.dumpQuat("quat_rotate_y", runVec4("quatY()")) + lines.dumpQuat("quat_rotate_z", runVec4("quatZ()")) + lines.dumpVec3("axis_normalized", runVec3("axisNormalized()")) + lines.dumpScalar("axis_angle_radians", runScalar("vec4(axisAngle, 0.0, 0.0, 0.0)")) + lines.dumpQuat("from_axis_angle", runVec4("axisQuat()")) + lines.dumpMat4("from_axis_angle.mat4", runMatrix("axisMat()")) + + lines.heading("quaternion multiply") + lines.dumpQuat("quat_x", runVec4("quatX()")) + lines.dumpQuat("quat_y", runVec4("quatY()")) + lines.dumpQuat("quat_z", runVec4("quatZ()")) + lines.dumpQuat("quat_multiply(quat_x, quat_y)", runVec4("quatMultiply(quatX(), quatY())")) + lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", runVec4("quatMultiply(quatMultiply(quatX(), quatY()), quatZ())")) + lines.dumpMat4("quat_xy.mat4", runMatrix("quatMat4(quatMultiply(quatX(), quatY()))")) + lines.dumpMat4("quat_xyz.mat4", runMatrix("quatMat4(quatMultiply(quatMultiply(quatX(), quatY()), quatZ()))")) + + lines.heading("quaternion vector rotate") + lines.dumpVec3("input", runVec3("vecA()")) + lines.dumpVec3("quat_rotate(quat_x, input)", runVec3("quatRotate(quatX(), vecA())")) + lines.dumpVec3("quat_rotate(quat_y, input)", runVec3("quatRotate(quatY(), vecA())")) + lines.dumpVec3("quat_rotate(quat_z, input)", runVec3("quatRotate(quatZ(), vecA())")) + lines.dumpVec3("quat_rotate(from_axis_angle, input)", runVec3("quatRotate(axisQuat(), vecA())")) + lines.dumpVec3("quat_z * input", runVec3("quatRotate(quatZ(), vecA())")) + + lines.heading("matrix quaternion roundtrip") + lines.dumpQuat("pure_rotation.quat", runVec4("mat4Quat(pureRotationM())")) + lines.dumpMat4("pure_rotation", runMatrix("pureRotationM()")) + lines.dumpMat4("pure_rotation.quat.mat4", runMatrix("quatMat4(mat4Quat(pureRotationM()))")) + lines.dumpMat4("transform.rotation_only", runMatrix("rotationOnly(transformM())")) + lines.dumpQuat("transform.rotation_only.quat", runVec4("mat4Quat(rotationOnly(transformM()))")) + lines.dumpQuat("axis_mat.quat", runVec4("mat4Quat(axisMat())")) + lines.dumpMat4("axis_mat.quat.mat4", runMatrix("quatMat4(mat4Quat(axisMat()))")) + lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") + lines.dumpQuat("hard_decomp.quat_original", runVec4("hardQuat()")) + lines.dumpQuat("hard_decomp.quat_from_mat", runVec4("mat4Quat(hardMat())")) + lines.dumpMat4("hard_decomp.mat4", runMatrix("hardMat()")) + lines.dumpMat4("hard_decomp.quat_from_mat.mat4", runMatrix("quatMat4(mat4Quat(hardMat()))")) + + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + lines.dumpMat4("perspective", runMatrix("perspectiveM(60.0, 1.5, 0.1, 100.0)")) + + lines.heading("ortho matrix") + lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") + lines.dumpMat4("ortho", runMatrix("orthoM(-10.0, 10.0, -7.5, 7.5, 0.1, 100.0)")) + + lines.heading("lookAt matrix") + lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") + lines.dumpMat4("lookAt", runMatrix("lookAtM(vec3(5.0, 5.0, 5.0), vec3(0.0), vec3(0.0, 1.0, 0.0))")) + + lines.heading("euler angle decomposition") + lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") + lines.dumpVec3("pure_rotation.quat.euler", runVec3("quatToAngles(mat4Quat(pureRotationM()))")) + lines.dumpVec3("from_axis_angle.euler", runVec3("quatToAngles(axisQuat())")) + + lines.heading("basis directions") + lines.appendLine("notes: library-specific, N/A for libraries without canonical basis helpers") + lines.dumpVec3("canonical_right", runVec3("vec3(1.0, 0.0, 0.0)")) + lines.dumpVec3("canonical_up", runVec3("vec3(0.0, 1.0, 0.0)")) + lines.dumpVec3("canonical_forward", runVec3("vec3(0.0, 0.0, 1.0)")) + lines.dumpVec3("quat_z.right", runVec3("quatRotate(quatZ(), vec3(1.0, 0.0, 0.0))")) + lines.dumpVec3("quat_z.up", runVec3("quatRotate(quatZ(), vec3(0.0, 1.0, 0.0))")) + lines.dumpVec3("quat_z.forward", runVec3("quatRotate(quatZ(), vec3(0.0, 0.0, 1.0))")) + + lines.heading("matrix inverse") + lines.dumpMat4("transform.inverse", runMatrix("inverse(transformM())")) + lines.dumpMat4("pure_rotation.inverse", runMatrix("inverse(pureRotationM())")) + + lines.heading("cross product") + lines.appendLine("notes: cross(a, b) where a and b are vec3") + lines.dumpVec3("cross(x_axis, y_axis)", runVec3("cross(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0))")) + lines.dumpVec3("cross(y_axis, x_axis)", runVec3("cross(vec3(0.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0))")) + lines.dumpVec3("cross(c, d)", runVec3("cross(normalize(vec3(1.0, 2.0, 3.0)), normalize(vec3(-1.0, 0.5, 2.0)))")) + + lines.heading("slerp") + lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") + lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", runVec4("slerpQ(quatX(), quatZ(), 0.25)")) + lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", runVec4("slerpQ(quatX(), quatZ(), 0.5)")) + lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", runVec4("slerpQ(quatX(), quatZ(), 0.75)")) + + lines.heading("fromTwoVectors") + lines.appendLine("notes: quaternion that rotates vector a to vector b") + lines.dumpQuat("from_x_to_y", runVec4("fromTwoVectorsQ(normalize(vec3(1.0, 0.0, 0.0)), normalize(vec3(0.0, 1.0, 0.0)))")) + lines.dumpQuat("from_c_to_d", runVec4("fromTwoVectorsQ(normalize(vec3(1.0, 2.0, -1.0)), normalize(vec3(-1.0, 0.5, 2.0)))")) + lines.dumpVec3("verify_x_to_y", runVec3("quatRotate(fromTwoVectorsQ(normalize(vec3(1.0, 0.0, 0.0)), normalize(vec3(0.0, 1.0, 0.0))), normalize(vec3(1.0, 0.0, 0.0)))")) + lines.dumpVec3("verify_c_to_d", runVec3("quatRotate(fromTwoVectorsQ(normalize(vec3(1.0, 2.0, -1.0)), normalize(vec3(-1.0, 0.5, 2.0))), normalize(vec3(1.0, 2.0, -1.0)))")) + + lines.heading("quaternion inverse") + lines.dumpQuat("from_axis_angle.inverse", runVec4("quatInverse(axisQuat())")) + lines.dumpQuat("verify_q_mul_qinv", runVec4("quatMultiply(axisQuat(), quatInverse(axisQuat()))")) + + lines.heading("quaternion to axis-angle") + lines.dumpVec3("from_axis_angle.axis", runVec3("quatAxisOnly(axisQuat())")) + lines.dumpScalar("from_axis_angle.angle", runScalar("vec4(quatAngleOnly(axisQuat()), 0.0, 0.0, 0.0)")) + lines.dumpVec3("quat_xyz.axis", runVec3("quatAxisOnly(quatMultiply(quatMultiply(quatX(), quatY()), quatZ()))")) + lines.dumpScalar("quat_xyz.angle", runScalar("vec4(quatAngleOnly(quatMultiply(quatMultiply(quatX(), quatY()), quatZ())), 0.0, 0.0, 0.0)")) + + lines.heading("element access [row,col]") + lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") + lines.dumpScalar("transform[0,0]", runScalar("vec4(transformM()[0][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[0,1]", runScalar("vec4(transformM()[1][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[0,2]", runScalar("vec4(transformM()[2][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[0,3]", runScalar("vec4(transformM()[3][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[1,0]", runScalar("vec4(transformM()[0][1], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[1,3]", runScalar("vec4(transformM()[3][1], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[2,0]", runScalar("vec4(transformM()[0][2], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[2,3]", runScalar("vec4(transformM()[3][2], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[3,3]", runScalar("vec4(transformM()[3][3], 0.0, 0.0, 0.0)")) + + writeFile(OutputPath, lines.join("\n") & "\n") + echo "Wrote ", OutputPath + +main() diff --git a/experiments/dump_glsl.txt b/experiments/dump_glsl.txt new file mode 100644 index 0000000..346e180 --- /dev/null +++ b/experiments/dump_glsl.txt @@ -0,0 +1,308 @@ +== dump == +notes: matrices are printed in raw in-memory order, four scalars per line + +== matrix constructors and composition == +identity: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +matrix_a: +[ + +001.000 +005.000 +009.000 +013.000 + +002.000 +006.000 +010.000 +014.000 + +003.000 +007.000 +011.000 +015.000 + +004.000 +008.000 +012.000 +016.000 +] +matrix_b: +[ + +000.500 +001.500 -003.000 +000.000 + -001.000 +000.750 +004.000 +001.000 + +002.000 -000.500 +001.250 -001.500 + +000.250 +002.000 -002.500 +003.000 +] +scale: +[ + +002.000 +000.000 +000.000 +000.000 + +000.000 +003.000 +000.000 +000.000 + +000.000 +000.000 +004.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +translate: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +010.000 +020.000 +030.000 +001.000 +] +rotate_x: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +000.799 +000.602 +000.000 + +000.000 -000.602 +000.799 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_y: +[ + +000.921 +000.000 +000.391 +000.000 + +000.000 +001.000 +000.000 +000.000 + -000.391 +000.000 +000.921 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_z: +[ + +000.326 +000.946 +000.000 +000.000 + -000.946 +000.326 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation = rotate_z * rotate_y * rotate_x: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform = translate * rotate_z * rotate_y * rotate_x * scale: +[ + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 + +010.000 +020.000 +030.000 +001.000 +] + +== matrix multiply == +lhs: +[ + +001.000 +005.000 +009.000 +013.000 + +002.000 +006.000 +010.000 +014.000 + +003.000 +007.000 +011.000 +015.000 + +004.000 +008.000 +012.000 +016.000 +] +rhs: +[ + +000.500 +001.500 -003.000 +000.000 + -001.000 +000.750 +004.000 +001.000 + +002.000 -000.500 +001.250 -001.500 + +000.250 +002.000 -002.500 +003.000 +] +lhs * rhs: +[ + -005.500 -009.500 -013.500 -017.500 + +016.500 +035.500 +054.500 +073.500 + -001.250 +003.750 +008.750 +013.750 + +008.750 +019.750 +030.750 +041.750 +] +rhs * lhs: +[ + +016.750 +026.750 -004.250 +030.500 + +018.500 +030.500 -004.500 +033.000 + +020.250 +034.250 -004.750 +035.500 + +022.000 +038.000 -005.000 +038.000 +] + +== matrix vector multiply == +vec3_input: <+001.250, -002.500, +003.750> +vec4_input: <+001.250, -002.500, +003.750, +001.000> +transform * vec3: <+023.998, +014.529, +037.849> +transform * vec4: <+023.998, +014.529, +037.849, +001.000> +rotate_z * vec3: <+002.771, +000.368, +003.750> +translate * vec3: <+011.250, +017.500, +033.750> + +== quaternion constructors == +quat_identity: <+000.000, +000.000, +000.000, +001.000> +quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> +quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> +quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> +axis_normalized: <+000.267, +000.535, -000.802> +axis_angle_radians: +000.838 +from_axis_angle: <+000.109, +000.217, -000.326, +000.914> +from_axis_angle.mat4: +[ + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion multiply == +quat_x: <+000.317, +000.000, +000.000, +000.948> +quat_y: <+000.000, -000.199, +000.000, +000.980> +quat_z: <+000.000, +000.000, +000.581, +000.814> +quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> +quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> +quat_xy.mat4: +[ + +000.921 -000.235 +000.312 +000.000 + +000.000 +000.799 +000.602 +000.000 + -000.391 -000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +quat_xyz.mat4: +[ + +000.300 +000.679 +000.671 +000.000 + -000.870 +000.482 -000.099 +000.000 + -000.391 -000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion vector rotate == +input: <+001.250, -002.500, +003.750> +quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> +quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> +quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> +quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> +quat_z * input: <+002.771, +000.368, +003.750> + +== matrix quaternion roundtrip == +pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> +pure_rotation: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation.quat.mat4: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only: +[ + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> +axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> +axis_mat.quat.mat4: +[ + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero +hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.quat_from_mat.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.002 -001.000 + +000.000 +000.000 -000.200 +000.000 +] + +== ortho matrix == +notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 +ortho: +[ + +000.100 +000.000 +000.000 +000.000 + +000.000 +000.133 +000.000 +000.000 + +000.000 +000.000 -000.020 +000.000 + +000.000 +000.000 -001.002 +001.000 +] + +== lookAt matrix == +notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) +lookAt: +[ + +000.707 -000.408 +000.577 +000.000 + +000.000 +000.816 +000.577 +000.000 + -000.707 -000.408 +000.577 +000.000 + +000.000 +000.000 -008.660 +001.000 +] + +== euler angle decomposition == +notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians +pure_rotation.quat.euler: <+000.646, -000.401, +001.239> +from_axis_angle.euler: <+000.064, +000.487, -000.670> + +== basis directions == +notes: library-specific, N/A for libraries without canonical basis helpers +canonical_right: <+001.000, +000.000, +000.000> +canonical_up: <+000.000, +001.000, +000.000> +canonical_forward: <+000.000, +000.000, +001.000> +quat_z.right: <+000.326, +000.946, +000.000> +quat_z.up: <-000.946, +000.326, +000.000> +quat_z.forward: <+000.000, +000.000, +001.000> + +== matrix inverse == +transform.inverse: +[ + +000.150 -000.277 +000.117 +000.000 + +000.435 +000.013 -000.123 +000.000 + +000.195 +000.185 +000.184 +000.000 + -016.063 -003.019 -004.227 +001.000 +] +pure_rotation.inverse: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== cross product == +notes: cross(a, b) where a and b are vec3 +cross(x_axis, y_axis): <+000.000, +000.000, +001.000> +cross(y_axis, x_axis): <+000.000, +000.000, -001.000> +cross(c, d): <+000.292, -000.583, +000.292> + +== slerp == +notes: slerp(a, b, t) between quat_x and quat_z +slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> +slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> +slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> + +== fromTwoVectors == +notes: quaternion that rotates vector a to vector b +from_x_to_y: <+000.000, +000.000, +000.707, +000.707> +from_c_to_d: <+000.707, -000.157, +000.393, +000.567> +verify_x_to_y: <+000.000, +001.000, +000.000> +verify_c_to_d: <-000.436, +000.218, +000.873> + +== quaternion inverse == +from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> +verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> + +== quaternion to axis-angle == +from_axis_angle.axis: <+000.267, +000.535, -000.802> +from_axis_angle.angle: +000.838 +quat_xyz.axis: <+000.235, -000.549, +000.802> +quat_xyz.angle: +001.309 + +== element access [row,col] == +notes: [row,col] in math convention, element (i,j) = row i, col j +transform[0,0]: +000.599 +transform[0,1]: -002.495 +transform[0,2]: +001.870 +transform[0,3]: +010.000 +transform[1,0]: +001.741 +transform[1,3]: +020.000 +transform[2,0]: +000.781 +transform[2,3]: +030.000 +transform[3,3]: +001.000 diff --git a/experiments/dump_jolt.nim b/experiments/dump_jolt.nim index 3b799e9..ddfcad1 100644 --- a/experiments/dump_jolt.nim +++ b/experiments/dump_jolt.nim @@ -221,7 +221,7 @@ proc main() = let axisMatQuat = axisQuat lines.dumpQuat("axis_mat.quat", axisMatQuat) lines.dumpMat4("axis_mat.quat.mat4", joltMat44FromQuat(axisMatQuat.x, axisMatQuat.y, axisMatQuat.z, axisMatQuat.w)) - lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero") + lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") lines.dumpQuat("hard_decomp.quat_original", hardQuat) let hardMatQuat = getQuaternion(hardMat) lines.dumpQuat("hard_decomp.quat_from_mat", hardMatQuat) diff --git a/experiments/dump_jolt.txt b/experiments/dump_jolt.txt index 2996fc6..47f5e86 100644 --- a/experiments/dump_jolt.txt +++ b/experiments/dump_jolt.txt @@ -188,7 +188,7 @@ axis_mat.quat.mat4: +000.326 -000.340 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] -hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero +hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> hard_decomp.mat4: diff --git a/experiments/dump_vmath.nim b/experiments/dump_vmath.nim index f5b125b..2bea40e 100644 --- a/experiments/dump_vmath.nim +++ b/experiments/dump_vmath.nim @@ -202,7 +202,7 @@ proc main() = lines.dumpQuat("transform.rotation_only.quat", rotationOnlyM.quat()) lines.dumpQuat("axis_mat.quat", axisMat.quat()) lines.dumpMat4("axis_mat.quat.mat4", axisMat.quat().mat4()) - lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero") + lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") lines.dumpQuat("hard_decomp.quat_original", hardQuat) lines.dumpQuat("hard_decomp.quat_from_mat", hardMat.quat()) lines.dumpMat4("hard_decomp.mat4", hardMat) diff --git a/experiments/dump_vmath.txt b/experiments/dump_vmath.txt index 92014e3..346e180 100644 --- a/experiments/dump_vmath.txt +++ b/experiments/dump_vmath.txt @@ -188,7 +188,7 @@ axis_mat.quat.mat4: +000.326 -000.340 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] -hard_decomp.note: 170 degrees around (1,-2,3) normalized — w near zero +hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> hard_decomp.mat4: From a19820fda662060b065312aab817248b11d2c8e1 Mon Sep 17 00:00:00 2001 From: treeform Date: Tue, 14 Apr 2026 08:25:40 -0700 Subject: [PATCH 12/35] rename to conformance --- {experiments => conformance}/dump_glm.nim | 0 {experiments => conformance}/dump_glm.txt | 0 {experiments => conformance}/dump_glmatrix.js | 0 {experiments => conformance}/dump_glmatrix.txt | 0 {experiments => conformance}/dump_glsl.comp | 0 {experiments => conformance}/dump_glsl.nim | 0 {experiments => conformance}/dump_glsl.txt | 0 {experiments => conformance}/dump_jolt.nim | 0 {experiments => conformance}/dump_jolt.txt | 0 {experiments => conformance}/dump_vmath.nim | 0 {experiments => conformance}/dump_vmath.txt | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename {experiments => conformance}/dump_glm.nim (100%) rename {experiments => conformance}/dump_glm.txt (100%) rename {experiments => conformance}/dump_glmatrix.js (100%) rename {experiments => conformance}/dump_glmatrix.txt (100%) rename {experiments => conformance}/dump_glsl.comp (100%) rename {experiments => conformance}/dump_glsl.nim (100%) rename {experiments => conformance}/dump_glsl.txt (100%) rename {experiments => conformance}/dump_jolt.nim (100%) rename {experiments => conformance}/dump_jolt.txt (100%) rename {experiments => conformance}/dump_vmath.nim (100%) rename {experiments => conformance}/dump_vmath.txt (100%) diff --git a/experiments/dump_glm.nim b/conformance/dump_glm.nim similarity index 100% rename from experiments/dump_glm.nim rename to conformance/dump_glm.nim diff --git a/experiments/dump_glm.txt b/conformance/dump_glm.txt similarity index 100% rename from experiments/dump_glm.txt rename to conformance/dump_glm.txt diff --git a/experiments/dump_glmatrix.js b/conformance/dump_glmatrix.js similarity index 100% rename from experiments/dump_glmatrix.js rename to conformance/dump_glmatrix.js diff --git a/experiments/dump_glmatrix.txt b/conformance/dump_glmatrix.txt similarity index 100% rename from experiments/dump_glmatrix.txt rename to conformance/dump_glmatrix.txt diff --git a/experiments/dump_glsl.comp b/conformance/dump_glsl.comp similarity index 100% rename from experiments/dump_glsl.comp rename to conformance/dump_glsl.comp diff --git a/experiments/dump_glsl.nim b/conformance/dump_glsl.nim similarity index 100% rename from experiments/dump_glsl.nim rename to conformance/dump_glsl.nim diff --git a/experiments/dump_glsl.txt b/conformance/dump_glsl.txt similarity index 100% rename from experiments/dump_glsl.txt rename to conformance/dump_glsl.txt diff --git a/experiments/dump_jolt.nim b/conformance/dump_jolt.nim similarity index 100% rename from experiments/dump_jolt.nim rename to conformance/dump_jolt.nim diff --git a/experiments/dump_jolt.txt b/conformance/dump_jolt.txt similarity index 100% rename from experiments/dump_jolt.txt rename to conformance/dump_jolt.txt diff --git a/experiments/dump_vmath.nim b/conformance/dump_vmath.nim similarity index 100% rename from experiments/dump_vmath.nim rename to conformance/dump_vmath.nim diff --git a/experiments/dump_vmath.txt b/conformance/dump_vmath.txt similarity index 100% rename from experiments/dump_vmath.txt rename to conformance/dump_vmath.txt From 8ce4a78f7700793743d34e6926612b29b541c2e5 Mon Sep 17 00:00:00 2001 From: treeform Date: Tue, 14 Apr 2026 14:45:37 -0700 Subject: [PATCH 13/35] foo... --- conformance/dump_glm.nim | 392 +++++++++++++++++------------------ conformance/dump_glm.txt | 300 ++------------------------- conformance/dump_jolt.nim | 409 +++++++++++++++++++------------------ conformance/dump_jolt.txt | 292 ++------------------------ conformance/dump_vmath.nim | 396 ++++++++++++++++++----------------- conformance/dump_vmath.txt | 304 ++------------------------- src/vmath.nim | 4 +- 7 files changed, 657 insertions(+), 1440 deletions(-) diff --git a/conformance/dump_glm.nim b/conformance/dump_glm.nim index 9625ab5..a930ea1 100644 --- a/conformance/dump_glm.nim +++ b/conformance/dump_glm.nim @@ -51,15 +51,14 @@ proc dumpQuat(lines: var seq[string], label: string, value: Quatf) = proc dumpMat4(lines: var seq[string], label: string, value: Mat4f) = lines.appendLine(label & ":") lines.appendLine("[") - for col in 0 ..< 4: - lines.appendLine( - " " & - fmt(value[col, 0]) & " " & - fmt(value[col, 1]) & " " & - fmt(value[col, 2]) & " " & - fmt(value[col, 3]) - ) + lines.appendLine(" " & fmt(value.arr[0][0]) & " " & fmt(value.arr[1][0]) & " " & fmt(value.arr[2][0]) & " " & fmt(value.arr[3][0]) ) + lines.appendLine(" " & fmt(value.arr[0][1]) & " " & fmt(value.arr[1][1]) & " " & fmt(value.arr[2][1]) & " " & fmt(value.arr[3][1]) ) + lines.appendLine(" " & fmt(value.arr[0][2]) & " " & fmt(value.arr[1][2]) & " " & fmt(value.arr[2][2]) & " " & fmt(value.arr[3][2]) ) + lines.appendLine(" " & fmt(value.arr[0][3]) & " " & fmt(value.arr[1][3]) & " " & fmt(value.arr[2][3]) & " " & fmt(value.arr[3][3]) ) lines.appendLine("]") + # lines.appendLine("direct " & $value) + # lines.appendLine("array " & $value.arr) + proc heading(lines: var seq[string], title: string) = if lines.len > 0: @@ -95,202 +94,205 @@ proc main() = angleB = -23'f32 * PI.float32 / 180'f32 angleC = 71'f32 * PI.float32 / 180'f32 - matA = mat4FromRows( - 1.0, 2.0, 3.0, 4.0, - 5.0, 6.0, 7.0, 8.0, - 9.0, 10.0, 11.0, 12.0, - 13.0, 14.0, 15.0, 16.0 + matA = mat4f( + vec4f(1.0f, 2.0f, 3.0f, 4.0f), + vec4f(5.0f, 6.0f, 7.0f, 8.0f), + vec4f(9.0f, 10.0f, 11.0f, 12.0f), + vec4f(13.0f, 14.0f, 15.0f, 16.0f) ) - matB = mat4FromRows( - 0.5, -1.0, 2.0, 0.25, - 1.5, 0.75, -0.5, 2.0, - -3.0, 4.0, 1.25, -2.5, - 0.0, 1.0, -1.5, 3.0 + matB = mat4f( + vec4f(-10.0f, -20.0f, -30.0f, -40.0f), + vec4f(50.0f, 60.0f, 70.0f, 80.0f), + vec4f(90.0f, 100.0f, 110.0f, 120.0f), + vec4f(130.0f, 140.0f, 150.0f, 160.0f) ) - vecA = vec3f(1.25, -2.5, 3.75) - vecB = vec4f(1.25, -2.5, 3.75, 1.0) - - scaleM = mat4f(1).scale(2'f32, 3'f32, 4'f32) - translateM = mat4f(1).translate(10'f32, 20'f32, 30'f32) - rotateXM = mat4f(1).rotateX(angleA) - rotateYM = mat4f(1).rotateY(angleB) - rotateZM = mat4f(1).rotateZ(angleC) - pureRotationM = rotateZM * rotateYM * rotateXM - - axis = normalize(vec3f(1.0, 2.0, -3.0)) - axisAngle = 48'f32 * PI.float32 / 180'f32 - axisQuat = quatf(axis, axisAngle) - axisMat = axisQuat.mat4() - transformM = translateM * rotateZM * rotateYM * rotateXM * scaleM - - quatX = quatf(vec3f(1'f32, 0'f32, 0'f32), angleA) - quatY = quatf(vec3f(0'f32, 1'f32, 0'f32), angleB) - quatZ = quatf(vec3f(0'f32, 0'f32, 1'f32), angleC) - quatXY = quatX * quatY - quatXYZ = quatXY * quatZ - - hardAxis = normalize(vec3f(1.0, -2.0, 3.0)) - hardAngle = 170'f32 * PI.float32 / 180'f32 - hardQuat = quatf(hardAxis, hardAngle) - hardMat = hardQuat.mat4() - - rotationOnlyM = rotationOnlyCopy(transformM) + # vecA = vec3f(1.25, -2.5, 3.75) + # vecB = vec4f(1.25, -2.5, 3.75, 1.0) + + # scaleM = mat4f(1).scale(2'f32, 3'f32, 4'f32) + # translateM = mat4f(1).translate(10'f32, 20'f32, 30'f32) + # rotateXM = mat4f(1).rotateX(angleA) + # rotateYM = mat4f(1).rotateY(angleB) + # rotateZM = mat4f(1).rotateZ(angleC) + # pureRotationM = rotateZM * rotateYM * rotateXM + + # axis = normalize(vec3f(1.0, 2.0, -3.0)) + # axisAngle = 48'f32 * PI.float32 / 180'f32 + # axisQuat = quatf(axis, axisAngle) + # axisMat = axisQuat.mat4() + # transformM = translateM * rotateZM * rotateYM * rotateXM * scaleM + + # quatX = quatf(vec3f(1'f32, 0'f32, 0'f32), angleA) + # quatY = quatf(vec3f(0'f32, 1'f32, 0'f32), angleB) + # quatZ = quatf(vec3f(0'f32, 0'f32, 1'f32), angleC) + # quatXY = quatX * quatY + # quatXYZ = quatXY * quatZ + + # hardAxis = normalize(vec3f(1.0, -2.0, 3.0)) + # hardAngle = 170'f32 * PI.float32 / 180'f32 + # hardQuat = quatf(hardAxis, hardAngle) + # hardMat = hardQuat.mat4() + + # rotationOnlyM = rotationOnlyCopy(transformM) lines.heading("dump") lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") lines.heading("matrix constructors and composition") - lines.dumpMat4("identity", mat4f()) + #lines.dumpMat4("identity", mat4f()) lines.dumpMat4("matrix_a", matA) lines.dumpMat4("matrix_b", matB) - lines.dumpMat4("scale", scaleM) - lines.dumpMat4("translate", translateM) - lines.dumpMat4("rotate_x", rotateXM) - lines.dumpMat4("rotate_y", rotateYM) - lines.dumpMat4("rotate_z", rotateZM) - lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) - lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) - - lines.heading("matrix multiply") - lines.dumpMat4("lhs", matA) - lines.dumpMat4("rhs", matB) - lines.dumpMat4("lhs * rhs", matA * matB) - lines.dumpMat4("rhs * lhs", matB * matA) - - lines.heading("matrix vector multiply") - lines.dumpVec3("vec3_input", vecA) - lines.dumpVec4("vec4_input", vecB) - lines.dumpVec3("transform * vec3", transformVec3ByMat4(transformM, vecA)) - lines.dumpVec4("transform * vec4", transformVec4ByMat4(transformM, vecB)) - lines.dumpVec3("rotate_z * vec3", transformVec3ByMat4(rotateZM, vecA)) - lines.dumpVec3("translate * vec3", transformVec3ByMat4(translateM, vecA)) - - lines.heading("quaternion constructors") - lines.dumpQuat("quat_identity", quatf()) - lines.dumpQuat("quat_rotate_x", quatX) - lines.dumpQuat("quat_rotate_y", quatY) - lines.dumpQuat("quat_rotate_z", quatZ) - lines.dumpVec3("axis_normalized", axis) - lines.dumpScalar("axis_angle_radians", axisAngle) - lines.dumpQuat("from_axis_angle", axisQuat) - lines.dumpMat4("from_axis_angle.mat4", axisMat) - - lines.heading("quaternion multiply") - lines.dumpQuat("quat_x", quatX) - lines.dumpQuat("quat_y", quatY) - lines.dumpQuat("quat_z", quatZ) - lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) - lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) - lines.dumpMat4("quat_xy.mat4", quatXY.mat4()) - lines.dumpMat4("quat_xyz.mat4", quatXYZ.mat4()) - - lines.heading("quaternion vector rotate") - lines.dumpVec3("input", vecA) - lines.dumpVec3("quat_rotate(quat_x, input)", quatX * vecA) - lines.dumpVec3("quat_rotate(quat_y, input)", quatY * vecA) - lines.dumpVec3("quat_rotate(quat_z, input)", quatZ * vecA) - lines.dumpVec3("quat_rotate(from_axis_angle, input)", axisQuat * vecA) - lines.dumpVec3("quat_z * input", quatZ * vecA) - - lines.heading("matrix quaternion roundtrip") - let pureRotationQuat = quat(pureRotationM) - lines.dumpQuat("pure_rotation.quat", pureRotationQuat) - lines.dumpMat4("pure_rotation", pureRotationM) - lines.dumpMat4("pure_rotation.quat.mat4", pureRotationQuat.mat4()) - lines.dumpMat4("transform.rotation_only", rotationOnlyM) - lines.dumpQuat("transform.rotation_only.quat", quat(rotationOnlyM)) - let axisMatQuat = quat(axisMat) - lines.dumpQuat("axis_mat.quat", axisMatQuat) - lines.dumpMat4("axis_mat.quat.mat4", axisMatQuat.mat4()) - lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") - lines.dumpQuat("hard_decomp.quat_original", hardQuat) - lines.dumpQuat("hard_decomp.quat_from_mat", quat(hardMat)) - lines.dumpMat4("hard_decomp.mat4", hardMat) - lines.dumpMat4("hard_decomp.quat_from_mat.mat4", quat(hardMat).mat4()) - - lines.heading("perspective matrix") - lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - let fovyRad = 60'f32 * PI.float32 / 180'f32 - lines.dumpMat4("perspective", perspectiveRH(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) - - lines.heading("ortho matrix") - lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") - lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) - - lines.heading("lookAt matrix") - lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") - lines.dumpMat4("lookAt", lookAtRH(vec3f(5, 5, 5), vec3f(0, 0, 0), vec3f(0, 1, 0))) - - lines.heading("euler angle decomposition") - lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") - let pureRotAngles = eulerAngles(quat(pureRotationM)) - let axisAngles = eulerAngles(axisQuat) - lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) - lines.dumpVec3("from_axis_angle.euler", axisAngles) - - lines.heading("matrix inverse") - lines.dumpMat4("transform.inverse", inverse(transformM)) - lines.dumpMat4("pure_rotation.inverse", inverse(pureRotationM)) - - lines.heading("cross product") - lines.appendLine("notes: cross(a, b) where a and b are vec3") - let crossA = vec3f(1, 0, 0) - let crossB = vec3f(0, 1, 0) - let crossC = normalize(vec3f(1, 2, 3)) - let crossD = normalize(vec3f(-1, 0.5, 2)) - lines.dumpVec3("cross(x_axis, y_axis)", cross(crossA, crossB)) - lines.dumpVec3("cross(y_axis, x_axis)", cross(crossB, crossA)) - lines.dumpVec3("cross(c, d)", cross(crossC, crossD)) - - lines.heading("slerp") - lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") - lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", slerp(quatX, quatZ, 0.25'f32)) - lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", slerp(quatX, quatZ, 0.5'f32)) - lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) - - lines.heading("fromTwoVectors") - lines.appendLine("notes: quaternion that rotates vector a to vector b") - lines.appendLine("notes: GLM has no native fromTwoVectors, implemented inline") - proc fromTwoVectors(a, b: Vec3f): Quatf = - let d = dot(a, b) - let c = cross(a, b) - let w = sqrt(dot(a, a) * dot(b, b)) + d - result = quatf(c.x, c.y, c.z, w) - result = normalize(result) - let fromA = vec3f(1, 0, 0) - let fromB = vec3f(0, 1, 0) - let fromC = normalize(vec3f(1, 2, -1)) - let fromD = normalize(vec3f(-1, 0.5, 2)) - lines.dumpQuat("from_x_to_y", fromTwoVectors(fromA, fromB)) - lines.dumpQuat("from_c_to_d", fromTwoVectors(fromC, fromD)) - lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) - lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) - - lines.heading("quaternion inverse") - lines.dumpQuat("from_axis_angle.inverse", inverse(axisQuat)) - lines.dumpQuat("verify_q_mul_qinv", axisQuat * inverse(axisQuat)) - - lines.heading("quaternion to axis-angle") - lines.dumpVec3("from_axis_angle.axis", axis(axisQuat)) - lines.dumpScalar("from_axis_angle.angle", angle(axisQuat)) - lines.dumpVec3("quat_xyz.axis", axis(quatXYZ)) - lines.dumpScalar("quat_xyz.angle", angle(quatXYZ)) - - lines.heading("basis directions") - lines.appendLine("N/A") - - lines.heading("element access [row,col]") - lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") - lines.appendLine("notes: GLM uses [col,row], so element (r,c) = value[c,r]") - lines.dumpScalar("transform[0,0]", transformM[0, 0]) - lines.dumpScalar("transform[0,1]", transformM[1, 0]) - lines.dumpScalar("transform[0,2]", transformM[2, 0]) - lines.dumpScalar("transform[0,3]", transformM[3, 0]) - lines.dumpScalar("transform[1,0]", transformM[0, 1]) - lines.dumpScalar("transform[1,3]", transformM[3, 1]) - lines.dumpScalar("transform[2,0]", transformM[0, 2]) - lines.dumpScalar("transform[2,3]", transformM[3, 2]) - lines.dumpScalar("transform[3,3]", transformM[3, 3]) + lines.dumpMat4("matrix_a * matrix_b", matA * matB) + lines.dumpMat4("matrix_b * matrix_a", matB * matA) + # lines.dumpMat4("matrix_b", matB) + # lines.dumpMat4("scale", scaleM) + # lines.dumpMat4("translate", translateM) + # lines.dumpMat4("rotate_x", rotateXM) + # lines.dumpMat4("rotate_y", rotateYM) + # lines.dumpMat4("rotate_z", rotateZM) + # lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) + # lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) + + # lines.heading("matrix multiply") + # lines.dumpMat4("lhs", matA) + # lines.dumpMat4("rhs", matB) + # lines.dumpMat4("lhs * rhs", matA * matB) + # lines.dumpMat4("rhs * lhs", matB * matA) + + # lines.heading("matrix vector multiply") + # lines.dumpVec3("vec3_input", vecA) + # lines.dumpVec4("vec4_input", vecB) + # lines.dumpVec3("transform * vec3", transformVec3ByMat4(transformM, vecA)) + # lines.dumpVec4("transform * vec4", transformVec4ByMat4(transformM, vecB)) + # lines.dumpVec3("rotate_z * vec3", transformVec3ByMat4(rotateZM, vecA)) + # lines.dumpVec3("translate * vec3", transformVec3ByMat4(translateM, vecA)) + + # lines.heading("quaternion constructors") + # lines.dumpQuat("quat_identity", quatf()) + # lines.dumpQuat("quat_rotate_x", quatX) + # lines.dumpQuat("quat_rotate_y", quatY) + # lines.dumpQuat("quat_rotate_z", quatZ) + # lines.dumpVec3("axis_normalized", axis) + # lines.dumpScalar("axis_angle_radians", axisAngle) + # lines.dumpQuat("from_axis_angle", axisQuat) + # lines.dumpMat4("from_axis_angle.mat4", axisMat) + + # lines.heading("quaternion multiply") + # lines.dumpQuat("quat_x", quatX) + # lines.dumpQuat("quat_y", quatY) + # lines.dumpQuat("quat_z", quatZ) + # lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) + # lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) + # lines.dumpMat4("quat_xy.mat4", quatXY.mat4()) + # lines.dumpMat4("quat_xyz.mat4", quatXYZ.mat4()) + + # lines.heading("quaternion vector rotate") + # lines.dumpVec3("input", vecA) + # lines.dumpVec3("quat_rotate(quat_x, input)", quatX * vecA) + # lines.dumpVec3("quat_rotate(quat_y, input)", quatY * vecA) + # lines.dumpVec3("quat_rotate(quat_z, input)", quatZ * vecA) + # lines.dumpVec3("quat_rotate(from_axis_angle, input)", axisQuat * vecA) + # lines.dumpVec3("quat_z * input", quatZ * vecA) + + # lines.heading("matrix quaternion roundtrip") + # let pureRotationQuat = quat(pureRotationM) + # lines.dumpQuat("pure_rotation.quat", pureRotationQuat) + # lines.dumpMat4("pure_rotation", pureRotationM) + # lines.dumpMat4("pure_rotation.quat.mat4", pureRotationQuat.mat4()) + # lines.dumpMat4("transform.rotation_only", rotationOnlyM) + # lines.dumpQuat("transform.rotation_only.quat", quat(rotationOnlyM)) + # let axisMatQuat = quat(axisMat) + # lines.dumpQuat("axis_mat.quat", axisMatQuat) + # lines.dumpMat4("axis_mat.quat.mat4", axisMatQuat.mat4()) + # lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") + # lines.dumpQuat("hard_decomp.quat_original", hardQuat) + # lines.dumpQuat("hard_decomp.quat_from_mat", quat(hardMat)) + # lines.dumpMat4("hard_decomp.mat4", hardMat) + # lines.dumpMat4("hard_decomp.quat_from_mat.mat4", quat(hardMat).mat4()) + + # lines.heading("perspective matrix") + # lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + # let fovyRad = 60'f32 * PI.float32 / 180'f32 + # lines.dumpMat4("perspective", perspectiveRH(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) + + # lines.heading("ortho matrix") + # lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") + # lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) + + # lines.heading("lookAt matrix") + # lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") + # lines.dumpMat4("lookAt", lookAtRH(vec3f(5, 5, 5), vec3f(0, 0, 0), vec3f(0, 1, 0))) + + # lines.heading("euler angle decomposition") + # lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") + # let pureRotAngles = eulerAngles(quat(pureRotationM)) + # let axisAngles = eulerAngles(axisQuat) + # lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) + # lines.dumpVec3("from_axis_angle.euler", axisAngles) + + # lines.heading("matrix inverse") + # lines.dumpMat4("transform.inverse", inverse(transformM)) + # lines.dumpMat4("pure_rotation.inverse", inverse(pureRotationM)) + + # lines.heading("cross product") + # lines.appendLine("notes: cross(a, b) where a and b are vec3") + # let crossA = vec3f(1, 0, 0) + # let crossB = vec3f(0, 1, 0) + # let crossC = normalize(vec3f(1, 2, 3)) + # let crossD = normalize(vec3f(-1, 0.5, 2)) + # lines.dumpVec3("cross(x_axis, y_axis)", cross(crossA, crossB)) + # lines.dumpVec3("cross(y_axis, x_axis)", cross(crossB, crossA)) + # lines.dumpVec3("cross(c, d)", cross(crossC, crossD)) + + # lines.heading("slerp") + # lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") + # lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", slerp(quatX, quatZ, 0.25'f32)) + # lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", slerp(quatX, quatZ, 0.5'f32)) + # lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) + + # lines.heading("fromTwoVectors") + # lines.appendLine("notes: quaternion that rotates vector a to vector b") + # lines.appendLine("notes: GLM has no native fromTwoVectors, implemented inline") + # proc fromTwoVectors(a, b: Vec3f): Quatf = + # let d = dot(a, b) + # let c = cross(a, b) + # let w = sqrt(dot(a, a) * dot(b, b)) + d + # result = quatf(c.x, c.y, c.z, w) + # result = normalize(result) + # let fromA = vec3f(1, 0, 0) + # let fromB = vec3f(0, 1, 0) + # let fromC = normalize(vec3f(1, 2, -1)) + # let fromD = normalize(vec3f(-1, 0.5, 2)) + # lines.dumpQuat("from_x_to_y", fromTwoVectors(fromA, fromB)) + # lines.dumpQuat("from_c_to_d", fromTwoVectors(fromC, fromD)) + # lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) + # lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) + + # lines.heading("quaternion inverse") + # lines.dumpQuat("from_axis_angle.inverse", inverse(axisQuat)) + # lines.dumpQuat("verify_q_mul_qinv", axisQuat * inverse(axisQuat)) + + # lines.heading("quaternion to axis-angle") + # lines.dumpVec3("from_axis_angle.axis", axis(axisQuat)) + # lines.dumpScalar("from_axis_angle.angle", angle(axisQuat)) + # lines.dumpVec3("quat_xyz.axis", axis(quatXYZ)) + # lines.dumpScalar("quat_xyz.angle", angle(quatXYZ)) + + # lines.heading("basis directions") + # lines.appendLine("N/A") + + # lines.heading("element access [row,col]") + # lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") + # lines.appendLine("notes: GLM uses [col,row], so element (r,c) = value[c,r]") + # lines.dumpScalar("transform[0,0]", transformM[0, 0]) + # lines.dumpScalar("transform[0,1]", transformM[1, 0]) + # lines.dumpScalar("transform[0,2]", transformM[2, 0]) + # lines.dumpScalar("transform[0,3]", transformM[3, 0]) + # lines.dumpScalar("transform[1,0]", transformM[0, 1]) + # lines.dumpScalar("transform[1,3]", transformM[3, 1]) + # lines.dumpScalar("transform[2,0]", transformM[0, 2]) + # lines.dumpScalar("transform[2,3]", transformM[3, 2]) + # lines.dumpScalar("transform[3,3]", transformM[3, 3]) writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/conformance/dump_glm.txt b/conformance/dump_glm.txt index 945d0bd..33b4545 100644 --- a/conformance/dump_glm.txt +++ b/conformance/dump_glm.txt @@ -2,13 +2,6 @@ notes: matrices are printed in raw in-memory order, four scalars per line == matrix constructors and composition == -identity: -[ - +001.000 +000.000 +000.000 +000.000 - +000.000 +001.000 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +000.000 +000.000 +000.000 +001.000 -] matrix_a: [ +001.000 +005.000 +009.000 +013.000 @@ -18,287 +11,22 @@ matrix_a: ] matrix_b: [ - +000.500 +001.500 -003.000 +000.000 - -001.000 +000.750 +004.000 +001.000 - +002.000 -000.500 +001.250 -001.500 - +000.250 +002.000 -002.500 +003.000 -] -scale: -[ - +002.000 +000.000 +000.000 +000.000 - +000.000 +003.000 +000.000 +000.000 - +000.000 +000.000 +004.000 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -translate: -[ - +001.000 +000.000 +000.000 +000.000 - +000.000 +001.000 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +010.000 +020.000 +030.000 +001.000 -] -rotate_x: -[ - +001.000 +000.000 +000.000 +000.000 - +000.000 +000.799 +000.602 +000.000 - +000.000 -000.602 +000.799 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -rotate_y: -[ - +000.921 +000.000 +000.391 +000.000 - +000.000 +001.000 +000.000 +000.000 - -000.391 +000.000 +000.921 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -rotate_z: -[ - +000.326 +000.946 +000.000 +000.000 - -000.946 +000.326 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -pure_rotation = rotate_z * rotate_y * rotate_x: -[ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -transform = translate * rotate_z * rotate_y * rotate_x * scale: -[ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 - +010.000 +020.000 +030.000 +001.000 -] - -== matrix multiply == -lhs: -[ - +001.000 +005.000 +009.000 +013.000 - +002.000 +006.000 +010.000 +014.000 - +003.000 +007.000 +011.000 +015.000 - +004.000 +008.000 +012.000 +016.000 -] -rhs: -[ - +000.500 +001.500 -003.000 +000.000 - -001.000 +000.750 +004.000 +001.000 - +002.000 -000.500 +001.250 -001.500 - +000.250 +002.000 -002.500 +003.000 -] -lhs * rhs: -[ - -005.500 -009.500 -013.500 -017.500 - +016.500 +035.500 +054.500 +073.500 - -001.250 +003.750 +008.750 +013.750 - +008.750 +019.750 +030.750 +041.750 -] -rhs * lhs: -[ - +016.750 +026.750 -004.250 +030.500 - +018.500 +030.500 -004.500 +033.000 - +020.250 +034.250 -004.750 +035.500 - +022.000 +038.000 -005.000 +038.000 -] - -== matrix vector multiply == -vec3_input: <+001.250, -002.500, +003.750> -vec4_input: <+001.250, -002.500, +003.750, +001.000> -transform * vec3: <+023.998, +014.529, +037.849> -transform * vec4: <+023.998, +014.529, +037.849, +001.000> -rotate_z * vec3: <+002.771, +000.368, +003.750> -translate * vec3: <+011.250, +017.500, +033.750> - -== quaternion constructors == -quat_identity: <+000.000, +000.000, +000.000, +001.000> -quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> -quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> -quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> -axis_normalized: <+000.267, +000.535, -000.802> -axis_angle_radians: +000.838 -from_axis_angle: <+000.109, +000.217, -000.326, +000.914> -from_axis_angle.mat4: -[ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== quaternion multiply == -quat_x: <+000.317, +000.000, +000.000, +000.948> -quat_y: <+000.000, -000.199, +000.000, +000.980> -quat_z: <+000.000, +000.000, +000.581, +000.814> -quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> -quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> -quat_xy.mat4: -[ - +000.921 -000.235 +000.312 +000.000 - +000.000 +000.799 +000.602 +000.000 - -000.391 -000.554 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -quat_xyz.mat4: -[ - +000.300 +000.679 +000.671 +000.000 - -000.870 +000.482 -000.099 +000.000 - -000.391 -000.554 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== quaternion vector rotate == -input: <+001.250, -002.500, +003.750> -quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> -quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> -quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> -quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> -quat_z * input: <+002.771, +000.368, +003.750> - -== matrix quaternion roundtrip == -pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> -pure_rotation: -[ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -pure_rotation.quat.mat4: -[ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 + -010.000 +050.000 +090.000 +130.000 + -020.000 +060.000 +100.000 +140.000 + -030.000 +070.000 +110.000 +150.000 + -040.000 +080.000 +120.000 +160.000 ] -transform.rotation_only: +matrix_a * matrix_b: [ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 - +000.000 +000.000 +000.000 +001.000 + -900.000 +2020.000 +3140.000 +4260.000 + -1000.000 +2280.000 +3560.000 +4840.000 + -1100.000 +2540.000 +3980.000 +5420.000 + -1200.000 +2800.000 +4400.000 +6000.000 ] -transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> -axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> -axis_mat.quat.mat4: +matrix_b * matrix_a: [ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 - +000.000 +000.000 +000.000 +001.000 + +880.000 +1920.000 +2960.000 +4000.000 + +960.000 +2080.000 +3200.000 +4320.000 + +1040.000 +2240.000 +3440.000 +4640.000 + +1120.000 +2400.000 +3680.000 +4960.000 ] -hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero -hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> -hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> -hard_decomp.mat4: -[ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -hard_decomp.quat_from_mat.mat4: -[ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== perspective matrix == -notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 -perspective: -[ - +001.155 +000.000 +000.000 +000.000 - +000.000 +001.732 +000.000 +000.000 - +000.000 +000.000 -001.002 -001.000 - +000.000 +000.000 -000.200 +000.000 -] - -== ortho matrix == -notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 -ortho: -[ - +000.100 +000.000 +000.000 +000.000 - +000.000 +000.133 +000.000 +000.000 - +000.000 +000.000 -000.020 +000.000 - +000.000 +000.000 -001.002 +001.000 -] - -== lookAt matrix == -notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) -lookAt: -[ - +000.707 -000.408 +000.577 +000.000 - +000.000 +000.816 +000.577 +000.000 - -000.707 -000.408 +000.577 +000.000 - +000.000 +000.000 -008.660 +001.000 -] - -== euler angle decomposition == -notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians -pure_rotation.quat.euler: <+000.646, -000.401, +001.239> -from_axis_angle.euler: <+000.064, +000.487, -000.670> - -== matrix inverse == -transform.inverse: -[ - +000.150 -000.277 +000.117 +000.000 - +000.435 +000.013 -000.123 +000.000 - +000.195 +000.185 +000.184 +000.000 - -016.063 -003.019 -004.227 +001.000 -] -pure_rotation.inverse: -[ - +000.300 -000.832 +000.467 +000.000 - +000.870 +000.038 -000.491 +000.000 - +000.391 +000.554 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== cross product == -notes: cross(a, b) where a and b are vec3 -cross(x_axis, y_axis): <+000.000, +000.000, +001.000> -cross(y_axis, x_axis): <+000.000, +000.000, -001.000> -cross(c, d): <+000.292, -000.583, +000.292> - -== slerp == -notes: slerp(a, b, t) between quat_x and quat_z -slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> -slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> -slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> - -== fromTwoVectors == -notes: quaternion that rotates vector a to vector b -notes: GLM has no native fromTwoVectors, implemented inline -from_x_to_y: <+000.000, +000.000, +000.707, +000.707> -from_c_to_d: <+000.707, -000.157, +000.393, +000.567> -verify_x_to_y: <+000.000, +001.000, +000.000> -verify_c_to_d: <-000.436, +000.218, +000.873> - -== quaternion inverse == -from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> -verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> - -== quaternion to axis-angle == -from_axis_angle.axis: <+000.267, +000.535, -000.802> -from_axis_angle.angle: +000.838 -quat_xyz.axis: <+000.235, -000.549, +000.802> -quat_xyz.angle: +001.309 - -== basis directions == -N/A - -== element access [row,col] == -notes: [row,col] in math convention, element (i,j) = row i, col j -notes: GLM uses [col,row], so element (r,c) = value[c,r] -transform[0,0]: +000.599 -transform[0,1]: -002.495 -transform[0,2]: +001.870 -transform[0,3]: +010.000 -transform[1,0]: +001.741 -transform[1,3]: +020.000 -transform[2,0]: +000.781 -transform[2,3]: +030.000 -transform[3,3]: +001.000 diff --git a/conformance/dump_jolt.nim b/conformance/dump_jolt.nim index ddfcad1..479c2a5 100644 --- a/conformance/dump_jolt.nim +++ b/conformance/dump_jolt.nim @@ -51,14 +51,19 @@ proc dumpQuat(lines: var seq[string], label: string, value: JoltQuat) = proc dumpMat4(lines: var seq[string], label: string, value: JoltMat44) = lines.appendLine(label & ":") lines.appendLine("[") - for offset in countup(0, 12, 4): - lines.appendLine( - " " & - fmt(value.m[offset + 0]) & " " & - fmt(value.m[offset + 1]) & " " & - fmt(value.m[offset + 2]) & " " & - fmt(value.m[offset + 3]) - ) + # for offset in countup(0, 12, 4): + # lines.appendLine( + # " " & + # fmt(value.m[offset + 0]) & " " & + # fmt(value.m[offset + 1]) & " " & + # fmt(value.m[offset + 2]) & " " & + # fmt(value.m[offset + 3]) + # ) + let d = cast[array[16, float32]](value) + lines.appendLine(" " & fmt(d[0]) & " " & fmt(d[4]) & " " & fmt(d[8]) & " " & fmt(d[12]) ) + lines.appendLine(" " & fmt(d[1]) & " " & fmt(d[5]) & " " & fmt(d[9]) & " " & fmt(d[13]) ) + lines.appendLine(" " & fmt(d[2]) & " " & fmt(d[6]) & " " & fmt(d[10]) & " " & fmt(d[14]) ) + lines.appendLine(" " & fmt(d[3]) & " " & fmt(d[7]) & " " & fmt(d[11]) & " " & fmt(d[15]) ) lines.appendLine("]") proc heading(lines: var seq[string], title: string) = @@ -114,202 +119,212 @@ proc main() = angleB = -23'f32 * PI.float32 / 180'f32 angleC = 71'f32 * PI.float32 / 180'f32 - matA = joltMat44FromMemory( - 1.0, 5.0, 9.0, 13.0, - 2.0, 6.0, 10.0, 14.0, - 3.0, 7.0, 11.0, 15.0, - 4.0, 8.0, 12.0, 16.0 - ) - matB = joltMat44FromMemory( - 0.5, 1.5, -3.0, 0.0, - -1.0, 0.75, 4.0, 1.0, - 2.0, -0.5, 1.25, -1.5, - 0.25, 2.0, -2.5, 3.0 - ) - vecA = JoltFloat3(x: 1.25, y: -2.5, z: 3.75) - vecB = JoltFloat4(x: 1.25, y: -2.5, z: 3.75, w: 1.0) - let - scaleM = joltMat44Scale(2.0, 3.0, 4.0) - translateM = joltMat44Translation(10.0, 20.0, 30.0) - rotateXM = joltMat44RotationX(angleA) - rotateYM = joltMat44RotationY(angleB) - rotateZM = joltMat44RotationZ(angleC) - pureRotationM = multiply(multiply(rotateZM, rotateYM), rotateXM) - let - axis = joltVec3Normalize(1.0, 2.0, -3.0) - axisAngle = 48'f32 * PI.float32 / 180'f32 - axisQuat = quatRotate(axis.x, axis.y, axis.z, axisAngle) - axisMat = joltMat44FromQuat(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - transformM = multiply(multiply(multiply(multiply(translateM, rotateZM), rotateYM), rotateXM), scaleM) - let - quatX = quatRotate(1.0, 0.0, 0.0, angleA) - quatY = quatRotate(0.0, 1.0, 0.0, angleB) - quatZ = quatRotate(0.0, 0.0, 1.0, angleC) - quatXY = quatMultiply(quatX, quatY) - quatXYZ = quatMultiply(quatXY, quatZ) - let - hardAxis = joltVec3Normalize(1.0, -2.0, 3.0) - hardAngle = 170'f32 * PI.float32 / 180'f32 - hardQuat = quatRotate(hardAxis.x, hardAxis.y, hardAxis.z, hardAngle) - hardMat = joltMat44FromQuat(hardQuat.x, hardQuat.y, hardQuat.z, hardQuat.w) - let - rotationOnlyM = getRotationSafe(transformM) + matA = cast[JoltMat44]([ + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f + ]) + + matB = cast[JoltMat44]([ + -10.0f, -20.0f, -30.0f, -40.0f, + 50.0f, 60.0f, 70.0f, 80.0f, + 90.0f, 100.0f, 110.0f, 120.0f, + 130.0f, 140.0f, 150.0f, 160.0f + ]) + # matB = joltMat44FromMemory( + # 0.5, 1.5, -3.0, 0.0, + # -1.0, 0.75, 4.0, 1.0, + # 2.0, -0.5, 1.25, -1.5, + # 0.25, 2.0, -2.5, 3.0 + # ) + # vecA = JoltFloat3(x: 1.25, y: -2.5, z: 3.75) + # vecB = JoltFloat4(x: 1.25, y: -2.5, z: 3.75, w: 1.0) + # let + # scaleM = joltMat44Scale(2.0, 3.0, 4.0) + # translateM = joltMat44Translation(10.0, 20.0, 30.0) + # rotateXM = joltMat44RotationX(angleA) + # rotateYM = joltMat44RotationY(angleB) + # rotateZM = joltMat44RotationZ(angleC) + # pureRotationM = multiply(multiply(rotateZM, rotateYM), rotateXM) + # let + # axis = joltVec3Normalize(1.0, 2.0, -3.0) + # axisAngle = 48'f32 * PI.float32 / 180'f32 + # axisQuat = quatRotate(axis.x, axis.y, axis.z, axisAngle) + # axisMat = joltMat44FromQuat(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + # transformM = multiply(multiply(multiply(multiply(translateM, rotateZM), rotateYM), rotateXM), scaleM) + # let + # quatX = quatRotate(1.0, 0.0, 0.0, angleA) + # quatY = quatRotate(0.0, 1.0, 0.0, angleB) + # quatZ = quatRotate(0.0, 0.0, 1.0, angleC) + # quatXY = quatMultiply(quatX, quatY) + # quatXYZ = quatMultiply(quatXY, quatZ) + # let + # hardAxis = joltVec3Normalize(1.0, -2.0, 3.0) + # hardAngle = 170'f32 * PI.float32 / 180'f32 + # hardQuat = quatRotate(hardAxis.x, hardAxis.y, hardAxis.z, hardAngle) + # hardMat = joltMat44FromQuat(hardQuat.x, hardQuat.y, hardQuat.z, hardQuat.w) + # let + # rotationOnlyM = getRotationSafe(transformM) lines.heading("dump") lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") lines.heading("matrix constructors and composition") - lines.dumpMat4("identity", joltMat44Identity()) + #lines.dumpMat4("identity", joltMat44Identity()) lines.dumpMat4("matrix_a", matA) lines.dumpMat4("matrix_b", matB) - lines.dumpMat4("scale", scaleM) - lines.dumpMat4("translate", translateM) - lines.dumpMat4("rotate_x", rotateXM) - lines.dumpMat4("rotate_y", rotateYM) - lines.dumpMat4("rotate_z", rotateZM) - lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) - lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) - - lines.heading("matrix multiply") - lines.dumpMat4("lhs", matA) - lines.dumpMat4("rhs", matB) - lines.dumpMat4("lhs * rhs", multiply(matA, matB)) - lines.dumpMat4("rhs * lhs", multiply(matB, matA)) - - lines.heading("matrix vector multiply") - lines.dumpVec3("vec3_input", vecA) - lines.dumpVec4("vec4_input", vecB) - lines.dumpVec3("transform * vec3", multiply(transformM, vecA)) - lines.dumpVec4("transform * vec4", multiply(transformM, vecB)) - lines.dumpVec3("rotate_z * vec3", multiply(rotateZM, vecA)) - lines.dumpVec3("translate * vec3", multiply(translateM, vecA)) - - lines.heading("quaternion constructors") - lines.dumpQuat("quat_identity", joltQuatIdentity()) - lines.dumpQuat("quat_rotate_x", quatX) - lines.dumpQuat("quat_rotate_y", quatY) - lines.dumpQuat("quat_rotate_z", quatZ) - lines.dumpVec3("axis_normalized", axis) - lines.dumpScalar("axis_angle_radians", axisAngle) - lines.dumpQuat("from_axis_angle", axisQuat) - lines.dumpMat4("from_axis_angle.mat4", axisMat) - - lines.heading("quaternion multiply") - lines.dumpQuat("quat_x", quatX) - lines.dumpQuat("quat_y", quatY) - lines.dumpQuat("quat_z", quatZ) - lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) - lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) - lines.dumpMat4("quat_xy.mat4", joltMat44FromQuat(quatXY.x, quatXY.y, quatXY.z, quatXY.w)) - lines.dumpMat4("quat_xyz.mat4", joltMat44FromQuat(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w)) - - lines.heading("quaternion vector rotate") - lines.dumpVec3("input", vecA) - lines.dumpVec3("quat_rotate(quat_x, input)", quatRotateVec3(quatX, vecA)) - lines.dumpVec3("quat_rotate(quat_y, input)", quatRotateVec3(quatY, vecA)) - lines.dumpVec3("quat_rotate(quat_z, input)", quatRotateVec3(quatZ, vecA)) - lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotateVec3(axisQuat, vecA)) - lines.dumpVec3("quat_z * input", quatRotateVec3(quatZ, vecA)) - - lines.heading("matrix quaternion roundtrip") - let pureRotationQuat = getQuaternion(pureRotationM) - lines.dumpQuat("pure_rotation.quat", pureRotationQuat) - lines.dumpMat4("pure_rotation", pureRotationM) - lines.dumpMat4("pure_rotation.quat.mat4", joltMat44FromQuat(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w)) - lines.dumpMat4("transform.rotation_only", rotationOnlyM) - lines.dumpQuat("transform.rotation_only.quat", getQuaternion(rotationOnlyM)) - let axisMatQuat = axisQuat - lines.dumpQuat("axis_mat.quat", axisMatQuat) - lines.dumpMat4("axis_mat.quat.mat4", joltMat44FromQuat(axisMatQuat.x, axisMatQuat.y, axisMatQuat.z, axisMatQuat.w)) - lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") - lines.dumpQuat("hard_decomp.quat_original", hardQuat) - let hardMatQuat = getQuaternion(hardMat) - lines.dumpQuat("hard_decomp.quat_from_mat", hardMatQuat) - lines.dumpMat4("hard_decomp.mat4", hardMat) - lines.dumpMat4("hard_decomp.quat_from_mat.mat4", joltMat44FromQuat(hardMatQuat.x, hardMatQuat.y, hardMatQuat.z, hardMatQuat.w)) - - let fovyRad = 60'f32 * PI.float32 / 180'f32 - - lines.heading("perspective matrix") - lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - lines.appendLine("notes: Jolt uses Z range [0,1] (Vulkan/DirectX convention), values will differ from OpenGL [-1,1]") - lines.dumpMat4("perspective", joltMat44Perspective(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) - - lines.heading("ortho matrix") - lines.appendLine("N/A") - - lines.heading("lookAt matrix") - lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") - lines.dumpMat4("lookAt", joltMat44LookAt(5, 5, 5, 0, 0, 0, 0, 1, 0)) - - lines.heading("euler angle decomposition") - lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") - let pureRotEuler = joltQuatGetEulerAngles(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w) - let axisEuler = joltQuatGetEulerAngles(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - lines.dumpVec3("pure_rotation.quat.euler", pureRotEuler) - lines.dumpVec3("from_axis_angle.euler", axisEuler) - - lines.heading("matrix inverse") - var transformInv = transformM - lines.dumpMat4("transform.inverse", joltMat44Inverse(addr transformInv)) - var pureRotInv = pureRotationM - lines.dumpMat4("pure_rotation.inverse", joltMat44Inverse(addr pureRotInv)) - - lines.heading("cross product") - lines.appendLine("notes: cross(a, b) where a and b are vec3") - lines.dumpVec3("cross(x_axis, y_axis)", joltVec3Cross(1, 0, 0, 0, 1, 0)) - lines.dumpVec3("cross(y_axis, x_axis)", joltVec3Cross(0, 1, 0, 1, 0, 0)) - let crossC = joltVec3Normalize(1, 2, 3) - let crossD = joltVec3Normalize(-1, 0.5, 2) - lines.dumpVec3("cross(c, d)", joltVec3Cross(crossC.x, crossC.y, crossC.z, crossD.x, crossD.y, crossD.z)) - - lines.heading("slerp") - lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") - lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.25)) - lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.5)) - lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.75)) - - lines.heading("fromTwoVectors") - lines.appendLine("notes: quaternion that rotates vector a to vector b") - let ftFromA = JoltFloat3(x: 1, y: 0, z: 0) - let ftFromB = JoltFloat3(x: 0, y: 1, z: 0) - let ftFromC = joltVec3Normalize(1, 2, -1) - let ftFromD = joltVec3Normalize(-1, 0.5, 2) - let ftQAB = joltQuatFromTo(ftFromA.x, ftFromA.y, ftFromA.z, ftFromB.x, ftFromB.y, ftFromB.z) - let ftQCD = joltQuatFromTo(ftFromC.x, ftFromC.y, ftFromC.z, ftFromD.x, ftFromD.y, ftFromD.z) - lines.dumpQuat("from_x_to_y", ftQAB) - lines.dumpQuat("from_c_to_d", ftQCD) - lines.dumpVec3("verify_x_to_y", quatRotateVec3(ftQAB, ftFromA)) - lines.dumpVec3("verify_c_to_d", quatRotateVec3(ftQCD, ftFromC)) - - lines.heading("quaternion inverse") - let axisQuatInv = joltQuatInverse(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - lines.dumpQuat("from_axis_angle.inverse", axisQuatInv) - let verifyInv = quatMultiply(axisQuat, axisQuatInv) - lines.dumpQuat("verify_q_mul_qinv", verifyInv) - - lines.heading("quaternion to axis-angle") - let aa1 = joltQuatGetAxisAngle(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - let aa2 = joltQuatGetAxisAngle(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w) - lines.dumpVec3("from_axis_angle.axis", JoltFloat3(x: aa1.x, y: aa1.y, z: aa1.z)) - lines.dumpScalar("from_axis_angle.angle", aa1.w) - lines.dumpVec3("quat_xyz.axis", JoltFloat3(x: aa2.x, y: aa2.y, z: aa2.z)) - lines.dumpScalar("quat_xyz.angle", aa2.w) - - lines.heading("basis directions") - lines.appendLine("N/A") - - lines.heading("element access [row,col]") - lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") - lines.dumpScalar("transform[0,0]", transformM.get(0, 0)) - lines.dumpScalar("transform[0,1]", transformM.get(0, 1)) - lines.dumpScalar("transform[0,2]", transformM.get(0, 2)) - lines.dumpScalar("transform[0,3]", transformM.get(0, 3)) - lines.dumpScalar("transform[1,0]", transformM.get(1, 0)) - lines.dumpScalar("transform[1,3]", transformM.get(1, 3)) - lines.dumpScalar("transform[2,0]", transformM.get(2, 0)) - lines.dumpScalar("transform[2,3]", transformM.get(2, 3)) - lines.dumpScalar("transform[3,3]", transformM.get(3, 3)) + lines.dumpMat4("matrix_a * matrix_b", multiply(matA, matB)) + lines.dumpMat4("matrix_b * matrix_a", multiply(matB, matA)) + # lines.dumpMat4("matrix_b", matB) + # lines.dumpMat4("scale", scaleM) + # lines.dumpMat4("translate", translateM) + # lines.dumpMat4("rotate_x", rotateXM) + # lines.dumpMat4("rotate_y", rotateYM) + # lines.dumpMat4("rotate_z", rotateZM) + # lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) + # lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) + + # lines.heading("matrix multiply") + # lines.dumpMat4("lhs", matA) + # lines.dumpMat4("rhs", matB) + # lines.dumpMat4("lhs * rhs", multiply(matA, matB)) + # lines.dumpMat4("rhs * lhs", multiply(matB, matA)) + + # lines.heading("matrix vector multiply") + # lines.dumpVec3("vec3_input", vecA) + # lines.dumpVec4("vec4_input", vecB) + # lines.dumpVec3("transform * vec3", multiply(transformM, vecA)) + # lines.dumpVec4("transform * vec4", multiply(transformM, vecB)) + # lines.dumpVec3("rotate_z * vec3", multiply(rotateZM, vecA)) + # lines.dumpVec3("translate * vec3", multiply(translateM, vecA)) + + # lines.heading("quaternion constructors") + # lines.dumpQuat("quat_identity", joltQuatIdentity()) + # lines.dumpQuat("quat_rotate_x", quatX) + # lines.dumpQuat("quat_rotate_y", quatY) + # lines.dumpQuat("quat_rotate_z", quatZ) + # lines.dumpVec3("axis_normalized", axis) + # lines.dumpScalar("axis_angle_radians", axisAngle) + # lines.dumpQuat("from_axis_angle", axisQuat) + # lines.dumpMat4("from_axis_angle.mat4", axisMat) + + # lines.heading("quaternion multiply") + # lines.dumpQuat("quat_x", quatX) + # lines.dumpQuat("quat_y", quatY) + # lines.dumpQuat("quat_z", quatZ) + # lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) + # lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) + # lines.dumpMat4("quat_xy.mat4", joltMat44FromQuat(quatXY.x, quatXY.y, quatXY.z, quatXY.w)) + # lines.dumpMat4("quat_xyz.mat4", joltMat44FromQuat(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w)) + + # lines.heading("quaternion vector rotate") + # lines.dumpVec3("input", vecA) + # lines.dumpVec3("quat_rotate(quat_x, input)", quatRotateVec3(quatX, vecA)) + # lines.dumpVec3("quat_rotate(quat_y, input)", quatRotateVec3(quatY, vecA)) + # lines.dumpVec3("quat_rotate(quat_z, input)", quatRotateVec3(quatZ, vecA)) + # lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotateVec3(axisQuat, vecA)) + # lines.dumpVec3("quat_z * input", quatRotateVec3(quatZ, vecA)) + + # lines.heading("matrix quaternion roundtrip") + # let pureRotationQuat = getQuaternion(pureRotationM) + # lines.dumpQuat("pure_rotation.quat", pureRotationQuat) + # lines.dumpMat4("pure_rotation", pureRotationM) + # lines.dumpMat4("pure_rotation.quat.mat4", joltMat44FromQuat(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w)) + # lines.dumpMat4("transform.rotation_only", rotationOnlyM) + # lines.dumpQuat("transform.rotation_only.quat", getQuaternion(rotationOnlyM)) + # let axisMatQuat = axisQuat + # lines.dumpQuat("axis_mat.quat", axisMatQuat) + # lines.dumpMat4("axis_mat.quat.mat4", joltMat44FromQuat(axisMatQuat.x, axisMatQuat.y, axisMatQuat.z, axisMatQuat.w)) + # lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") + # lines.dumpQuat("hard_decomp.quat_original", hardQuat) + # let hardMatQuat = getQuaternion(hardMat) + # lines.dumpQuat("hard_decomp.quat_from_mat", hardMatQuat) + # lines.dumpMat4("hard_decomp.mat4", hardMat) + # lines.dumpMat4("hard_decomp.quat_from_mat.mat4", joltMat44FromQuat(hardMatQuat.x, hardMatQuat.y, hardMatQuat.z, hardMatQuat.w)) + + # let fovyRad = 60'f32 * PI.float32 / 180'f32 + + # lines.heading("perspective matrix") + # lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + # lines.appendLine("notes: Jolt uses Z range [0,1] (Vulkan/DirectX convention), values will differ from OpenGL [-1,1]") + # lines.dumpMat4("perspective", joltMat44Perspective(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) + + # lines.heading("ortho matrix") + # lines.appendLine("N/A") + + # lines.heading("lookAt matrix") + # lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") + # lines.dumpMat4("lookAt", joltMat44LookAt(5, 5, 5, 0, 0, 0, 0, 1, 0)) + + # lines.heading("euler angle decomposition") + # lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") + # let pureRotEuler = joltQuatGetEulerAngles(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w) + # let axisEuler = joltQuatGetEulerAngles(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + # lines.dumpVec3("pure_rotation.quat.euler", pureRotEuler) + # lines.dumpVec3("from_axis_angle.euler", axisEuler) + + # lines.heading("matrix inverse") + # var transformInv = transformM + # lines.dumpMat4("transform.inverse", joltMat44Inverse(addr transformInv)) + # var pureRotInv = pureRotationM + # lines.dumpMat4("pure_rotation.inverse", joltMat44Inverse(addr pureRotInv)) + + # lines.heading("cross product") + # lines.appendLine("notes: cross(a, b) where a and b are vec3") + # lines.dumpVec3("cross(x_axis, y_axis)", joltVec3Cross(1, 0, 0, 0, 1, 0)) + # lines.dumpVec3("cross(y_axis, x_axis)", joltVec3Cross(0, 1, 0, 1, 0, 0)) + # let crossC = joltVec3Normalize(1, 2, 3) + # let crossD = joltVec3Normalize(-1, 0.5, 2) + # lines.dumpVec3("cross(c, d)", joltVec3Cross(crossC.x, crossC.y, crossC.z, crossD.x, crossD.y, crossD.z)) + + # lines.heading("slerp") + # lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") + # lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.25)) + # lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.5)) + # lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.75)) + + # lines.heading("fromTwoVectors") + # lines.appendLine("notes: quaternion that rotates vector a to vector b") + # let ftFromA = JoltFloat3(x: 1, y: 0, z: 0) + # let ftFromB = JoltFloat3(x: 0, y: 1, z: 0) + # let ftFromC = joltVec3Normalize(1, 2, -1) + # let ftFromD = joltVec3Normalize(-1, 0.5, 2) + # let ftQAB = joltQuatFromTo(ftFromA.x, ftFromA.y, ftFromA.z, ftFromB.x, ftFromB.y, ftFromB.z) + # let ftQCD = joltQuatFromTo(ftFromC.x, ftFromC.y, ftFromC.z, ftFromD.x, ftFromD.y, ftFromD.z) + # lines.dumpQuat("from_x_to_y", ftQAB) + # lines.dumpQuat("from_c_to_d", ftQCD) + # lines.dumpVec3("verify_x_to_y", quatRotateVec3(ftQAB, ftFromA)) + # lines.dumpVec3("verify_c_to_d", quatRotateVec3(ftQCD, ftFromC)) + + # lines.heading("quaternion inverse") + # let axisQuatInv = joltQuatInverse(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + # lines.dumpQuat("from_axis_angle.inverse", axisQuatInv) + # let verifyInv = quatMultiply(axisQuat, axisQuatInv) + # lines.dumpQuat("verify_q_mul_qinv", verifyInv) + + # lines.heading("quaternion to axis-angle") + # let aa1 = joltQuatGetAxisAngle(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + # let aa2 = joltQuatGetAxisAngle(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w) + # lines.dumpVec3("from_axis_angle.axis", JoltFloat3(x: aa1.x, y: aa1.y, z: aa1.z)) + # lines.dumpScalar("from_axis_angle.angle", aa1.w) + # lines.dumpVec3("quat_xyz.axis", JoltFloat3(x: aa2.x, y: aa2.y, z: aa2.z)) + # lines.dumpScalar("quat_xyz.angle", aa2.w) + + # lines.heading("basis directions") + # lines.appendLine("N/A") + + # lines.heading("element access [row,col]") + # lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") + # lines.dumpScalar("transform[0,0]", transformM.get(0, 0)) + # lines.dumpScalar("transform[0,1]", transformM.get(0, 1)) + # lines.dumpScalar("transform[0,2]", transformM.get(0, 2)) + # lines.dumpScalar("transform[0,3]", transformM.get(0, 3)) + # lines.dumpScalar("transform[1,0]", transformM.get(1, 0)) + # lines.dumpScalar("transform[1,3]", transformM.get(1, 3)) + # lines.dumpScalar("transform[2,0]", transformM.get(2, 0)) + # lines.dumpScalar("transform[2,3]", transformM.get(2, 3)) + # lines.dumpScalar("transform[3,3]", transformM.get(3, 3)) writeFile(OutputPath, lines.join("\n") & "\n") physics.destroy() diff --git a/conformance/dump_jolt.txt b/conformance/dump_jolt.txt index 47f5e86..33b4545 100644 --- a/conformance/dump_jolt.txt +++ b/conformance/dump_jolt.txt @@ -2,13 +2,6 @@ notes: matrices are printed in raw in-memory order, four scalars per line == matrix constructors and composition == -identity: -[ - +001.000 +000.000 +000.000 +000.000 - +000.000 +001.000 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +000.000 +000.000 +000.000 +001.000 -] matrix_a: [ +001.000 +005.000 +009.000 +013.000 @@ -18,279 +11,22 @@ matrix_a: ] matrix_b: [ - +000.500 +001.500 -003.000 +000.000 - -001.000 +000.750 +004.000 +001.000 - +002.000 -000.500 +001.250 -001.500 - +000.250 +002.000 -002.500 +003.000 -] -scale: -[ - +002.000 +000.000 +000.000 +000.000 - +000.000 +003.000 +000.000 +000.000 - +000.000 +000.000 +004.000 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -translate: -[ - +001.000 +000.000 +000.000 +000.000 - +000.000 +001.000 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +010.000 +020.000 +030.000 +001.000 -] -rotate_x: -[ - +001.000 +000.000 +000.000 +000.000 - +000.000 +000.799 +000.602 +000.000 - +000.000 -000.602 +000.799 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -rotate_y: -[ - +000.921 +000.000 +000.391 +000.000 - +000.000 +001.000 +000.000 +000.000 - -000.391 +000.000 +000.921 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -rotate_z: -[ - +000.326 +000.946 +000.000 +000.000 - -000.946 +000.326 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -pure_rotation = rotate_z * rotate_y * rotate_x: -[ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -transform = translate * rotate_z * rotate_y * rotate_x * scale: -[ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 - +010.000 +020.000 +030.000 +001.000 -] - -== matrix multiply == -lhs: -[ - +001.000 +005.000 +009.000 +013.000 - +002.000 +006.000 +010.000 +014.000 - +003.000 +007.000 +011.000 +015.000 - +004.000 +008.000 +012.000 +016.000 -] -rhs: -[ - +000.500 +001.500 -003.000 +000.000 - -001.000 +000.750 +004.000 +001.000 - +002.000 -000.500 +001.250 -001.500 - +000.250 +002.000 -002.500 +003.000 -] -lhs * rhs: -[ - -005.500 -009.500 -013.500 -017.500 - +016.500 +035.500 +054.500 +073.500 - -001.250 +003.750 +008.750 +013.750 - +008.750 +019.750 +030.750 +041.750 -] -rhs * lhs: -[ - +016.750 +026.750 -004.250 +030.500 - +018.500 +030.500 -004.500 +033.000 - +020.250 +034.250 -004.750 +035.500 - +022.000 +038.000 -005.000 +038.000 -] - -== matrix vector multiply == -vec3_input: <+001.250, -002.500, +003.750> -vec4_input: <+001.250, -002.500, +003.750, +001.000> -transform * vec3: <+023.998, +014.529, +037.849> -transform * vec4: <+023.998, +014.529, +037.849, +001.000> -rotate_z * vec3: <+002.771, +000.368, +003.750> -translate * vec3: <+011.250, +017.500, +033.750> - -== quaternion constructors == -quat_identity: <+000.000, +000.000, +000.000, +001.000> -quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> -quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> -quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> -axis_normalized: <+000.267, +000.535, -000.802> -axis_angle_radians: +000.838 -from_axis_angle: <+000.109, +000.217, -000.326, +000.914> -from_axis_angle.mat4: -[ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== quaternion multiply == -quat_x: <+000.317, +000.000, +000.000, +000.948> -quat_y: <+000.000, -000.199, +000.000, +000.980> -quat_z: <+000.000, +000.000, +000.581, +000.814> -quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> -quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> -quat_xy.mat4: -[ - +000.921 -000.235 +000.312 +000.000 - +000.000 +000.799 +000.602 +000.000 - -000.391 -000.554 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -quat_xyz.mat4: -[ - +000.300 +000.679 +000.671 +000.000 - -000.870 +000.482 -000.099 +000.000 - -000.391 -000.554 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== quaternion vector rotate == -input: <+001.250, -002.500, +003.750> -quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> -quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> -quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> -quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> -quat_z * input: <+002.771, +000.368, +003.750> - -== matrix quaternion roundtrip == -pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> -pure_rotation: -[ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -pure_rotation.quat.mat4: -[ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -transform.rotation_only: -[ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> -axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> -axis_mat.quat.mat4: -[ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero -hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> -hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> -hard_decomp.mat4: -[ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -hard_decomp.quat_from_mat.mat4: -[ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 - +000.000 +000.000 +000.000 +001.000 + -010.000 +050.000 +090.000 +130.000 + -020.000 +060.000 +100.000 +140.000 + -030.000 +070.000 +110.000 +150.000 + -040.000 +080.000 +120.000 +160.000 ] - -== perspective matrix == -notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 -notes: Jolt uses Z range [0,1] (Vulkan/DirectX convention), values will differ from OpenGL [-1,1] -perspective: +matrix_a * matrix_b: [ - +001.155 +000.000 +000.000 +000.000 - +000.000 +001.732 +000.000 +000.000 - +000.000 +000.000 -001.001 -001.000 - +000.000 +000.000 -000.100 +000.000 + -900.000 +2020.000 +3140.000 +4260.000 + -1000.000 +2280.000 +3560.000 +4840.000 + -1100.000 +2540.000 +3980.000 +5420.000 + -1200.000 +2800.000 +4400.000 +6000.000 ] - -== ortho matrix == -N/A - -== lookAt matrix == -notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) -lookAt: +matrix_b * matrix_a: [ - +000.707 -000.408 +000.577 +000.000 - +000.000 +000.816 +000.577 +000.000 - -000.707 -000.408 +000.577 +000.000 - +000.000 +000.000 -008.660 +001.000 + +880.000 +1920.000 +2960.000 +4000.000 + +960.000 +2080.000 +3200.000 +4320.000 + +1040.000 +2240.000 +3440.000 +4640.000 + +1120.000 +2400.000 +3680.000 +4960.000 ] - -== euler angle decomposition == -notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians -pure_rotation.quat.euler: <+000.646, -000.401, +001.239> -from_axis_angle.euler: <+000.064, +000.487, -000.670> - -== matrix inverse == -transform.inverse: -[ - +000.150 -000.277 +000.117 +000.000 - +000.435 +000.013 -000.123 +000.000 - +000.195 +000.185 +000.184 +000.000 - -016.063 -003.019 -004.227 +001.000 -] -pure_rotation.inverse: -[ - +000.300 -000.832 +000.467 +000.000 - +000.870 +000.038 -000.491 +000.000 - +000.391 +000.554 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== cross product == -notes: cross(a, b) where a and b are vec3 -cross(x_axis, y_axis): <+000.000, +000.000, +001.000> -cross(y_axis, x_axis): <+000.000, +000.000, -001.000> -cross(c, d): <+000.292, -000.583, +000.292> - -== slerp == -notes: slerp(a, b, t) between quat_x and quat_z -slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> -slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> -slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> - -== fromTwoVectors == -notes: quaternion that rotates vector a to vector b -from_x_to_y: <+000.000, +000.000, +000.707, +000.707> -from_c_to_d: <+000.707, -000.157, +000.393, +000.567> -verify_x_to_y: <+000.000, +001.000, +000.000> -verify_c_to_d: <-000.436, +000.218, +000.873> - -== quaternion inverse == -from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> -verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> - -== quaternion to axis-angle == -from_axis_angle.axis: <+000.267, +000.535, -000.802> -from_axis_angle.angle: +000.838 -quat_xyz.axis: <+000.235, -000.549, +000.802> -quat_xyz.angle: +001.309 - -== basis directions == -N/A - -== element access [row,col] == -notes: [row,col] in math convention, element (i,j) = row i, col j -transform[0,0]: +000.599 -transform[0,1]: -002.495 -transform[0,2]: +001.870 -transform[0,3]: +010.000 -transform[1,0]: +001.741 -transform[1,3]: +020.000 -transform[2,0]: +000.781 -transform[2,3]: +030.000 -transform[3,3]: +001.000 diff --git a/conformance/dump_vmath.nim b/conformance/dump_vmath.nim index 2bea40e..4496acd 100644 --- a/conformance/dump_vmath.nim +++ b/conformance/dump_vmath.nim @@ -1,6 +1,6 @@ import std/[math, os, strutils], - vmath + vmath {.all.} const OutputPath = parentDir(currentSourcePath()) / "dump_vmath.txt" @@ -50,17 +50,17 @@ proc dumpQuat(lines: var seq[string], label: string, value: Quat) = proc dumpMat4(lines: var seq[string], label: string, value: Mat4) = lines.appendLine(label & ":") + let d = GMat4[float32](value) lines.appendLine("[") - for col in 0 ..< 4: - lines.appendLine( - " " & - fmt(value[0, col]) & " " & - fmt(value[1, col]) & " " & - fmt(value[2, col]) & " " & - fmt(value[3, col]) - ) + lines.appendLine(" " & fmt(d.arr[0]) & " " & fmt(d.arr[4]) & " " & fmt(d.arr[8]) & " " & fmt(d.arr[12]) ) + lines.appendLine(" " & fmt(d.arr[1]) & " " & fmt(d.arr[5]) & " " & fmt(d.arr[9]) & " " & fmt(d.arr[13]) ) + lines.appendLine(" " & fmt(d.arr[2]) & " " & fmt(d.arr[6]) & " " & fmt(d.arr[10]) & " " & fmt(d.arr[14]) ) + lines.appendLine(" " & fmt(d.arr[3]) & " " & fmt(d.arr[7]) & " " & fmt(d.arr[11]) & " " & fmt(d.arr[15]) ) lines.appendLine("]") + # lines.appendLine("direct " & $value) + # lines.appendLine("array " & $value.arr) + proc heading(lines: var seq[string], title: string) = if lines.len > 0: lines.appendLine() @@ -94,202 +94,214 @@ proc main() = angleB = -23'f32.toRadians angleC = 71'f32.toRadians - matA = mat4FromRows( + + matA = mat4( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0 ) - matB = mat4FromRows( - 0.5, -1.0, 2.0, 0.25, - 1.5, 0.75, -0.5, 2.0, - -3.0, 4.0, 1.25, -2.5, - 0.0, 1.0, -1.5, 3.0 + + # matA = mat4FromRows( + # 1.0, 2.0, 3.0, 4.0, + # 5.0, 6.0, 7.0, 8.0, + # 9.0, 10.0, 11.0, 12.0, + # 13.0, 14.0, 15.0, 16.0 + # ) + matB = mat4( + -10, -20, -30, -40, + 50, 60, 70, 80, + 90, 100, 110, 120, + 130, 140, 150, 160 ) - vecA = vec3(1.25, -2.5, 3.75) - vecB = vec4(1.25, -2.5, 3.75, 1.0) - - scaleM = scale(vec3(2.0, 3.0, 4.0)) - translateM = translate(vec3(10.0, 20.0, 30.0)) - rotateXM = rotateX(angleA) - rotateYM = rotateY(angleB) - rotateZM = rotateZ(angleC) - pureRotationM = rotateZM * rotateYM * rotateXM - axis = normalize(vec3(1.0, 2.0, -3.0)) - axisAngle = 48'f32.toRadians - axisQuat = fromAxisAngle(axis, axisAngle) - axisMat = axisQuat.mat4() - transformM = translateM * rotateZM * rotateYM * rotateXM * scaleM - - quatX = quatRotateX(angleA) - quatY = quatRotateY(angleB) - quatZ = quatRotateZ(angleC) - quatXY = quatMultiply(quatX, quatY) - quatXYZ = quatMultiply(quatXY, quatZ) - - # Hard quat decomposition case: 170° around arbitrary axis (w near zero) - hardAxis = normalize(vec3(1.0, -2.0, 3.0)) - hardAngle = 170'f32.toRadians - hardQuat = fromAxisAngle(hardAxis, hardAngle) - hardMat = hardQuat.mat4() - - rotationOnlyM = rotationOnlyCopy(transformM) - basisRight = vec3(1.0, 0.0, 0.0) - basisUp = vec3(0.0, 1.0, 0.0) - basisForward = vec3(0.0, 0.0, 1.0) + # vecA = vec3(1.25, -2.5, 3.75) + # vecB = vec4(1.25, -2.5, 3.75, 1.0) + + # scaleM = scale(vec3(2.0, 3.0, 4.0)) + # translateM = translate(vec3(10.0, 20.0, 30.0)) + # rotateXM = rotateX(angleA) + # rotateYM = rotateY(angleB) + # rotateZM = rotateZ(angleC) + # pureRotationM = rotateZM * rotateYM * rotateXM + # axis = normalize(vec3(1.0, 2.0, -3.0)) + # axisAngle = 48'f32.toRadians + # axisQuat = fromAxisAngle(axis, axisAngle) + # axisMat = axisQuat.mat4() + # transformM = translateM * rotateZM * rotateYM * rotateXM * scaleM + + # quatX = quatRotateX(angleA) + # quatY = quatRotateY(angleB) + # quatZ = quatRotateZ(angleC) + # quatXY = quatMultiply(quatX, quatY) + # quatXYZ = quatMultiply(quatXY, quatZ) + + # # Hard quat decomposition case: 170° around arbitrary axis (w near zero) + # hardAxis = normalize(vec3(1.0, -2.0, 3.0)) + # hardAngle = 170'f32.toRadians + # hardQuat = fromAxisAngle(hardAxis, hardAngle) + # hardMat = hardQuat.mat4() + + # rotationOnlyM = rotationOnlyCopy(transformM) + # basisRight = vec3(1.0, 0.0, 0.0) + # basisUp = vec3(0.0, 1.0, 0.0) + # basisForward = vec3(0.0, 0.0, 1.0) lines.heading("dump") lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") lines.heading("matrix constructors and composition") - lines.dumpMat4("identity", mat4()) + #lines.dumpMat4("identity", mat4()) lines.dumpMat4("matrix_a", matA) lines.dumpMat4("matrix_b", matB) - lines.dumpMat4("scale", scaleM) - lines.dumpMat4("translate", translateM) - lines.dumpMat4("rotate_x", rotateXM) - lines.dumpMat4("rotate_y", rotateYM) - lines.dumpMat4("rotate_z", rotateZM) - lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) - lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) - - lines.heading("matrix multiply") - lines.dumpMat4("lhs", matA) - lines.dumpMat4("rhs", matB) - lines.dumpMat4("lhs * rhs", matA * matB) - lines.dumpMat4("rhs * lhs", matB * matA) - - lines.heading("matrix vector multiply") - lines.dumpVec3("vec3_input", vecA) - lines.dumpVec4("vec4_input", vecB) - lines.dumpVec3("transform * vec3", transformM * vecA) - lines.dumpVec4("transform * vec4", transformM * vecB) - lines.dumpVec3("rotate_z * vec3", rotateZM * vecA) - lines.dumpVec3("translate * vec3", translateM * vecA) - - lines.heading("quaternion constructors") - lines.dumpQuat("quat_identity", quat()) - lines.dumpQuat("quat_rotate_x", quatX) - lines.dumpQuat("quat_rotate_y", quatY) - lines.dumpQuat("quat_rotate_z", quatZ) - lines.dumpVec3("axis_normalized", axis) - lines.dumpScalar("axis_angle_radians", axisAngle) - lines.dumpQuat("from_axis_angle", axisQuat) - lines.dumpMat4("from_axis_angle.mat4", axisMat) - - lines.heading("quaternion multiply") - lines.dumpQuat("quat_x", quatX) - lines.dumpQuat("quat_y", quatY) - lines.dumpQuat("quat_z", quatZ) - lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) - lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) - lines.dumpMat4("quat_xy.mat4", quatXY.mat4()) - lines.dumpMat4("quat_xyz.mat4", quatXYZ.mat4()) - - lines.heading("quaternion vector rotate") - lines.dumpVec3("input", vecA) - lines.dumpVec3("quat_rotate(quat_x, input)", quatRotate(quatX, vecA)) - lines.dumpVec3("quat_rotate(quat_y, input)", quatRotate(quatY, vecA)) - lines.dumpVec3("quat_rotate(quat_z, input)", quatRotate(quatZ, vecA)) - lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotate(axisQuat, vecA)) - lines.dumpVec3("quat_z * input", quatZ * vecA) - - lines.heading("matrix quaternion roundtrip") - lines.dumpQuat("pure_rotation.quat", pureRotationM.quat()) - lines.dumpMat4("pure_rotation", pureRotationM) - lines.dumpMat4("pure_rotation.quat.mat4", pureRotationM.quat().mat4()) - lines.dumpMat4("transform.rotation_only", rotationOnlyM) - lines.dumpQuat("transform.rotation_only.quat", rotationOnlyM.quat()) - lines.dumpQuat("axis_mat.quat", axisMat.quat()) - lines.dumpMat4("axis_mat.quat.mat4", axisMat.quat().mat4()) - lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") - lines.dumpQuat("hard_decomp.quat_original", hardQuat) - lines.dumpQuat("hard_decomp.quat_from_mat", hardMat.quat()) - lines.dumpMat4("hard_decomp.mat4", hardMat) - lines.dumpMat4("hard_decomp.quat_from_mat.mat4", hardMat.quat().mat4()) - - lines.heading("perspective matrix") - lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - lines.dumpMat4("perspective", perspective(60'f32, 1.5'f32, 0.1'f32, 100'f32)) - - lines.heading("ortho matrix") - lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") - lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) - - lines.heading("lookAt matrix") - lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") - lines.dumpMat4("lookAt", lookAt(vec3(5'f32, 5'f32, 5'f32), vec3(0'f32, 0'f32, 0'f32), vec3(0'f32, 1'f32, 0'f32))) - - lines.heading("euler angle decomposition") - lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") - let pureRotAngles = pureRotationM.quat().toAngles() - let axisAngles = axisQuat.toAngles() - lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) - lines.dumpVec3("from_axis_angle.euler", axisAngles) - - lines.heading("basis directions") - lines.appendLine("notes: library-specific, N/A for libraries without canonical basis helpers") - lines.dumpVec3("canonical_right", basisRight) - lines.dumpVec3("canonical_up", basisUp) - lines.dumpVec3("canonical_forward", basisForward) - lines.dumpVec3("quat_z.right", quatRotate(quatZ, basisRight)) - lines.dumpVec3("quat_z.up", quatRotate(quatZ, basisUp)) - lines.dumpVec3("quat_z.forward", quatRotate(quatZ, basisForward)) - - lines.heading("matrix inverse") - lines.dumpMat4("transform.inverse", transformM.inverse()) - lines.dumpMat4("pure_rotation.inverse", pureRotationM.inverse()) - - lines.heading("cross product") - lines.appendLine("notes: cross(a, b) where a and b are vec3") - let crossA = vec3(1'f32, 0'f32, 0'f32) - let crossB = vec3(0'f32, 1'f32, 0'f32) - let crossC = normalize(vec3(1'f32, 2'f32, 3'f32)) - let crossD = normalize(vec3(-1'f32, 0.5'f32, 2'f32)) - lines.dumpVec3("cross(x_axis, y_axis)", cross(crossA, crossB)) - lines.dumpVec3("cross(y_axis, x_axis)", cross(crossB, crossA)) - lines.dumpVec3("cross(c, d)", cross(crossC, crossD)) - - lines.heading("slerp") - lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") - lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", slerp(quatX, quatZ, 0.25'f32)) - lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", slerp(quatX, quatZ, 0.5'f32)) - lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) - - lines.heading("fromTwoVectors") - lines.appendLine("notes: quaternion that rotates vector a to vector b") - let fromA = normalize(vec3(1'f32, 0'f32, 0'f32)) - let fromB = normalize(vec3(0'f32, 1'f32, 0'f32)) - let fromC = normalize(vec3(1'f32, 2'f32, -1'f32)) - let fromD = normalize(vec3(-1'f32, 0.5'f32, 2'f32)) - lines.dumpQuat("from_x_to_y", fromTwoVectors(fromA, fromB)) - lines.dumpQuat("from_c_to_d", fromTwoVectors(fromC, fromD)) - lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) - lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) - - lines.heading("quaternion inverse") - lines.dumpQuat("from_axis_angle.inverse", quatInverse(axisQuat)) - lines.dumpQuat("verify_q_mul_qinv", quatMultiply(axisQuat, quatInverse(axisQuat))) - - lines.heading("quaternion to axis-angle") - let (aaAxis1, aaAngle1) = toAxisAngle(axisQuat) - let (aaAxis2, aaAngle2) = toAxisAngle(quatXYZ) - lines.dumpVec3("from_axis_angle.axis", aaAxis1) - lines.dumpScalar("from_axis_angle.angle", aaAngle1) - lines.dumpVec3("quat_xyz.axis", aaAxis2) - lines.dumpScalar("quat_xyz.angle", aaAngle2) - - lines.heading("element access [row,col]") - lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") - lines.dumpScalar("transform[0,0]", transformM[0, 0]) - lines.dumpScalar("transform[0,1]", transformM[0, 1]) - lines.dumpScalar("transform[0,2]", transformM[0, 2]) - lines.dumpScalar("transform[0,3]", transformM[0, 3]) - lines.dumpScalar("transform[1,0]", transformM[1, 0]) - lines.dumpScalar("transform[1,3]", transformM[1, 3]) - lines.dumpScalar("transform[2,0]", transformM[2, 0]) - lines.dumpScalar("transform[2,3]", transformM[2, 3]) - lines.dumpScalar("transform[3,3]", transformM[3, 3]) + + lines.dumpMat4("matrix_a * matrix_b", matA * matB) + lines.dumpMat4("matrix_b * matrix_a", matB * matA) + # lines.dumpMat4("matrix_b", matB) + # lines.dumpMat4("scale", scaleM) + # lines.dumpMat4("translate", translateM) + # lines.dumpMat4("rotate_x", rotateXM) + # lines.dumpMat4("rotate_y", rotateYM) + # lines.dumpMat4("rotate_z", rotateZM) + # lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) + # lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) + + # lines.heading("matrix multiply") + # lines.dumpMat4("lhs", matA) + # lines.dumpMat4("rhs", matB) + # lines.dumpMat4("lhs * rhs", matA * matB) + # lines.dumpMat4("rhs * lhs", matB * matA) + + # lines.heading("matrix vector multiply") + # lines.dumpVec3("vec3_input", vecA) + # lines.dumpVec4("vec4_input", vecB) + # lines.dumpVec3("transform * vec3", transformM * vecA) + # lines.dumpVec4("transform * vec4", transformM * vecB) + # lines.dumpVec3("rotate_z * vec3", rotateZM * vecA) + # lines.dumpVec3("translate * vec3", translateM * vecA) + + # lines.heading("quaternion constructors") + # lines.dumpQuat("quat_identity", quat()) + # lines.dumpQuat("quat_rotate_x", quatX) + # lines.dumpQuat("quat_rotate_y", quatY) + # lines.dumpQuat("quat_rotate_z", quatZ) + # lines.dumpVec3("axis_normalized", axis) + # lines.dumpScalar("axis_angle_radians", axisAngle) + # lines.dumpQuat("from_axis_angle", axisQuat) + # lines.dumpMat4("from_axis_angle.mat4", axisMat) + + # lines.heading("quaternion multiply") + # lines.dumpQuat("quat_x", quatX) + # lines.dumpQuat("quat_y", quatY) + # lines.dumpQuat("quat_z", quatZ) + # lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) + # lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) + # lines.dumpMat4("quat_xy.mat4", quatXY.mat4()) + # lines.dumpMat4("quat_xyz.mat4", quatXYZ.mat4()) + + # lines.heading("quaternion vector rotate") + # lines.dumpVec3("input", vecA) + # lines.dumpVec3("quat_rotate(quat_x, input)", quatRotate(quatX, vecA)) + # lines.dumpVec3("quat_rotate(quat_y, input)", quatRotate(quatY, vecA)) + # lines.dumpVec3("quat_rotate(quat_z, input)", quatRotate(quatZ, vecA)) + # lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotate(axisQuat, vecA)) + # lines.dumpVec3("quat_z * input", quatZ * vecA) + + # lines.heading("matrix quaternion roundtrip") + # lines.dumpQuat("pure_rotation.quat", pureRotationM.quat()) + # lines.dumpMat4("pure_rotation", pureRotationM) + # lines.dumpMat4("pure_rotation.quat.mat4", pureRotationM.quat().mat4()) + # lines.dumpMat4("transform.rotation_only", rotationOnlyM) + # lines.dumpQuat("transform.rotation_only.quat", rotationOnlyM.quat()) + # lines.dumpQuat("axis_mat.quat", axisMat.quat()) + # lines.dumpMat4("axis_mat.quat.mat4", axisMat.quat().mat4()) + # lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") + # lines.dumpQuat("hard_decomp.quat_original", hardQuat) + # lines.dumpQuat("hard_decomp.quat_from_mat", hardMat.quat()) + # lines.dumpMat4("hard_decomp.mat4", hardMat) + # lines.dumpMat4("hard_decomp.quat_from_mat.mat4", hardMat.quat().mat4()) + + # lines.heading("perspective matrix") + # lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + # lines.dumpMat4("perspective", perspective(60'f32, 1.5'f32, 0.1'f32, 100'f32)) + + # lines.heading("ortho matrix") + # lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") + # lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) + + # lines.heading("lookAt matrix") + # lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") + # lines.dumpMat4("lookAt", lookAt(vec3(5'f32, 5'f32, 5'f32), vec3(0'f32, 0'f32, 0'f32), vec3(0'f32, 1'f32, 0'f32))) + + # lines.heading("euler angle decomposition") + # lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") + # let pureRotAngles = pureRotationM.quat().toAngles() + # let axisAngles = axisQuat.toAngles() + # lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) + # lines.dumpVec3("from_axis_angle.euler", axisAngles) + + # lines.heading("basis directions") + # lines.appendLine("notes: library-specific, N/A for libraries without canonical basis helpers") + # lines.dumpVec3("canonical_right", basisRight) + # lines.dumpVec3("canonical_up", basisUp) + # lines.dumpVec3("canonical_forward", basisForward) + # lines.dumpVec3("quat_z.right", quatRotate(quatZ, basisRight)) + # lines.dumpVec3("quat_z.up", quatRotate(quatZ, basisUp)) + # lines.dumpVec3("quat_z.forward", quatRotate(quatZ, basisForward)) + + # lines.heading("matrix inverse") + # lines.dumpMat4("transform.inverse", transformM.inverse()) + # lines.dumpMat4("pure_rotation.inverse", pureRotationM.inverse()) + + # lines.heading("cross product") + # lines.appendLine("notes: cross(a, b) where a and b are vec3") + # let crossA = vec3(1'f32, 0'f32, 0'f32) + # let crossB = vec3(0'f32, 1'f32, 0'f32) + # let crossC = normalize(vec3(1'f32, 2'f32, 3'f32)) + # let crossD = normalize(vec3(-1'f32, 0.5'f32, 2'f32)) + # lines.dumpVec3("cross(x_axis, y_axis)", cross(crossA, crossB)) + # lines.dumpVec3("cross(y_axis, x_axis)", cross(crossB, crossA)) + # lines.dumpVec3("cross(c, d)", cross(crossC, crossD)) + + # lines.heading("slerp") + # lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") + # lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", slerp(quatX, quatZ, 0.25'f32)) + # lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", slerp(quatX, quatZ, 0.5'f32)) + # lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) + + # lines.heading("fromTwoVectors") + # lines.appendLine("notes: quaternion that rotates vector a to vector b") + # let fromA = normalize(vec3(1'f32, 0'f32, 0'f32)) + # let fromB = normalize(vec3(0'f32, 1'f32, 0'f32)) + # let fromC = normalize(vec3(1'f32, 2'f32, -1'f32)) + # let fromD = normalize(vec3(-1'f32, 0.5'f32, 2'f32)) + # lines.dumpQuat("from_x_to_y", fromTwoVectors(fromA, fromB)) + # lines.dumpQuat("from_c_to_d", fromTwoVectors(fromC, fromD)) + # lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) + # lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) + + # lines.heading("quaternion inverse") + # lines.dumpQuat("from_axis_angle.inverse", quatInverse(axisQuat)) + # lines.dumpQuat("verify_q_mul_qinv", quatMultiply(axisQuat, quatInverse(axisQuat))) + + # lines.heading("quaternion to axis-angle") + # let (aaAxis1, aaAngle1) = toAxisAngle(axisQuat) + # let (aaAxis2, aaAngle2) = toAxisAngle(quatXYZ) + # lines.dumpVec3("from_axis_angle.axis", aaAxis1) + # lines.dumpScalar("from_axis_angle.angle", aaAngle1) + # lines.dumpVec3("quat_xyz.axis", aaAxis2) + # lines.dumpScalar("quat_xyz.angle", aaAngle2) + + # lines.heading("element access [row,col]") + # lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") + # lines.dumpScalar("transform[0,0]", transformM[0, 0]) + # lines.dumpScalar("transform[0,1]", transformM[0, 1]) + # lines.dumpScalar("transform[0,2]", transformM[0, 2]) + # lines.dumpScalar("transform[0,3]", transformM[0, 3]) + # lines.dumpScalar("transform[1,0]", transformM[1, 0]) + # lines.dumpScalar("transform[1,3]", transformM[1, 3]) + # lines.dumpScalar("transform[2,0]", transformM[2, 0]) + # lines.dumpScalar("transform[2,3]", transformM[2, 3]) + # lines.dumpScalar("transform[3,3]", transformM[3, 3]) writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/conformance/dump_vmath.txt b/conformance/dump_vmath.txt index 346e180..33b4545 100644 --- a/conformance/dump_vmath.txt +++ b/conformance/dump_vmath.txt @@ -2,13 +2,6 @@ notes: matrices are printed in raw in-memory order, four scalars per line == matrix constructors and composition == -identity: -[ - +001.000 +000.000 +000.000 +000.000 - +000.000 +001.000 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +000.000 +000.000 +000.000 +001.000 -] matrix_a: [ +001.000 +005.000 +009.000 +013.000 @@ -18,291 +11,22 @@ matrix_a: ] matrix_b: [ - +000.500 +001.500 -003.000 +000.000 - -001.000 +000.750 +004.000 +001.000 - +002.000 -000.500 +001.250 -001.500 - +000.250 +002.000 -002.500 +003.000 -] -scale: -[ - +002.000 +000.000 +000.000 +000.000 - +000.000 +003.000 +000.000 +000.000 - +000.000 +000.000 +004.000 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -translate: -[ - +001.000 +000.000 +000.000 +000.000 - +000.000 +001.000 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +010.000 +020.000 +030.000 +001.000 -] -rotate_x: -[ - +001.000 +000.000 +000.000 +000.000 - +000.000 +000.799 +000.602 +000.000 - +000.000 -000.602 +000.799 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -rotate_y: -[ - +000.921 +000.000 +000.391 +000.000 - +000.000 +001.000 +000.000 +000.000 - -000.391 +000.000 +000.921 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -rotate_z: -[ - +000.326 +000.946 +000.000 +000.000 - -000.946 +000.326 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -pure_rotation = rotate_z * rotate_y * rotate_x: -[ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -transform = translate * rotate_z * rotate_y * rotate_x * scale: -[ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 - +010.000 +020.000 +030.000 +001.000 -] - -== matrix multiply == -lhs: -[ - +001.000 +005.000 +009.000 +013.000 - +002.000 +006.000 +010.000 +014.000 - +003.000 +007.000 +011.000 +015.000 - +004.000 +008.000 +012.000 +016.000 -] -rhs: -[ - +000.500 +001.500 -003.000 +000.000 - -001.000 +000.750 +004.000 +001.000 - +002.000 -000.500 +001.250 -001.500 - +000.250 +002.000 -002.500 +003.000 -] -lhs * rhs: -[ - -005.500 -009.500 -013.500 -017.500 - +016.500 +035.500 +054.500 +073.500 - -001.250 +003.750 +008.750 +013.750 - +008.750 +019.750 +030.750 +041.750 -] -rhs * lhs: -[ - +016.750 +026.750 -004.250 +030.500 - +018.500 +030.500 -004.500 +033.000 - +020.250 +034.250 -004.750 +035.500 - +022.000 +038.000 -005.000 +038.000 -] - -== matrix vector multiply == -vec3_input: <+001.250, -002.500, +003.750> -vec4_input: <+001.250, -002.500, +003.750, +001.000> -transform * vec3: <+023.998, +014.529, +037.849> -transform * vec4: <+023.998, +014.529, +037.849, +001.000> -rotate_z * vec3: <+002.771, +000.368, +003.750> -translate * vec3: <+011.250, +017.500, +033.750> - -== quaternion constructors == -quat_identity: <+000.000, +000.000, +000.000, +001.000> -quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> -quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> -quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> -axis_normalized: <+000.267, +000.535, -000.802> -axis_angle_radians: +000.838 -from_axis_angle: <+000.109, +000.217, -000.326, +000.914> -from_axis_angle.mat4: -[ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== quaternion multiply == -quat_x: <+000.317, +000.000, +000.000, +000.948> -quat_y: <+000.000, -000.199, +000.000, +000.980> -quat_z: <+000.000, +000.000, +000.581, +000.814> -quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> -quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> -quat_xy.mat4: -[ - +000.921 -000.235 +000.312 +000.000 - +000.000 +000.799 +000.602 +000.000 - -000.391 -000.554 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -quat_xyz.mat4: -[ - +000.300 +000.679 +000.671 +000.000 - -000.870 +000.482 -000.099 +000.000 - -000.391 -000.554 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== quaternion vector rotate == -input: <+001.250, -002.500, +003.750> -quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> -quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> -quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> -quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> -quat_z * input: <+002.771, +000.368, +003.750> - -== matrix quaternion roundtrip == -pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> -pure_rotation: -[ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -pure_rotation.quat.mat4: -[ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 + -010.000 +050.000 +090.000 +130.000 + -020.000 +060.000 +100.000 +140.000 + -030.000 +070.000 +110.000 +150.000 + -040.000 +080.000 +120.000 +160.000 ] -transform.rotation_only: +matrix_a * matrix_b: [ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 - +000.000 +000.000 +000.000 +001.000 + -900.000 +2020.000 +3140.000 +4260.000 + -1000.000 +2280.000 +3560.000 +4840.000 + -1100.000 +2540.000 +3980.000 +5420.000 + -1200.000 +2800.000 +4400.000 +6000.000 ] -transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> -axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> -axis_mat.quat.mat4: +matrix_b * matrix_a: [ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 - +000.000 +000.000 +000.000 +001.000 + +880.000 +1920.000 +2960.000 +4000.000 + +960.000 +2080.000 +3200.000 +4320.000 + +1040.000 +2240.000 +3440.000 +4640.000 + +1120.000 +2400.000 +3680.000 +4960.000 ] -hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero -hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> -hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> -hard_decomp.mat4: -[ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 - +000.000 +000.000 +000.000 +001.000 -] -hard_decomp.quat_from_mat.mat4: -[ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== perspective matrix == -notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 -perspective: -[ - +001.155 +000.000 +000.000 +000.000 - +000.000 +001.732 +000.000 +000.000 - +000.000 +000.000 -001.002 -001.000 - +000.000 +000.000 -000.200 +000.000 -] - -== ortho matrix == -notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 -ortho: -[ - +000.100 +000.000 +000.000 +000.000 - +000.000 +000.133 +000.000 +000.000 - +000.000 +000.000 -000.020 +000.000 - +000.000 +000.000 -001.002 +001.000 -] - -== lookAt matrix == -notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) -lookAt: -[ - +000.707 -000.408 +000.577 +000.000 - +000.000 +000.816 +000.577 +000.000 - -000.707 -000.408 +000.577 +000.000 - +000.000 +000.000 -008.660 +001.000 -] - -== euler angle decomposition == -notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians -pure_rotation.quat.euler: <+000.646, -000.401, +001.239> -from_axis_angle.euler: <+000.064, +000.487, -000.670> - -== basis directions == -notes: library-specific, N/A for libraries without canonical basis helpers -canonical_right: <+001.000, +000.000, +000.000> -canonical_up: <+000.000, +001.000, +000.000> -canonical_forward: <+000.000, +000.000, +001.000> -quat_z.right: <+000.326, +000.946, +000.000> -quat_z.up: <-000.946, +000.326, +000.000> -quat_z.forward: <+000.000, +000.000, +001.000> - -== matrix inverse == -transform.inverse: -[ - +000.150 -000.277 +000.117 +000.000 - +000.435 +000.013 -000.123 +000.000 - +000.195 +000.185 +000.184 +000.000 - -016.063 -003.019 -004.227 +001.000 -] -pure_rotation.inverse: -[ - +000.300 -000.832 +000.467 +000.000 - +000.870 +000.038 -000.491 +000.000 - +000.391 +000.554 +000.735 +000.000 - +000.000 +000.000 +000.000 +001.000 -] - -== cross product == -notes: cross(a, b) where a and b are vec3 -cross(x_axis, y_axis): <+000.000, +000.000, +001.000> -cross(y_axis, x_axis): <+000.000, +000.000, -001.000> -cross(c, d): <+000.292, -000.583, +000.292> - -== slerp == -notes: slerp(a, b, t) between quat_x and quat_z -slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> -slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> -slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> - -== fromTwoVectors == -notes: quaternion that rotates vector a to vector b -from_x_to_y: <+000.000, +000.000, +000.707, +000.707> -from_c_to_d: <+000.707, -000.157, +000.393, +000.567> -verify_x_to_y: <+000.000, +001.000, +000.000> -verify_c_to_d: <-000.436, +000.218, +000.873> - -== quaternion inverse == -from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> -verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> - -== quaternion to axis-angle == -from_axis_angle.axis: <+000.267, +000.535, -000.802> -from_axis_angle.angle: +000.838 -quat_xyz.axis: <+000.235, -000.549, +000.802> -quat_xyz.angle: +001.309 - -== element access [row,col] == -notes: [row,col] in math convention, element (i,j) = row i, col j -transform[0,0]: +000.599 -transform[0,1]: -002.495 -transform[0,2]: +001.870 -transform[0,3]: +010.000 -transform[1,0]: +001.741 -transform[1,3]: +020.000 -transform[2,0]: +000.781 -transform[2,3]: +030.000 -transform[3,3]: +001.000 diff --git a/src/vmath.nim b/src/vmath.nim index 601cfbb..0647ffd 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -303,7 +303,7 @@ elif true or defined(vmathObjArrayBased): GMat3*[T] {.bycopy.} = object arr: array[9, T] GMat4*[T] {.bycopy.} = object - arr: array[16, T] + arr*: array[16, T] proc gmat2*[T]( m00, m01, @@ -913,7 +913,7 @@ proc matToString[T](a: T, n: int): string = for x in 0 ..< n: result.add " " for y in 0 ..< n: - result.add $a[y, x] & ", " + result.add $a[x, y] & ", " result.setLen(result.len - 1) result.add "\n" result.setLen(result.len - 2) From 01f2c6f9c3a3f8d2796abfec073ead9acdfd37d0 Mon Sep 17 00:00:00 2001 From: treeform Date: Tue, 14 Apr 2026 16:25:39 -0700 Subject: [PATCH 14/35] make dump scripts same and use raw memory --- conformance/dump_glm.nim | 397 +++++++++++++++++----------------- conformance/dump_glm.txt | 268 ++++++++++++++++++++++- conformance/dump_jolt.nim | 393 +++++++++++++++++----------------- conformance/dump_jolt.txt | 262 ++++++++++++++++++++++- conformance/dump_vmath.nim | 428 ++++++++++++++++++------------------- conformance/dump_vmath.txt | 289 ++++++++++++++++++++++++- src/vmath.nim | 12 +- 7 files changed, 1412 insertions(+), 637 deletions(-) diff --git a/conformance/dump_glm.nim b/conformance/dump_glm.nim index a930ea1..6da2131 100644 --- a/conformance/dump_glm.nim +++ b/conformance/dump_glm.nim @@ -50,15 +50,13 @@ proc dumpQuat(lines: var seq[string], label: string, value: Quatf) = proc dumpMat4(lines: var seq[string], label: string, value: Mat4f) = lines.appendLine(label & ":") + let d = cast[array[16, float32]](value) lines.appendLine("[") - lines.appendLine(" " & fmt(value.arr[0][0]) & " " & fmt(value.arr[1][0]) & " " & fmt(value.arr[2][0]) & " " & fmt(value.arr[3][0]) ) - lines.appendLine(" " & fmt(value.arr[0][1]) & " " & fmt(value.arr[1][1]) & " " & fmt(value.arr[2][1]) & " " & fmt(value.arr[3][1]) ) - lines.appendLine(" " & fmt(value.arr[0][2]) & " " & fmt(value.arr[1][2]) & " " & fmt(value.arr[2][2]) & " " & fmt(value.arr[3][2]) ) - lines.appendLine(" " & fmt(value.arr[0][3]) & " " & fmt(value.arr[1][3]) & " " & fmt(value.arr[2][3]) & " " & fmt(value.arr[3][3]) ) + lines.appendLine(" " & fmt(d[0]) & " " & fmt(d[4]) & " " & fmt(d[8]) & " " & fmt(d[12]) ) + lines.appendLine(" " & fmt(d[1]) & " " & fmt(d[5]) & " " & fmt(d[9]) & " " & fmt(d[13]) ) + lines.appendLine(" " & fmt(d[2]) & " " & fmt(d[6]) & " " & fmt(d[10]) & " " & fmt(d[14]) ) + lines.appendLine(" " & fmt(d[3]) & " " & fmt(d[7]) & " " & fmt(d[11]) & " " & fmt(d[15]) ) lines.appendLine("]") - # lines.appendLine("direct " & $value) - # lines.appendLine("array " & $value.arr) - proc heading(lines: var seq[string], title: string) = if lines.len > 0: @@ -94,205 +92,206 @@ proc main() = angleB = -23'f32 * PI.float32 / 180'f32 angleC = 71'f32 * PI.float32 / 180'f32 - matA = mat4f( - vec4f(1.0f, 2.0f, 3.0f, 4.0f), - vec4f(5.0f, 6.0f, 7.0f, 8.0f), - vec4f(9.0f, 10.0f, 11.0f, 12.0f), - vec4f(13.0f, 14.0f, 15.0f, 16.0f) - ) - matB = mat4f( - vec4f(-10.0f, -20.0f, -30.0f, -40.0f), - vec4f(50.0f, 60.0f, 70.0f, 80.0f), - vec4f(90.0f, 100.0f, 110.0f, 120.0f), - vec4f(130.0f, 140.0f, 150.0f, 160.0f) - ) - # vecA = vec3f(1.25, -2.5, 3.75) - # vecB = vec4f(1.25, -2.5, 3.75, 1.0) - - # scaleM = mat4f(1).scale(2'f32, 3'f32, 4'f32) - # translateM = mat4f(1).translate(10'f32, 20'f32, 30'f32) - # rotateXM = mat4f(1).rotateX(angleA) - # rotateYM = mat4f(1).rotateY(angleB) - # rotateZM = mat4f(1).rotateZ(angleC) - # pureRotationM = rotateZM * rotateYM * rotateXM - - # axis = normalize(vec3f(1.0, 2.0, -3.0)) - # axisAngle = 48'f32 * PI.float32 / 180'f32 - # axisQuat = quatf(axis, axisAngle) - # axisMat = axisQuat.mat4() - # transformM = translateM * rotateZM * rotateYM * rotateXM * scaleM - - # quatX = quatf(vec3f(1'f32, 0'f32, 0'f32), angleA) - # quatY = quatf(vec3f(0'f32, 1'f32, 0'f32), angleB) - # quatZ = quatf(vec3f(0'f32, 0'f32, 1'f32), angleC) - # quatXY = quatX * quatY - # quatXYZ = quatXY * quatZ - - # hardAxis = normalize(vec3f(1.0, -2.0, 3.0)) - # hardAngle = 170'f32 * PI.float32 / 180'f32 - # hardQuat = quatf(hardAxis, hardAngle) - # hardMat = hardQuat.mat4() - - # rotationOnlyM = rotationOnlyCopy(transformM) + matA = cast[Mat4f]([ + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f + ]) + matB = cast[Mat4f]([ + -10.0f, -20.0f, -30.0f, -40.0f, + 50.0f, 60.0f, 70.0f, 80.0f, + 90.0f, 100.0f, 110.0f, 120.0f, + 130.0f, 140.0f, 150.0f, 160.0f + ]) + vecA = vec3f(1.25, -2.5, 3.75) + vecB = vec4f(1.25, -2.5, 3.75, 1.0) + + scaleM = mat4f(1).scale(2'f32, 3'f32, 4'f32) + translateM = mat4f(1).translate(10'f32, 20'f32, 30'f32) + rotateXM = mat4f(1).rotateX(angleA) + rotateYM = mat4f(1).rotateY(angleB) + rotateZM = mat4f(1).rotateZ(angleC) + pureRotationM = rotateZM * rotateYM * rotateXM + + axis = normalize(vec3f(1.0, 2.0, -3.0)) + axisAngle = 48'f32 * PI.float32 / 180'f32 + axisQuat = quatf(axis, axisAngle) + axisMat = axisQuat.mat4() + transformM = translateM * rotateZM * rotateYM * rotateXM * scaleM + + quatX = quatf(vec3f(1'f32, 0'f32, 0'f32), angleA) + quatY = quatf(vec3f(0'f32, 1'f32, 0'f32), angleB) + quatZ = quatf(vec3f(0'f32, 0'f32, 1'f32), angleC) + quatXY = quatX * quatY + quatXYZ = quatXY * quatZ + + hardAxis = normalize(vec3f(1.0, -2.0, 3.0)) + hardAngle = 170'f32 * PI.float32 / 180'f32 + hardQuat = quatf(hardAxis, hardAngle) + hardMat = hardQuat.mat4() + + rotationOnlyM = rotationOnlyCopy(transformM) lines.heading("dump") - lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") + lines.appendLine("notes: matrices are printed in common column-major order") - lines.heading("matrix constructors and composition") - #lines.dumpMat4("identity", mat4f()) + lines.heading("matrix basics") + lines.dumpMat4("identity", mat4f()) lines.dumpMat4("matrix_a", matA) lines.dumpMat4("matrix_b", matB) + + lines.heading("matrix multiply") lines.dumpMat4("matrix_a * matrix_b", matA * matB) lines.dumpMat4("matrix_b * matrix_a", matB * matA) - # lines.dumpMat4("matrix_b", matB) - # lines.dumpMat4("scale", scaleM) - # lines.dumpMat4("translate", translateM) - # lines.dumpMat4("rotate_x", rotateXM) - # lines.dumpMat4("rotate_y", rotateYM) - # lines.dumpMat4("rotate_z", rotateZM) - # lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) - # lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) - - # lines.heading("matrix multiply") - # lines.dumpMat4("lhs", matA) - # lines.dumpMat4("rhs", matB) - # lines.dumpMat4("lhs * rhs", matA * matB) - # lines.dumpMat4("rhs * lhs", matB * matA) - - # lines.heading("matrix vector multiply") - # lines.dumpVec3("vec3_input", vecA) - # lines.dumpVec4("vec4_input", vecB) - # lines.dumpVec3("transform * vec3", transformVec3ByMat4(transformM, vecA)) - # lines.dumpVec4("transform * vec4", transformVec4ByMat4(transformM, vecB)) - # lines.dumpVec3("rotate_z * vec3", transformVec3ByMat4(rotateZM, vecA)) - # lines.dumpVec3("translate * vec3", transformVec3ByMat4(translateM, vecA)) - - # lines.heading("quaternion constructors") - # lines.dumpQuat("quat_identity", quatf()) - # lines.dumpQuat("quat_rotate_x", quatX) - # lines.dumpQuat("quat_rotate_y", quatY) - # lines.dumpQuat("quat_rotate_z", quatZ) - # lines.dumpVec3("axis_normalized", axis) - # lines.dumpScalar("axis_angle_radians", axisAngle) - # lines.dumpQuat("from_axis_angle", axisQuat) - # lines.dumpMat4("from_axis_angle.mat4", axisMat) - - # lines.heading("quaternion multiply") - # lines.dumpQuat("quat_x", quatX) - # lines.dumpQuat("quat_y", quatY) - # lines.dumpQuat("quat_z", quatZ) - # lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) - # lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) - # lines.dumpMat4("quat_xy.mat4", quatXY.mat4()) - # lines.dumpMat4("quat_xyz.mat4", quatXYZ.mat4()) - - # lines.heading("quaternion vector rotate") - # lines.dumpVec3("input", vecA) - # lines.dumpVec3("quat_rotate(quat_x, input)", quatX * vecA) - # lines.dumpVec3("quat_rotate(quat_y, input)", quatY * vecA) - # lines.dumpVec3("quat_rotate(quat_z, input)", quatZ * vecA) - # lines.dumpVec3("quat_rotate(from_axis_angle, input)", axisQuat * vecA) - # lines.dumpVec3("quat_z * input", quatZ * vecA) - - # lines.heading("matrix quaternion roundtrip") - # let pureRotationQuat = quat(pureRotationM) - # lines.dumpQuat("pure_rotation.quat", pureRotationQuat) - # lines.dumpMat4("pure_rotation", pureRotationM) - # lines.dumpMat4("pure_rotation.quat.mat4", pureRotationQuat.mat4()) - # lines.dumpMat4("transform.rotation_only", rotationOnlyM) - # lines.dumpQuat("transform.rotation_only.quat", quat(rotationOnlyM)) - # let axisMatQuat = quat(axisMat) - # lines.dumpQuat("axis_mat.quat", axisMatQuat) - # lines.dumpMat4("axis_mat.quat.mat4", axisMatQuat.mat4()) - # lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") - # lines.dumpQuat("hard_decomp.quat_original", hardQuat) - # lines.dumpQuat("hard_decomp.quat_from_mat", quat(hardMat)) - # lines.dumpMat4("hard_decomp.mat4", hardMat) - # lines.dumpMat4("hard_decomp.quat_from_mat.mat4", quat(hardMat).mat4()) - - # lines.heading("perspective matrix") - # lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - # let fovyRad = 60'f32 * PI.float32 / 180'f32 - # lines.dumpMat4("perspective", perspectiveRH(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) - - # lines.heading("ortho matrix") - # lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") - # lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) - - # lines.heading("lookAt matrix") - # lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") - # lines.dumpMat4("lookAt", lookAtRH(vec3f(5, 5, 5), vec3f(0, 0, 0), vec3f(0, 1, 0))) - - # lines.heading("euler angle decomposition") - # lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") - # let pureRotAngles = eulerAngles(quat(pureRotationM)) - # let axisAngles = eulerAngles(axisQuat) - # lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) - # lines.dumpVec3("from_axis_angle.euler", axisAngles) - - # lines.heading("matrix inverse") - # lines.dumpMat4("transform.inverse", inverse(transformM)) - # lines.dumpMat4("pure_rotation.inverse", inverse(pureRotationM)) - - # lines.heading("cross product") - # lines.appendLine("notes: cross(a, b) where a and b are vec3") - # let crossA = vec3f(1, 0, 0) - # let crossB = vec3f(0, 1, 0) - # let crossC = normalize(vec3f(1, 2, 3)) - # let crossD = normalize(vec3f(-1, 0.5, 2)) - # lines.dumpVec3("cross(x_axis, y_axis)", cross(crossA, crossB)) - # lines.dumpVec3("cross(y_axis, x_axis)", cross(crossB, crossA)) - # lines.dumpVec3("cross(c, d)", cross(crossC, crossD)) - - # lines.heading("slerp") - # lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") - # lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", slerp(quatX, quatZ, 0.25'f32)) - # lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", slerp(quatX, quatZ, 0.5'f32)) - # lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) - - # lines.heading("fromTwoVectors") - # lines.appendLine("notes: quaternion that rotates vector a to vector b") - # lines.appendLine("notes: GLM has no native fromTwoVectors, implemented inline") - # proc fromTwoVectors(a, b: Vec3f): Quatf = - # let d = dot(a, b) - # let c = cross(a, b) - # let w = sqrt(dot(a, a) * dot(b, b)) + d - # result = quatf(c.x, c.y, c.z, w) - # result = normalize(result) - # let fromA = vec3f(1, 0, 0) - # let fromB = vec3f(0, 1, 0) - # let fromC = normalize(vec3f(1, 2, -1)) - # let fromD = normalize(vec3f(-1, 0.5, 2)) - # lines.dumpQuat("from_x_to_y", fromTwoVectors(fromA, fromB)) - # lines.dumpQuat("from_c_to_d", fromTwoVectors(fromC, fromD)) - # lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) - # lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) - - # lines.heading("quaternion inverse") - # lines.dumpQuat("from_axis_angle.inverse", inverse(axisQuat)) - # lines.dumpQuat("verify_q_mul_qinv", axisQuat * inverse(axisQuat)) - - # lines.heading("quaternion to axis-angle") - # lines.dumpVec3("from_axis_angle.axis", axis(axisQuat)) - # lines.dumpScalar("from_axis_angle.angle", angle(axisQuat)) - # lines.dumpVec3("quat_xyz.axis", axis(quatXYZ)) - # lines.dumpScalar("quat_xyz.angle", angle(quatXYZ)) - - # lines.heading("basis directions") - # lines.appendLine("N/A") - - # lines.heading("element access [row,col]") - # lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") - # lines.appendLine("notes: GLM uses [col,row], so element (r,c) = value[c,r]") - # lines.dumpScalar("transform[0,0]", transformM[0, 0]) - # lines.dumpScalar("transform[0,1]", transformM[1, 0]) - # lines.dumpScalar("transform[0,2]", transformM[2, 0]) - # lines.dumpScalar("transform[0,3]", transformM[3, 0]) - # lines.dumpScalar("transform[1,0]", transformM[0, 1]) - # lines.dumpScalar("transform[1,3]", transformM[3, 1]) - # lines.dumpScalar("transform[2,0]", transformM[0, 2]) - # lines.dumpScalar("transform[2,3]", transformM[3, 2]) - # lines.dumpScalar("transform[3,3]", transformM[3, 3]) + + lines.heading("element access [row, col]") + lines.dumpScalar("transform[0, 0]", matA[0, 0]) + lines.dumpScalar("transform[0, 1]", matA[0, 1]) + lines.dumpScalar("transform[0, 2]", matA[0, 2]) + lines.dumpScalar("transform[0, 3]", matA[0, 3]) + lines.dumpScalar("transform[1, 0]", matA[1, 0]) + lines.dumpScalar("transform[1, 1]", matA[1, 1]) + lines.dumpScalar("transform[1, 2]", matA[1, 2]) + lines.dumpScalar("transform[1, 3]", matA[1, 3]) + lines.dumpScalar("transform[2, 0]", matA[2, 0]) + lines.dumpScalar("transform[2, 1]", matA[2, 1]) + lines.dumpScalar("transform[2, 2]", matA[2, 2]) + lines.dumpScalar("transform[2, 3]", matA[2, 3]) + lines.dumpScalar("transform[3, 0]", matA[3, 0]) + lines.dumpScalar("transform[3, 1]", matA[3, 1]) + lines.dumpScalar("transform[3, 2]", matA[3, 2]) + lines.dumpScalar("transform[3, 3]", matA[3, 3]) + + lines.heading("matrix constructors and composition") + lines.dumpMat4("scale", scaleM) + lines.dumpMat4("translate", translateM) + lines.dumpMat4("rotate_x", rotateXM) + lines.dumpMat4("rotate_y", rotateYM) + lines.dumpMat4("rotate_z", rotateZM) + lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) + lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) + + lines.heading("matrix vector multiply") + lines.dumpVec3("vec3_input", vecA) + lines.dumpVec4("vec4_input", vecB) + lines.dumpVec3("transform * vec3", transformVec3ByMat4(transformM, vecA)) + lines.dumpVec4("transform * vec4", transformVec4ByMat4(transformM, vecB)) + lines.dumpVec3("rotate_z * vec3", transformVec3ByMat4(rotateZM, vecA)) + lines.dumpVec3("translate * vec3", transformVec3ByMat4(translateM, vecA)) + + lines.heading("quaternion constructors") + lines.dumpQuat("quat_identity", quatf()) + lines.dumpQuat("quat_rotate_x", quatX) + lines.dumpQuat("quat_rotate_y", quatY) + lines.dumpQuat("quat_rotate_z", quatZ) + lines.dumpVec3("axis_normalized", axis) + lines.dumpScalar("axis_angle_radians", axisAngle) + lines.dumpQuat("from_axis_angle", axisQuat) + lines.dumpMat4("from_axis_angle.mat4", axisMat) + + lines.heading("quaternion multiply") + lines.dumpQuat("quat_x", quatX) + lines.dumpQuat("quat_y", quatY) + lines.dumpQuat("quat_z", quatZ) + lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) + lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) + lines.dumpMat4("quat_xy.mat4", quatXY.mat4()) + lines.dumpMat4("quat_xyz.mat4", quatXYZ.mat4()) + + lines.heading("quaternion vector rotate") + lines.dumpVec3("input", vecA) + lines.dumpVec3("quat_rotate(quat_x, input)", quatX * vecA) + lines.dumpVec3("quat_rotate(quat_y, input)", quatY * vecA) + lines.dumpVec3("quat_rotate(quat_z, input)", quatZ * vecA) + lines.dumpVec3("quat_rotate(from_axis_angle, input)", axisQuat * vecA) + lines.dumpVec3("quat_z * input", quatZ * vecA) + + lines.heading("matrix quaternion roundtrip") + let pureRotationQuat = quat(pureRotationM) + lines.dumpQuat("pure_rotation.quat", pureRotationQuat) + lines.dumpMat4("pure_rotation", pureRotationM) + lines.dumpMat4("pure_rotation.quat.mat4", pureRotationQuat.mat4()) + lines.dumpMat4("transform.rotation_only", rotationOnlyM) + lines.dumpQuat("transform.rotation_only.quat", quat(rotationOnlyM)) + let axisMatQuat = quat(axisMat) + lines.dumpQuat("axis_mat.quat", axisMatQuat) + lines.dumpMat4("axis_mat.quat.mat4", axisMatQuat.mat4()) + lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") + lines.dumpQuat("hard_decomp.quat_original", hardQuat) + lines.dumpQuat("hard_decomp.quat_from_mat", quat(hardMat)) + lines.dumpMat4("hard_decomp.mat4", hardMat) + lines.dumpMat4("hard_decomp.quat_from_mat.mat4", quat(hardMat).mat4()) + + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + let fovyRad = 60'f32 * PI.float32 / 180'f32 + lines.dumpMat4("perspective", perspectiveRH(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) + + lines.heading("ortho matrix") + lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") + lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) + + lines.heading("lookAt matrix") + lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") + lines.dumpMat4("lookAt", lookAtRH(vec3f(5, 5, 5), vec3f(0, 0, 0), vec3f(0, 1, 0))) + + lines.heading("euler angle decomposition") + lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") + let pureRotAngles = eulerAngles(quat(pureRotationM)) + let axisAngles = eulerAngles(axisQuat) + lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) + lines.dumpVec3("from_axis_angle.euler", axisAngles) + + lines.heading("matrix inverse") + lines.dumpMat4("transform.inverse", inverse(transformM)) + lines.dumpMat4("pure_rotation.inverse", inverse(pureRotationM)) + + lines.heading("cross product") + lines.appendLine("notes: cross(a, b) where a and b are vec3") + let crossA = vec3f(1, 0, 0) + let crossB = vec3f(0, 1, 0) + let crossC = normalize(vec3f(1, 2, 3)) + let crossD = normalize(vec3f(-1, 0.5, 2)) + lines.dumpVec3("cross(x_axis, y_axis)", cross(crossA, crossB)) + lines.dumpVec3("cross(y_axis, x_axis)", cross(crossB, crossA)) + lines.dumpVec3("cross(c, d)", cross(crossC, crossD)) + + lines.heading("slerp") + lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") + lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", slerp(quatX, quatZ, 0.25'f32)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", slerp(quatX, quatZ, 0.5'f32)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) + + lines.heading("fromTwoVectors") + lines.appendLine("notes: quaternion that rotates vector a to vector b") + proc fromTwoVectors(a, b: Vec3f): Quatf = + let d = dot(a, b) + let c = cross(a, b) + let w = sqrt(dot(a, a) * dot(b, b)) + d + result = quatf(c.x, c.y, c.z, w) + result = normalize(result) + let fromA = vec3f(1, 0, 0) + let fromB = vec3f(0, 1, 0) + let fromC = normalize(vec3f(1, 2, -1)) + let fromD = normalize(vec3f(-1, 0.5, 2)) + lines.dumpQuat("from_x_to_y", fromTwoVectors(fromA, fromB)) + lines.dumpQuat("from_c_to_d", fromTwoVectors(fromC, fromD)) + lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) + lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) + + lines.heading("quaternion inverse") + lines.dumpQuat("from_axis_angle.inverse", inverse(axisQuat)) + lines.dumpQuat("verify_q_mul_qinv", axisQuat * inverse(axisQuat)) + + lines.heading("quaternion to axis-angle") + lines.dumpVec3("from_axis_angle.axis", axis(axisQuat)) + lines.dumpScalar("from_axis_angle.angle", angle(axisQuat)) + lines.dumpVec3("quat_xyz.axis", axis(quatXYZ)) + lines.dumpScalar("quat_xyz.angle", angle(quatXYZ)) + + lines.heading("basis directions") + lines.appendLine("N/A") writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/conformance/dump_glm.txt b/conformance/dump_glm.txt index 33b4545..0f3f158 100644 --- a/conformance/dump_glm.txt +++ b/conformance/dump_glm.txt @@ -1,7 +1,14 @@ == dump == -notes: matrices are printed in raw in-memory order, four scalars per line +notes: matrices are printed in common column-major order -== matrix constructors and composition == +== matrix basics == +identity: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] matrix_a: [ +001.000 +005.000 +009.000 +013.000 @@ -16,6 +23,8 @@ matrix_b: -030.000 +070.000 +110.000 +150.000 -040.000 +080.000 +120.000 +160.000 ] + +== matrix multiply == matrix_a * matrix_b: [ -900.000 +2020.000 +3140.000 +4260.000 @@ -30,3 +39,258 @@ matrix_b * matrix_a: +1040.000 +2240.000 +3440.000 +4640.000 +1120.000 +2400.000 +3680.000 +4960.000 ] + +== element access [row, col] == +transform[0, 0]: +001.000 +transform[0, 1]: +002.000 +transform[0, 2]: +003.000 +transform[0, 3]: +004.000 +transform[1, 0]: +005.000 +transform[1, 1]: +006.000 +transform[1, 2]: +007.000 +transform[1, 3]: +008.000 +transform[2, 0]: +009.000 +transform[2, 1]: +010.000 +transform[2, 2]: +011.000 +transform[2, 3]: +012.000 +transform[3, 0]: +013.000 +transform[3, 1]: +014.000 +transform[3, 2]: +015.000 +transform[3, 3]: +016.000 + +== matrix constructors and composition == +scale: +[ + +002.000 +000.000 +000.000 +000.000 + +000.000 +003.000 +000.000 +000.000 + +000.000 +000.000 +004.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +translate: +[ + +001.000 +000.000 +000.000 +010.000 + +000.000 +001.000 +000.000 +020.000 + +000.000 +000.000 +001.000 +030.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_x: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +000.799 -000.602 +000.000 + +000.000 +000.602 +000.799 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_y: +[ + +000.921 +000.000 -000.391 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.391 +000.000 +000.921 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_z: +[ + +000.326 -000.946 +000.000 +000.000 + +000.946 +000.326 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation = rotate_z * rotate_y * rotate_x: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform = translate * rotate_z * rotate_y * rotate_x * scale: +[ + +000.599 -002.495 +001.870 +010.000 + +001.741 +000.113 -001.964 +020.000 + +000.781 +001.662 +002.941 +030.000 + +000.000 +000.000 +000.000 +001.000 +] + +== matrix vector multiply == +vec3_input: <+001.250, -002.500, +003.750> +vec4_input: <+001.250, -002.500, +003.750, +001.000> +transform * vec3: <+023.998, +014.529, +037.849> +transform * vec4: <+023.998, +014.529, +037.849, +001.000> +rotate_z * vec3: <+002.771, +000.368, +003.750> +translate * vec3: <+011.250, +017.500, +033.750> + +== quaternion constructors == +quat_identity: <+000.000, +000.000, +000.000, +001.000> +quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> +quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> +quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> +axis_normalized: <+000.267, +000.535, -000.802> +axis_angle_radians: +000.838 +from_axis_angle: <+000.109, +000.217, -000.326, +000.914> +from_axis_angle.mat4: +[ + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion multiply == +quat_x: <+000.317, +000.000, +000.000, +000.948> +quat_y: <+000.000, -000.199, +000.000, +000.980> +quat_z: <+000.000, +000.000, +000.581, +000.814> +quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> +quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> +quat_xy.mat4: +[ + +000.921 +000.000 -000.391 +000.000 + -000.235 +000.799 -000.554 +000.000 + +000.312 +000.602 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +quat_xyz.mat4: +[ + +000.300 -000.870 -000.391 +000.000 + +000.679 +000.482 -000.554 +000.000 + +000.671 -000.099 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion vector rotate == +input: <+001.250, -002.500, +003.750> +quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> +quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> +quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> +quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> +quat_z * input: <+002.771, +000.368, +003.750> + +== matrix quaternion roundtrip == +pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> +pure_rotation: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation.quat.mat4: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only: +[ + +000.599 -002.495 +001.870 +000.000 + +001.741 +000.113 -001.964 +000.000 + +000.781 +001.662 +002.941 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> +axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> +axis_mat.quat.mat4: +[ + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero +hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.mat4: +[ + -000.843 -000.423 +000.332 +000.000 + -000.144 -000.418 -000.897 +000.000 + +000.518 -000.804 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.quat_from_mat.mat4: +[ + -000.843 -000.423 +000.332 +000.000 + -000.144 -000.418 -000.897 +000.000 + +000.518 -000.804 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.002 -000.200 + +000.000 +000.000 -001.000 +000.000 +] + +== ortho matrix == +notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 +ortho: +[ + +000.100 +000.000 +000.000 +000.000 + +000.000 +000.133 +000.000 +000.000 + +000.000 +000.000 -000.020 -001.002 + +000.000 +000.000 +000.000 +001.000 +] + +== lookAt matrix == +notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) +lookAt: +[ + +000.707 +000.000 -000.707 +000.000 + -000.408 +000.816 -000.408 +000.000 + +000.577 +000.577 +000.577 -008.660 + +000.000 +000.000 +000.000 +001.000 +] + +== euler angle decomposition == +notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians +pure_rotation.quat.euler: <+000.646, -000.401, +001.239> +from_axis_angle.euler: <+000.064, +000.487, -000.670> + +== matrix inverse == +transform.inverse: +[ + +000.150 +000.435 +000.195 -016.063 + -000.277 +000.013 +000.185 -003.019 + +000.117 -000.123 +000.184 -004.227 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation.inverse: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== cross product == +notes: cross(a, b) where a and b are vec3 +cross(x_axis, y_axis): <+000.000, +000.000, +001.000> +cross(y_axis, x_axis): <+000.000, +000.000, -001.000> +cross(c, d): <+000.292, -000.583, +000.292> + +== slerp == +notes: slerp(a, b, t) between quat_x and quat_z +slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> +slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> +slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> + +== fromTwoVectors == +notes: quaternion that rotates vector a to vector b +from_x_to_y: <+000.000, +000.000, +000.707, +000.707> +from_c_to_d: <+000.707, -000.157, +000.393, +000.567> +verify_x_to_y: <+000.000, +001.000, +000.000> +verify_c_to_d: <-000.436, +000.218, +000.873> + +== quaternion inverse == +from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> +verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> + +== quaternion to axis-angle == +from_axis_angle.axis: <+000.267, +000.535, -000.802> +from_axis_angle.angle: +000.838 +quat_xyz.axis: <+000.235, -000.549, +000.802> +quat_xyz.angle: +001.309 + +== basis directions == +N/A diff --git a/conformance/dump_jolt.nim b/conformance/dump_jolt.nim index 479c2a5..a5b033f 100644 --- a/conformance/dump_jolt.nim +++ b/conformance/dump_jolt.nim @@ -50,16 +50,8 @@ proc dumpQuat(lines: var seq[string], label: string, value: JoltQuat) = proc dumpMat4(lines: var seq[string], label: string, value: JoltMat44) = lines.appendLine(label & ":") - lines.appendLine("[") - # for offset in countup(0, 12, 4): - # lines.appendLine( - # " " & - # fmt(value.m[offset + 0]) & " " & - # fmt(value.m[offset + 1]) & " " & - # fmt(value.m[offset + 2]) & " " & - # fmt(value.m[offset + 3]) - # ) let d = cast[array[16, float32]](value) + lines.appendLine("[") lines.appendLine(" " & fmt(d[0]) & " " & fmt(d[4]) & " " & fmt(d[8]) & " " & fmt(d[12]) ) lines.appendLine(" " & fmt(d[1]) & " " & fmt(d[5]) & " " & fmt(d[9]) & " " & fmt(d[13]) ) lines.appendLine(" " & fmt(d[2]) & " " & fmt(d[6]) & " " & fmt(d[10]) & " " & fmt(d[14]) ) @@ -84,10 +76,6 @@ proc multiply(m: JoltMat44, v: JoltFloat4): JoltFloat4 = var mm = m joltMat44MultiplyVec4(addr mm, v.x, v.y, v.z, v.w) -proc getRotationSafe(m: JoltMat44): JoltMat44 = - var mm = m - joltMat44GetRotationSafe(addr mm) - proc getQuaternion(m: JoltMat44): JoltQuat = var mm = m joltMat44GetQuaternion(addr mm) @@ -105,6 +93,18 @@ proc get(m: JoltMat44, row, col: int32): float32 = var mm = m joltMat44Get(addr mm, row, col) +proc inverse(m: JoltMat44): JoltMat44 = + var mm = m + joltMat44Inverse(addr mm) + +proc rotationOnlyCopy(value: JoltMat44): JoltMat44 = + var d = cast[array[16, float32]](value) + d[12] = 0'f32 + d[13] = 0'f32 + d[14] = 0'f32 + d[15] = 1'f32 + result = cast[JoltMat44](d) + proc main() = let physics = newPhysicsSystem( maxBodies = 1024, @@ -132,199 +132,194 @@ proc main() = 90.0f, 100.0f, 110.0f, 120.0f, 130.0f, 140.0f, 150.0f, 160.0f ]) - # matB = joltMat44FromMemory( - # 0.5, 1.5, -3.0, 0.0, - # -1.0, 0.75, 4.0, 1.0, - # 2.0, -0.5, 1.25, -1.5, - # 0.25, 2.0, -2.5, 3.0 - # ) - # vecA = JoltFloat3(x: 1.25, y: -2.5, z: 3.75) - # vecB = JoltFloat4(x: 1.25, y: -2.5, z: 3.75, w: 1.0) - # let - # scaleM = joltMat44Scale(2.0, 3.0, 4.0) - # translateM = joltMat44Translation(10.0, 20.0, 30.0) - # rotateXM = joltMat44RotationX(angleA) - # rotateYM = joltMat44RotationY(angleB) - # rotateZM = joltMat44RotationZ(angleC) - # pureRotationM = multiply(multiply(rotateZM, rotateYM), rotateXM) - # let - # axis = joltVec3Normalize(1.0, 2.0, -3.0) - # axisAngle = 48'f32 * PI.float32 / 180'f32 - # axisQuat = quatRotate(axis.x, axis.y, axis.z, axisAngle) - # axisMat = joltMat44FromQuat(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - # transformM = multiply(multiply(multiply(multiply(translateM, rotateZM), rotateYM), rotateXM), scaleM) - # let - # quatX = quatRotate(1.0, 0.0, 0.0, angleA) - # quatY = quatRotate(0.0, 1.0, 0.0, angleB) - # quatZ = quatRotate(0.0, 0.0, 1.0, angleC) - # quatXY = quatMultiply(quatX, quatY) - # quatXYZ = quatMultiply(quatXY, quatZ) - # let - # hardAxis = joltVec3Normalize(1.0, -2.0, 3.0) - # hardAngle = 170'f32 * PI.float32 / 180'f32 - # hardQuat = quatRotate(hardAxis.x, hardAxis.y, hardAxis.z, hardAngle) - # hardMat = joltMat44FromQuat(hardQuat.x, hardQuat.y, hardQuat.z, hardQuat.w) - # let - # rotationOnlyM = getRotationSafe(transformM) + + vecA = JoltFloat3(x: 1.25, y: -2.5, z: 3.75) + vecB = JoltFloat4(x: 1.25, y: -2.5, z: 3.75, w: 1.0) + + scaleM = joltMat44Scale(2.0, 3.0, 4.0) + translateM = joltMat44Translation(10.0, 20.0, 30.0) + rotateXM = joltMat44RotationX(angleA) + rotateYM = joltMat44RotationY(angleB) + rotateZM = joltMat44RotationZ(angleC) + pureRotationM = multiply(multiply(rotateZM, rotateYM), rotateXM) + + axis = joltVec3Normalize(1.0, 2.0, -3.0) + axisAngle = 48'f32 * PI.float32 / 180'f32 + axisQuat = quatRotate(axis.x, axis.y, axis.z, axisAngle) + axisMat = joltMat44FromQuat(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + transformM = multiply(multiply(multiply(multiply(translateM, rotateZM), rotateYM), rotateXM), scaleM) + + quatX = quatRotate(1.0, 0.0, 0.0, angleA) + quatY = quatRotate(0.0, 1.0, 0.0, angleB) + quatZ = quatRotate(0.0, 0.0, 1.0, angleC) + quatXY = quatMultiply(quatX, quatY) + quatXYZ = quatMultiply(quatXY, quatZ) + + hardAxis = joltVec3Normalize(1.0, -2.0, 3.0) + hardAngle = 170'f32 * PI.float32 / 180'f32 + hardQuat = quatRotate(hardAxis.x, hardAxis.y, hardAxis.z, hardAngle) + hardMat = joltMat44FromQuat(hardQuat.x, hardQuat.y, hardQuat.z, hardQuat.w) + + rotationOnlyM = rotationOnlyCopy(transformM) lines.heading("dump") - lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") + lines.appendLine("notes: matrices are printed in common column-major order") - lines.heading("matrix constructors and composition") - #lines.dumpMat4("identity", joltMat44Identity()) + lines.heading("matrix basics") + lines.dumpMat4("identity", joltMat44Identity()) lines.dumpMat4("matrix_a", matA) lines.dumpMat4("matrix_b", matB) + + lines.heading("matrix multiply") lines.dumpMat4("matrix_a * matrix_b", multiply(matA, matB)) lines.dumpMat4("matrix_b * matrix_a", multiply(matB, matA)) - # lines.dumpMat4("matrix_b", matB) - # lines.dumpMat4("scale", scaleM) - # lines.dumpMat4("translate", translateM) - # lines.dumpMat4("rotate_x", rotateXM) - # lines.dumpMat4("rotate_y", rotateYM) - # lines.dumpMat4("rotate_z", rotateZM) - # lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) - # lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) - - # lines.heading("matrix multiply") - # lines.dumpMat4("lhs", matA) - # lines.dumpMat4("rhs", matB) - # lines.dumpMat4("lhs * rhs", multiply(matA, matB)) - # lines.dumpMat4("rhs * lhs", multiply(matB, matA)) - - # lines.heading("matrix vector multiply") - # lines.dumpVec3("vec3_input", vecA) - # lines.dumpVec4("vec4_input", vecB) - # lines.dumpVec3("transform * vec3", multiply(transformM, vecA)) - # lines.dumpVec4("transform * vec4", multiply(transformM, vecB)) - # lines.dumpVec3("rotate_z * vec3", multiply(rotateZM, vecA)) - # lines.dumpVec3("translate * vec3", multiply(translateM, vecA)) - - # lines.heading("quaternion constructors") - # lines.dumpQuat("quat_identity", joltQuatIdentity()) - # lines.dumpQuat("quat_rotate_x", quatX) - # lines.dumpQuat("quat_rotate_y", quatY) - # lines.dumpQuat("quat_rotate_z", quatZ) - # lines.dumpVec3("axis_normalized", axis) - # lines.dumpScalar("axis_angle_radians", axisAngle) - # lines.dumpQuat("from_axis_angle", axisQuat) - # lines.dumpMat4("from_axis_angle.mat4", axisMat) - - # lines.heading("quaternion multiply") - # lines.dumpQuat("quat_x", quatX) - # lines.dumpQuat("quat_y", quatY) - # lines.dumpQuat("quat_z", quatZ) - # lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) - # lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) - # lines.dumpMat4("quat_xy.mat4", joltMat44FromQuat(quatXY.x, quatXY.y, quatXY.z, quatXY.w)) - # lines.dumpMat4("quat_xyz.mat4", joltMat44FromQuat(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w)) - - # lines.heading("quaternion vector rotate") - # lines.dumpVec3("input", vecA) - # lines.dumpVec3("quat_rotate(quat_x, input)", quatRotateVec3(quatX, vecA)) - # lines.dumpVec3("quat_rotate(quat_y, input)", quatRotateVec3(quatY, vecA)) - # lines.dumpVec3("quat_rotate(quat_z, input)", quatRotateVec3(quatZ, vecA)) - # lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotateVec3(axisQuat, vecA)) - # lines.dumpVec3("quat_z * input", quatRotateVec3(quatZ, vecA)) - - # lines.heading("matrix quaternion roundtrip") - # let pureRotationQuat = getQuaternion(pureRotationM) - # lines.dumpQuat("pure_rotation.quat", pureRotationQuat) - # lines.dumpMat4("pure_rotation", pureRotationM) - # lines.dumpMat4("pure_rotation.quat.mat4", joltMat44FromQuat(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w)) - # lines.dumpMat4("transform.rotation_only", rotationOnlyM) - # lines.dumpQuat("transform.rotation_only.quat", getQuaternion(rotationOnlyM)) - # let axisMatQuat = axisQuat - # lines.dumpQuat("axis_mat.quat", axisMatQuat) - # lines.dumpMat4("axis_mat.quat.mat4", joltMat44FromQuat(axisMatQuat.x, axisMatQuat.y, axisMatQuat.z, axisMatQuat.w)) - # lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") - # lines.dumpQuat("hard_decomp.quat_original", hardQuat) - # let hardMatQuat = getQuaternion(hardMat) - # lines.dumpQuat("hard_decomp.quat_from_mat", hardMatQuat) - # lines.dumpMat4("hard_decomp.mat4", hardMat) - # lines.dumpMat4("hard_decomp.quat_from_mat.mat4", joltMat44FromQuat(hardMatQuat.x, hardMatQuat.y, hardMatQuat.z, hardMatQuat.w)) - - # let fovyRad = 60'f32 * PI.float32 / 180'f32 - - # lines.heading("perspective matrix") - # lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - # lines.appendLine("notes: Jolt uses Z range [0,1] (Vulkan/DirectX convention), values will differ from OpenGL [-1,1]") - # lines.dumpMat4("perspective", joltMat44Perspective(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) - - # lines.heading("ortho matrix") - # lines.appendLine("N/A") - - # lines.heading("lookAt matrix") - # lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") - # lines.dumpMat4("lookAt", joltMat44LookAt(5, 5, 5, 0, 0, 0, 0, 1, 0)) - - # lines.heading("euler angle decomposition") - # lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") - # let pureRotEuler = joltQuatGetEulerAngles(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w) - # let axisEuler = joltQuatGetEulerAngles(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - # lines.dumpVec3("pure_rotation.quat.euler", pureRotEuler) - # lines.dumpVec3("from_axis_angle.euler", axisEuler) - - # lines.heading("matrix inverse") - # var transformInv = transformM - # lines.dumpMat4("transform.inverse", joltMat44Inverse(addr transformInv)) - # var pureRotInv = pureRotationM - # lines.dumpMat4("pure_rotation.inverse", joltMat44Inverse(addr pureRotInv)) - - # lines.heading("cross product") - # lines.appendLine("notes: cross(a, b) where a and b are vec3") - # lines.dumpVec3("cross(x_axis, y_axis)", joltVec3Cross(1, 0, 0, 0, 1, 0)) - # lines.dumpVec3("cross(y_axis, x_axis)", joltVec3Cross(0, 1, 0, 1, 0, 0)) - # let crossC = joltVec3Normalize(1, 2, 3) - # let crossD = joltVec3Normalize(-1, 0.5, 2) - # lines.dumpVec3("cross(c, d)", joltVec3Cross(crossC.x, crossC.y, crossC.z, crossD.x, crossD.y, crossD.z)) - - # lines.heading("slerp") - # lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") - # lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.25)) - # lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.5)) - # lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.75)) - - # lines.heading("fromTwoVectors") - # lines.appendLine("notes: quaternion that rotates vector a to vector b") - # let ftFromA = JoltFloat3(x: 1, y: 0, z: 0) - # let ftFromB = JoltFloat3(x: 0, y: 1, z: 0) - # let ftFromC = joltVec3Normalize(1, 2, -1) - # let ftFromD = joltVec3Normalize(-1, 0.5, 2) - # let ftQAB = joltQuatFromTo(ftFromA.x, ftFromA.y, ftFromA.z, ftFromB.x, ftFromB.y, ftFromB.z) - # let ftQCD = joltQuatFromTo(ftFromC.x, ftFromC.y, ftFromC.z, ftFromD.x, ftFromD.y, ftFromD.z) - # lines.dumpQuat("from_x_to_y", ftQAB) - # lines.dumpQuat("from_c_to_d", ftQCD) - # lines.dumpVec3("verify_x_to_y", quatRotateVec3(ftQAB, ftFromA)) - # lines.dumpVec3("verify_c_to_d", quatRotateVec3(ftQCD, ftFromC)) - - # lines.heading("quaternion inverse") - # let axisQuatInv = joltQuatInverse(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - # lines.dumpQuat("from_axis_angle.inverse", axisQuatInv) - # let verifyInv = quatMultiply(axisQuat, axisQuatInv) - # lines.dumpQuat("verify_q_mul_qinv", verifyInv) - - # lines.heading("quaternion to axis-angle") - # let aa1 = joltQuatGetAxisAngle(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - # let aa2 = joltQuatGetAxisAngle(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w) - # lines.dumpVec3("from_axis_angle.axis", JoltFloat3(x: aa1.x, y: aa1.y, z: aa1.z)) - # lines.dumpScalar("from_axis_angle.angle", aa1.w) - # lines.dumpVec3("quat_xyz.axis", JoltFloat3(x: aa2.x, y: aa2.y, z: aa2.z)) - # lines.dumpScalar("quat_xyz.angle", aa2.w) - - # lines.heading("basis directions") - # lines.appendLine("N/A") - - # lines.heading("element access [row,col]") - # lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") - # lines.dumpScalar("transform[0,0]", transformM.get(0, 0)) - # lines.dumpScalar("transform[0,1]", transformM.get(0, 1)) - # lines.dumpScalar("transform[0,2]", transformM.get(0, 2)) - # lines.dumpScalar("transform[0,3]", transformM.get(0, 3)) - # lines.dumpScalar("transform[1,0]", transformM.get(1, 0)) - # lines.dumpScalar("transform[1,3]", transformM.get(1, 3)) - # lines.dumpScalar("transform[2,0]", transformM.get(2, 0)) - # lines.dumpScalar("transform[2,3]", transformM.get(2, 3)) - # lines.dumpScalar("transform[3,3]", transformM.get(3, 3)) + + lines.heading("element access [row, col]") + lines.dumpScalar("transform[0, 0]", matA.get(0, 0)) + lines.dumpScalar("transform[0, 1]", matA.get(0, 1)) + lines.dumpScalar("transform[0, 2]", matA.get(0, 2)) + lines.dumpScalar("transform[0, 3]", matA.get(0, 3)) + lines.dumpScalar("transform[1, 0]", matA.get(1, 0)) + lines.dumpScalar("transform[1, 1]", matA.get(1, 1)) + lines.dumpScalar("transform[1, 2]", matA.get(1, 2)) + lines.dumpScalar("transform[1, 3]", matA.get(1, 3)) + lines.dumpScalar("transform[2, 0]", matA.get(2, 0)) + lines.dumpScalar("transform[2, 1]", matA.get(2, 1)) + lines.dumpScalar("transform[2, 2]", matA.get(2, 2)) + lines.dumpScalar("transform[2, 3]", matA.get(2, 3)) + lines.dumpScalar("transform[3, 0]", matA.get(3, 0)) + lines.dumpScalar("transform[3, 1]", matA.get(3, 1)) + lines.dumpScalar("transform[3, 2]", matA.get(3, 2)) + lines.dumpScalar("transform[3, 3]", matA.get(3, 3)) + + lines.heading("matrix constructors and composition") + lines.dumpMat4("scale", scaleM) + lines.dumpMat4("translate", translateM) + lines.dumpMat4("rotate_x", rotateXM) + lines.dumpMat4("rotate_y", rotateYM) + lines.dumpMat4("rotate_z", rotateZM) + lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) + lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) + + lines.heading("matrix vector multiply") + lines.dumpVec3("vec3_input", vecA) + lines.dumpVec4("vec4_input", vecB) + lines.dumpVec3("transform * vec3", multiply(transformM, vecA)) + lines.dumpVec4("transform * vec4", multiply(transformM, vecB)) + lines.dumpVec3("rotate_z * vec3", multiply(rotateZM, vecA)) + lines.dumpVec3("translate * vec3", multiply(translateM, vecA)) + + lines.heading("quaternion constructors") + lines.dumpQuat("quat_identity", joltQuatIdentity()) + lines.dumpQuat("quat_rotate_x", quatX) + lines.dumpQuat("quat_rotate_y", quatY) + lines.dumpQuat("quat_rotate_z", quatZ) + lines.dumpVec3("axis_normalized", axis) + lines.dumpScalar("axis_angle_radians", axisAngle) + lines.dumpQuat("from_axis_angle", axisQuat) + lines.dumpMat4("from_axis_angle.mat4", axisMat) + + lines.heading("quaternion multiply") + lines.dumpQuat("quat_x", quatX) + lines.dumpQuat("quat_y", quatY) + lines.dumpQuat("quat_z", quatZ) + lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) + lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) + lines.dumpMat4("quat_xy.mat4", joltMat44FromQuat(quatXY.x, quatXY.y, quatXY.z, quatXY.w)) + lines.dumpMat4("quat_xyz.mat4", joltMat44FromQuat(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w)) + + lines.heading("quaternion vector rotate") + lines.dumpVec3("input", vecA) + lines.dumpVec3("quat_rotate(quat_x, input)", quatRotateVec3(quatX, vecA)) + lines.dumpVec3("quat_rotate(quat_y, input)", quatRotateVec3(quatY, vecA)) + lines.dumpVec3("quat_rotate(quat_z, input)", quatRotateVec3(quatZ, vecA)) + lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotateVec3(axisQuat, vecA)) + lines.dumpVec3("quat_z * input", quatRotateVec3(quatZ, vecA)) + + lines.heading("matrix quaternion roundtrip") + let pureRotationQuat = getQuaternion(pureRotationM) + lines.dumpQuat("pure_rotation.quat", pureRotationQuat) + lines.dumpMat4("pure_rotation", pureRotationM) + lines.dumpMat4("pure_rotation.quat.mat4", joltMat44FromQuat(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w)) + lines.dumpMat4("transform.rotation_only", rotationOnlyM) + lines.dumpQuat("transform.rotation_only.quat", getQuaternion(rotationOnlyM)) + let axisMatQuat = getQuaternion(axisMat) + lines.dumpQuat("axis_mat.quat", axisMatQuat) + lines.dumpMat4("axis_mat.quat.mat4", joltMat44FromQuat(axisMatQuat.x, axisMatQuat.y, axisMatQuat.z, axisMatQuat.w)) + lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") + lines.dumpQuat("hard_decomp.quat_original", hardQuat) + let hardMatQuat = getQuaternion(hardMat) + lines.dumpQuat("hard_decomp.quat_from_mat", hardMatQuat) + lines.dumpMat4("hard_decomp.mat4", hardMat) + lines.dumpMat4("hard_decomp.quat_from_mat.mat4", joltMat44FromQuat(hardMatQuat.x, hardMatQuat.y, hardMatQuat.z, hardMatQuat.w)) + + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + lines.appendLine("notes: Jolt uses Z range [0,1] (Vulkan/DirectX convention), values will differ from OpenGL [-1,1]") + let fovyRad = 60'f32 * PI.float32 / 180'f32 + lines.dumpMat4("perspective", joltMat44Perspective(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) + + lines.heading("ortho matrix") + lines.appendLine("N/A") + + lines.heading("lookAt matrix") + lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") + lines.dumpMat4("lookAt", joltMat44LookAt(5, 5, 5, 0, 0, 0, 0, 1, 0)) + + lines.heading("euler angle decomposition") + lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") + let pureRotEuler = joltQuatGetEulerAngles(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w) + let axisEuler = joltQuatGetEulerAngles(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + lines.dumpVec3("pure_rotation.quat.euler", pureRotEuler) + lines.dumpVec3("from_axis_angle.euler", axisEuler) + + lines.heading("matrix inverse") + lines.dumpMat4("transform.inverse", inverse(transformM)) + lines.dumpMat4("pure_rotation.inverse", inverse(pureRotationM)) + + lines.heading("cross product") + lines.appendLine("notes: cross(a, b) where a and b are vec3") + lines.dumpVec3("cross(x_axis, y_axis)", joltVec3Cross(1, 0, 0, 0, 1, 0)) + lines.dumpVec3("cross(y_axis, x_axis)", joltVec3Cross(0, 1, 0, 1, 0, 0)) + let crossC = joltVec3Normalize(1, 2, 3) + let crossD = joltVec3Normalize(-1, 0.5, 2) + lines.dumpVec3("cross(c, d)", joltVec3Cross(crossC.x, crossC.y, crossC.z, crossD.x, crossD.y, crossD.z)) + + lines.heading("slerp") + lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") + lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.25)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.5)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.75)) + + lines.heading("fromTwoVectors") + lines.appendLine("notes: quaternion that rotates vector a to vector b") + let ftFromA = JoltFloat3(x: 1, y: 0, z: 0) + let ftFromB = JoltFloat3(x: 0, y: 1, z: 0) + let ftFromC = joltVec3Normalize(1, 2, -1) + let ftFromD = joltVec3Normalize(-1, 0.5, 2) + let ftQAB = joltQuatFromTo(ftFromA.x, ftFromA.y, ftFromA.z, ftFromB.x, ftFromB.y, ftFromB.z) + let ftQCD = joltQuatFromTo(ftFromC.x, ftFromC.y, ftFromC.z, ftFromD.x, ftFromD.y, ftFromD.z) + lines.dumpQuat("from_x_to_y", ftQAB) + lines.dumpQuat("from_c_to_d", ftQCD) + lines.dumpVec3("verify_x_to_y", quatRotateVec3(ftQAB, ftFromA)) + lines.dumpVec3("verify_c_to_d", quatRotateVec3(ftQCD, ftFromC)) + + lines.heading("quaternion inverse") + let axisQuatInv = joltQuatInverse(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + lines.dumpQuat("from_axis_angle.inverse", axisQuatInv) + let verifyInv = quatMultiply(axisQuat, axisQuatInv) + lines.dumpQuat("verify_q_mul_qinv", verifyInv) + + lines.heading("quaternion to axis-angle") + let aa1 = joltQuatGetAxisAngle(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) + let aa2 = joltQuatGetAxisAngle(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w) + lines.dumpVec3("from_axis_angle.axis", JoltFloat3(x: aa1.x, y: aa1.y, z: aa1.z)) + lines.dumpScalar("from_axis_angle.angle", aa1.w) + lines.dumpVec3("quat_xyz.axis", JoltFloat3(x: aa2.x, y: aa2.y, z: aa2.z)) + lines.dumpScalar("quat_xyz.angle", aa2.w) + + lines.heading("basis directions") + lines.appendLine("N/A") writeFile(OutputPath, lines.join("\n") & "\n") physics.destroy() diff --git a/conformance/dump_jolt.txt b/conformance/dump_jolt.txt index 33b4545..3300a11 100644 --- a/conformance/dump_jolt.txt +++ b/conformance/dump_jolt.txt @@ -1,7 +1,14 @@ == dump == -notes: matrices are printed in raw in-memory order, four scalars per line +notes: matrices are printed in common column-major order -== matrix constructors and composition == +== matrix basics == +identity: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] matrix_a: [ +001.000 +005.000 +009.000 +013.000 @@ -16,6 +23,8 @@ matrix_b: -030.000 +070.000 +110.000 +150.000 -040.000 +080.000 +120.000 +160.000 ] + +== matrix multiply == matrix_a * matrix_b: [ -900.000 +2020.000 +3140.000 +4260.000 @@ -30,3 +39,252 @@ matrix_b * matrix_a: +1040.000 +2240.000 +3440.000 +4640.000 +1120.000 +2400.000 +3680.000 +4960.000 ] + +== element access [row, col] == +transform[0, 0]: +001.000 +transform[0, 1]: +005.000 +transform[0, 2]: +009.000 +transform[0, 3]: +013.000 +transform[1, 0]: +002.000 +transform[1, 1]: +006.000 +transform[1, 2]: +010.000 +transform[1, 3]: +014.000 +transform[2, 0]: +003.000 +transform[2, 1]: +007.000 +transform[2, 2]: +011.000 +transform[2, 3]: +015.000 +transform[3, 0]: +004.000 +transform[3, 1]: +008.000 +transform[3, 2]: +012.000 +transform[3, 3]: +016.000 + +== matrix constructors and composition == +scale: +[ + +002.000 +000.000 +000.000 +000.000 + +000.000 +003.000 +000.000 +000.000 + +000.000 +000.000 +004.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +translate: +[ + +001.000 +000.000 +000.000 +010.000 + +000.000 +001.000 +000.000 +020.000 + +000.000 +000.000 +001.000 +030.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_x: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +000.799 -000.602 +000.000 + +000.000 +000.602 +000.799 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_y: +[ + +000.921 +000.000 -000.391 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.391 +000.000 +000.921 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_z: +[ + +000.326 -000.946 +000.000 +000.000 + +000.946 +000.326 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation = rotate_z * rotate_y * rotate_x: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform = translate * rotate_z * rotate_y * rotate_x * scale: +[ + +000.599 -002.495 +001.870 +010.000 + +001.741 +000.113 -001.964 +020.000 + +000.781 +001.662 +002.941 +030.000 + +000.000 +000.000 +000.000 +001.000 +] + +== matrix vector multiply == +vec3_input: <+001.250, -002.500, +003.750> +vec4_input: <+001.250, -002.500, +003.750, +001.000> +transform * vec3: <+023.998, +014.529, +037.849> +transform * vec4: <+023.998, +014.529, +037.849, +001.000> +rotate_z * vec3: <+002.771, +000.368, +003.750> +translate * vec3: <+011.250, +017.500, +033.750> + +== quaternion constructors == +quat_identity: <+000.000, +000.000, +000.000, +001.000> +quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> +quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> +quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> +axis_normalized: <+000.267, +000.535, -000.802> +axis_angle_radians: +000.838 +from_axis_angle: <+000.109, +000.217, -000.326, +000.914> +from_axis_angle.mat4: +[ + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion multiply == +quat_x: <+000.317, +000.000, +000.000, +000.948> +quat_y: <+000.000, -000.199, +000.000, +000.980> +quat_z: <+000.000, +000.000, +000.581, +000.814> +quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> +quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> +quat_xy.mat4: +[ + +000.921 +000.000 -000.391 +000.000 + -000.235 +000.799 -000.554 +000.000 + +000.312 +000.602 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +quat_xyz.mat4: +[ + +000.300 -000.870 -000.391 +000.000 + +000.679 +000.482 -000.554 +000.000 + +000.671 -000.099 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion vector rotate == +input: <+001.250, -002.500, +003.750> +quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> +quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> +quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> +quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> +quat_z * input: <+002.771, +000.368, +003.750> + +== matrix quaternion roundtrip == +pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> +pure_rotation: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation.quat.mat4: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only: +[ + +000.599 -002.495 +001.870 +000.000 + +001.741 +000.113 -001.964 +000.000 + +000.781 +001.662 +002.941 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> +axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> +axis_mat.quat.mat4: +[ + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero +hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.mat4: +[ + -000.843 -000.423 +000.332 +000.000 + -000.144 -000.418 -000.897 +000.000 + +000.518 -000.804 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.quat_from_mat.mat4: +[ + -000.843 -000.423 +000.332 +000.000 + -000.144 -000.418 -000.897 +000.000 + +000.518 -000.804 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +notes: Jolt uses Z range [0,1] (Vulkan/DirectX convention), values will differ from OpenGL [-1,1] +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.001 -000.100 + +000.000 +000.000 -001.000 +000.000 +] + +== ortho matrix == +N/A + +== lookAt matrix == +notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) +lookAt: +[ + +000.707 +000.000 -000.707 +000.000 + -000.408 +000.816 -000.408 +000.000 + +000.577 +000.577 +000.577 -008.660 + +000.000 +000.000 +000.000 +001.000 +] + +== euler angle decomposition == +notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians +pure_rotation.quat.euler: <+000.646, -000.401, +001.239> +from_axis_angle.euler: <+000.064, +000.487, -000.670> + +== matrix inverse == +transform.inverse: +[ + +000.150 +000.435 +000.195 -016.063 + -000.277 +000.013 +000.185 -003.019 + +000.117 -000.123 +000.184 -004.227 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation.inverse: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== cross product == +notes: cross(a, b) where a and b are vec3 +cross(x_axis, y_axis): <+000.000, +000.000, +001.000> +cross(y_axis, x_axis): <+000.000, +000.000, -001.000> +cross(c, d): <+000.292, -000.583, +000.292> + +== slerp == +notes: slerp(a, b, t) between quat_x and quat_z +slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> +slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> +slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> + +== fromTwoVectors == +notes: quaternion that rotates vector a to vector b +from_x_to_y: <+000.000, +000.000, +000.707, +000.707> +from_c_to_d: <+000.707, -000.157, +000.393, +000.567> +verify_x_to_y: <+000.000, +001.000, +000.000> +verify_c_to_d: <-000.436, +000.218, +000.873> + +== quaternion inverse == +from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> +verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> + +== quaternion to axis-angle == +from_axis_angle.axis: <+000.267, +000.535, -000.802> +from_axis_angle.angle: +000.838 +quat_xyz.axis: <+000.235, -000.549, +000.802> +quat_xyz.angle: +001.309 + +== basis directions == +N/A diff --git a/conformance/dump_vmath.nim b/conformance/dump_vmath.nim index 4496acd..46155eb 100644 --- a/conformance/dump_vmath.nim +++ b/conformance/dump_vmath.nim @@ -50,36 +50,19 @@ proc dumpQuat(lines: var seq[string], label: string, value: Quat) = proc dumpMat4(lines: var seq[string], label: string, value: Mat4) = lines.appendLine(label & ":") - let d = GMat4[float32](value) + let d = cast[array[16, float32]](value) lines.appendLine("[") - lines.appendLine(" " & fmt(d.arr[0]) & " " & fmt(d.arr[4]) & " " & fmt(d.arr[8]) & " " & fmt(d.arr[12]) ) - lines.appendLine(" " & fmt(d.arr[1]) & " " & fmt(d.arr[5]) & " " & fmt(d.arr[9]) & " " & fmt(d.arr[13]) ) - lines.appendLine(" " & fmt(d.arr[2]) & " " & fmt(d.arr[6]) & " " & fmt(d.arr[10]) & " " & fmt(d.arr[14]) ) - lines.appendLine(" " & fmt(d.arr[3]) & " " & fmt(d.arr[7]) & " " & fmt(d.arr[11]) & " " & fmt(d.arr[15]) ) + lines.appendLine(" " & fmt(d[0]) & " " & fmt(d[4]) & " " & fmt(d[8]) & " " & fmt(d[12]) ) + lines.appendLine(" " & fmt(d[1]) & " " & fmt(d[5]) & " " & fmt(d[9]) & " " & fmt(d[13]) ) + lines.appendLine(" " & fmt(d[2]) & " " & fmt(d[6]) & " " & fmt(d[10]) & " " & fmt(d[14]) ) + lines.appendLine(" " & fmt(d[3]) & " " & fmt(d[7]) & " " & fmt(d[11]) & " " & fmt(d[15]) ) lines.appendLine("]") - # lines.appendLine("direct " & $value) - # lines.appendLine("array " & $value.arr) - proc heading(lines: var seq[string], title: string) = if lines.len > 0: lines.appendLine() lines.appendLine("== " & title & " ==") -proc mat4FromRows( - m00, m01, m02, m03, - m10, m11, m12, m13, - m20, m21, m22, m23, - m30, m31, m32, m33: float32 -): Mat4 = - ## Create a mat4 from row-major input (matching math notation). - mat4( - m00, m10, m20, m30, - m01, m11, m21, m31, - m02, m12, m22, m32, - m03, m13, m23, m33 - ) - proc rotationOnlyCopy(value: Mat4): Mat4 = result = value result[0, 3] = 0 @@ -94,214 +77,215 @@ proc main() = angleB = -23'f32.toRadians angleC = 71'f32.toRadians - - matA = mat4( - 1.0, 2.0, 3.0, 4.0, - 5.0, 6.0, 7.0, 8.0, - 9.0, 10.0, 11.0, 12.0, - 13.0, 14.0, 15.0, 16.0 - ) - - # matA = mat4FromRows( - # 1.0, 2.0, 3.0, 4.0, - # 5.0, 6.0, 7.0, 8.0, - # 9.0, 10.0, 11.0, 12.0, - # 13.0, 14.0, 15.0, 16.0 - # ) - matB = mat4( - -10, -20, -30, -40, - 50, 60, 70, 80, - 90, 100, 110, 120, - 130, 140, 150, 160 - ) - # vecA = vec3(1.25, -2.5, 3.75) - # vecB = vec4(1.25, -2.5, 3.75, 1.0) - - # scaleM = scale(vec3(2.0, 3.0, 4.0)) - # translateM = translate(vec3(10.0, 20.0, 30.0)) - # rotateXM = rotateX(angleA) - # rotateYM = rotateY(angleB) - # rotateZM = rotateZ(angleC) - # pureRotationM = rotateZM * rotateYM * rotateXM - # axis = normalize(vec3(1.0, 2.0, -3.0)) - # axisAngle = 48'f32.toRadians - # axisQuat = fromAxisAngle(axis, axisAngle) - # axisMat = axisQuat.mat4() - # transformM = translateM * rotateZM * rotateYM * rotateXM * scaleM - - # quatX = quatRotateX(angleA) - # quatY = quatRotateY(angleB) - # quatZ = quatRotateZ(angleC) - # quatXY = quatMultiply(quatX, quatY) - # quatXYZ = quatMultiply(quatXY, quatZ) - - # # Hard quat decomposition case: 170° around arbitrary axis (w near zero) - # hardAxis = normalize(vec3(1.0, -2.0, 3.0)) - # hardAngle = 170'f32.toRadians - # hardQuat = fromAxisAngle(hardAxis, hardAngle) - # hardMat = hardQuat.mat4() - - # rotationOnlyM = rotationOnlyCopy(transformM) - # basisRight = vec3(1.0, 0.0, 0.0) - # basisUp = vec3(0.0, 1.0, 0.0) - # basisForward = vec3(0.0, 0.0, 1.0) + matA = cast[Mat4]([ + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f + ]) + + matB = cast[Mat4]([ + -10.0f, -20.0f, -30.0f, -40.0f, + 50.0f, 60.0f, 70.0f, 80.0f, + 90.0f, 100.0f, 110.0f, 120.0f, + 130.0f, 140.0f, 150.0f, 160.0f + ]) + + vecA = vec3(1.25, -2.5, 3.75) + vecB = vec4(1.25, -2.5, 3.75, 1.0) + + scaleM = scale(vec3(2.0, 3.0, 4.0)) + translateM = translate(vec3(10.0, 20.0, 30.0)) + rotateXM = rotateX(angleA) + rotateYM = rotateY(angleB) + rotateZM = rotateZ(angleC) + pureRotationM = rotateZM * rotateYM * rotateXM + axis = normalize(vec3(1.0, 2.0, -3.0)) + axisAngle = 48'f32.toRadians + axisQuat = fromAxisAngle(axis, axisAngle) + axisMat = axisQuat.mat4() + transformM = translateM * rotateZM * rotateYM * rotateXM * scaleM + + quatX = quatRotateX(angleA) + quatY = quatRotateY(angleB) + quatZ = quatRotateZ(angleC) + quatXY = quatMultiply(quatX, quatY) + quatXYZ = quatMultiply(quatXY, quatZ) + + # Hard quat decomposition case: 170° around arbitrary axis (w near zero) + hardAxis = normalize(vec3(1.0, -2.0, 3.0)) + hardAngle = 170'f32.toRadians + hardQuat = fromAxisAngle(hardAxis, hardAngle) + hardMat = hardQuat.mat4() + + rotationOnlyM = rotationOnlyCopy(transformM) + basisRight = vec3(1.0, 0.0, 0.0) + basisUp = vec3(0.0, 1.0, 0.0) + basisForward = vec3(0.0, 0.0, 1.0) lines.heading("dump") - lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") + lines.appendLine("notes: matrices are printed in common column-major order") - lines.heading("matrix constructors and composition") - #lines.dumpMat4("identity", mat4()) + lines.heading("matrix basics") + lines.dumpMat4("identity", mat4()) lines.dumpMat4("matrix_a", matA) lines.dumpMat4("matrix_b", matB) + lines.heading("matrix multiply") lines.dumpMat4("matrix_a * matrix_b", matA * matB) lines.dumpMat4("matrix_b * matrix_a", matB * matA) - # lines.dumpMat4("matrix_b", matB) - # lines.dumpMat4("scale", scaleM) - # lines.dumpMat4("translate", translateM) - # lines.dumpMat4("rotate_x", rotateXM) - # lines.dumpMat4("rotate_y", rotateYM) - # lines.dumpMat4("rotate_z", rotateZM) - # lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) - # lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) - - # lines.heading("matrix multiply") - # lines.dumpMat4("lhs", matA) - # lines.dumpMat4("rhs", matB) - # lines.dumpMat4("lhs * rhs", matA * matB) - # lines.dumpMat4("rhs * lhs", matB * matA) - - # lines.heading("matrix vector multiply") - # lines.dumpVec3("vec3_input", vecA) - # lines.dumpVec4("vec4_input", vecB) - # lines.dumpVec3("transform * vec3", transformM * vecA) - # lines.dumpVec4("transform * vec4", transformM * vecB) - # lines.dumpVec3("rotate_z * vec3", rotateZM * vecA) - # lines.dumpVec3("translate * vec3", translateM * vecA) - - # lines.heading("quaternion constructors") - # lines.dumpQuat("quat_identity", quat()) - # lines.dumpQuat("quat_rotate_x", quatX) - # lines.dumpQuat("quat_rotate_y", quatY) - # lines.dumpQuat("quat_rotate_z", quatZ) - # lines.dumpVec3("axis_normalized", axis) - # lines.dumpScalar("axis_angle_radians", axisAngle) - # lines.dumpQuat("from_axis_angle", axisQuat) - # lines.dumpMat4("from_axis_angle.mat4", axisMat) - - # lines.heading("quaternion multiply") - # lines.dumpQuat("quat_x", quatX) - # lines.dumpQuat("quat_y", quatY) - # lines.dumpQuat("quat_z", quatZ) - # lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) - # lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) - # lines.dumpMat4("quat_xy.mat4", quatXY.mat4()) - # lines.dumpMat4("quat_xyz.mat4", quatXYZ.mat4()) - - # lines.heading("quaternion vector rotate") - # lines.dumpVec3("input", vecA) - # lines.dumpVec3("quat_rotate(quat_x, input)", quatRotate(quatX, vecA)) - # lines.dumpVec3("quat_rotate(quat_y, input)", quatRotate(quatY, vecA)) - # lines.dumpVec3("quat_rotate(quat_z, input)", quatRotate(quatZ, vecA)) - # lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotate(axisQuat, vecA)) - # lines.dumpVec3("quat_z * input", quatZ * vecA) - - # lines.heading("matrix quaternion roundtrip") - # lines.dumpQuat("pure_rotation.quat", pureRotationM.quat()) - # lines.dumpMat4("pure_rotation", pureRotationM) - # lines.dumpMat4("pure_rotation.quat.mat4", pureRotationM.quat().mat4()) - # lines.dumpMat4("transform.rotation_only", rotationOnlyM) - # lines.dumpQuat("transform.rotation_only.quat", rotationOnlyM.quat()) - # lines.dumpQuat("axis_mat.quat", axisMat.quat()) - # lines.dumpMat4("axis_mat.quat.mat4", axisMat.quat().mat4()) - # lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") - # lines.dumpQuat("hard_decomp.quat_original", hardQuat) - # lines.dumpQuat("hard_decomp.quat_from_mat", hardMat.quat()) - # lines.dumpMat4("hard_decomp.mat4", hardMat) - # lines.dumpMat4("hard_decomp.quat_from_mat.mat4", hardMat.quat().mat4()) - - # lines.heading("perspective matrix") - # lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - # lines.dumpMat4("perspective", perspective(60'f32, 1.5'f32, 0.1'f32, 100'f32)) - - # lines.heading("ortho matrix") - # lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") - # lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) - - # lines.heading("lookAt matrix") - # lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") - # lines.dumpMat4("lookAt", lookAt(vec3(5'f32, 5'f32, 5'f32), vec3(0'f32, 0'f32, 0'f32), vec3(0'f32, 1'f32, 0'f32))) - - # lines.heading("euler angle decomposition") - # lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") - # let pureRotAngles = pureRotationM.quat().toAngles() - # let axisAngles = axisQuat.toAngles() - # lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) - # lines.dumpVec3("from_axis_angle.euler", axisAngles) - - # lines.heading("basis directions") - # lines.appendLine("notes: library-specific, N/A for libraries without canonical basis helpers") - # lines.dumpVec3("canonical_right", basisRight) - # lines.dumpVec3("canonical_up", basisUp) - # lines.dumpVec3("canonical_forward", basisForward) - # lines.dumpVec3("quat_z.right", quatRotate(quatZ, basisRight)) - # lines.dumpVec3("quat_z.up", quatRotate(quatZ, basisUp)) - # lines.dumpVec3("quat_z.forward", quatRotate(quatZ, basisForward)) - - # lines.heading("matrix inverse") - # lines.dumpMat4("transform.inverse", transformM.inverse()) - # lines.dumpMat4("pure_rotation.inverse", pureRotationM.inverse()) - - # lines.heading("cross product") - # lines.appendLine("notes: cross(a, b) where a and b are vec3") - # let crossA = vec3(1'f32, 0'f32, 0'f32) - # let crossB = vec3(0'f32, 1'f32, 0'f32) - # let crossC = normalize(vec3(1'f32, 2'f32, 3'f32)) - # let crossD = normalize(vec3(-1'f32, 0.5'f32, 2'f32)) - # lines.dumpVec3("cross(x_axis, y_axis)", cross(crossA, crossB)) - # lines.dumpVec3("cross(y_axis, x_axis)", cross(crossB, crossA)) - # lines.dumpVec3("cross(c, d)", cross(crossC, crossD)) - - # lines.heading("slerp") - # lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") - # lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", slerp(quatX, quatZ, 0.25'f32)) - # lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", slerp(quatX, quatZ, 0.5'f32)) - # lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) - - # lines.heading("fromTwoVectors") - # lines.appendLine("notes: quaternion that rotates vector a to vector b") - # let fromA = normalize(vec3(1'f32, 0'f32, 0'f32)) - # let fromB = normalize(vec3(0'f32, 1'f32, 0'f32)) - # let fromC = normalize(vec3(1'f32, 2'f32, -1'f32)) - # let fromD = normalize(vec3(-1'f32, 0.5'f32, 2'f32)) - # lines.dumpQuat("from_x_to_y", fromTwoVectors(fromA, fromB)) - # lines.dumpQuat("from_c_to_d", fromTwoVectors(fromC, fromD)) - # lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) - # lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) - - # lines.heading("quaternion inverse") - # lines.dumpQuat("from_axis_angle.inverse", quatInverse(axisQuat)) - # lines.dumpQuat("verify_q_mul_qinv", quatMultiply(axisQuat, quatInverse(axisQuat))) - - # lines.heading("quaternion to axis-angle") - # let (aaAxis1, aaAngle1) = toAxisAngle(axisQuat) - # let (aaAxis2, aaAngle2) = toAxisAngle(quatXYZ) - # lines.dumpVec3("from_axis_angle.axis", aaAxis1) - # lines.dumpScalar("from_axis_angle.angle", aaAngle1) - # lines.dumpVec3("quat_xyz.axis", aaAxis2) - # lines.dumpScalar("quat_xyz.angle", aaAngle2) - - # lines.heading("element access [row,col]") - # lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") - # lines.dumpScalar("transform[0,0]", transformM[0, 0]) - # lines.dumpScalar("transform[0,1]", transformM[0, 1]) - # lines.dumpScalar("transform[0,2]", transformM[0, 2]) - # lines.dumpScalar("transform[0,3]", transformM[0, 3]) - # lines.dumpScalar("transform[1,0]", transformM[1, 0]) - # lines.dumpScalar("transform[1,3]", transformM[1, 3]) - # lines.dumpScalar("transform[2,0]", transformM[2, 0]) - # lines.dumpScalar("transform[2,3]", transformM[2, 3]) - # lines.dumpScalar("transform[3,3]", transformM[3, 3]) + + lines.heading("element access [row, col]") + lines.dumpScalar("transform[0, 0]", matA[0, 0]) + lines.dumpScalar("transform[0, 1]", matA[0, 1]) + lines.dumpScalar("transform[0, 2]", matA[0, 2]) + lines.dumpScalar("transform[0, 3]", matA[0, 3]) + lines.dumpScalar("transform[1, 0]", matA[1, 0]) + lines.dumpScalar("transform[1, 1]", matA[1, 1]) + lines.dumpScalar("transform[1, 2]", matA[1, 2]) + lines.dumpScalar("transform[1, 3]", matA[1, 3]) + lines.dumpScalar("transform[2, 0]", matA[2, 0]) + lines.dumpScalar("transform[2, 1]", matA[2, 1]) + lines.dumpScalar("transform[2, 2]", matA[2, 2]) + lines.dumpScalar("transform[2, 3]", matA[2, 3]) + lines.dumpScalar("transform[3, 0]", matA[3, 0]) + lines.dumpScalar("transform[3, 1]", matA[3, 1]) + lines.dumpScalar("transform[3, 2]", matA[3, 2]) + lines.dumpScalar("transform[3, 3]", matA[3, 3]) + + lines.heading("matrix constructors and composition") + lines.dumpMat4("scale", scaleM) + lines.dumpMat4("translate", translateM) + lines.dumpMat4("rotate_x", rotateXM) + lines.dumpMat4("rotate_y", rotateYM) + lines.dumpMat4("rotate_z", rotateZM) + lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) + lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) + + lines.heading("matrix vector multiply") + lines.dumpVec3("vec3_input", vecA) + lines.dumpVec4("vec4_input", vecB) + lines.dumpVec3("transform * vec3", transformM * vecA) + lines.dumpVec4("transform * vec4", transformM * vecB) + lines.dumpVec3("rotate_z * vec3", rotateZM * vecA) + lines.dumpVec3("translate * vec3", translateM * vecA) + + lines.heading("quaternion constructors") + lines.dumpQuat("quat_identity", quat()) + lines.dumpQuat("quat_rotate_x", quatX) + lines.dumpQuat("quat_rotate_y", quatY) + lines.dumpQuat("quat_rotate_z", quatZ) + lines.dumpVec3("axis_normalized", axis) + lines.dumpScalar("axis_angle_radians", axisAngle) + lines.dumpQuat("from_axis_angle", axisQuat) + lines.dumpMat4("from_axis_angle.mat4", axisMat) + + lines.heading("quaternion multiply") + lines.dumpQuat("quat_x", quatX) + lines.dumpQuat("quat_y", quatY) + lines.dumpQuat("quat_z", quatZ) + lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) + lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) + lines.dumpMat4("quat_xy.mat4", quatXY.mat4()) + lines.dumpMat4("quat_xyz.mat4", quatXYZ.mat4()) + + lines.heading("quaternion vector rotate") + lines.dumpVec3("input", vecA) + lines.dumpVec3("quat_rotate(quat_x, input)", quatRotate(quatX, vecA)) + lines.dumpVec3("quat_rotate(quat_y, input)", quatRotate(quatY, vecA)) + lines.dumpVec3("quat_rotate(quat_z, input)", quatRotate(quatZ, vecA)) + lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotate(axisQuat, vecA)) + lines.dumpVec3("quat_z * input", quatZ * vecA) + + lines.heading("matrix quaternion roundtrip") + lines.dumpQuat("pure_rotation.quat", pureRotationM.quat()) + lines.dumpMat4("pure_rotation", pureRotationM) + lines.dumpMat4("pure_rotation.quat.mat4", pureRotationM.quat().mat4()) + lines.dumpMat4("transform.rotation_only", rotationOnlyM) + lines.dumpQuat("transform.rotation_only.quat", rotationOnlyM.quat()) + lines.dumpQuat("axis_mat.quat", axisMat.quat()) + lines.dumpMat4("axis_mat.quat.mat4", axisMat.quat().mat4()) + lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") + lines.dumpQuat("hard_decomp.quat_original", hardQuat) + lines.dumpQuat("hard_decomp.quat_from_mat", hardMat.quat()) + lines.dumpMat4("hard_decomp.mat4", hardMat) + lines.dumpMat4("hard_decomp.quat_from_mat.mat4", hardMat.quat().mat4()) + + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + lines.dumpMat4("perspective", perspective(60'f32, 1.5'f32, 0.1'f32, 100'f32)) + + lines.heading("ortho matrix") + lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") + lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) + + lines.heading("lookAt matrix") + lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") + lines.dumpMat4("lookAt", lookAt(vec3(5'f32, 5'f32, 5'f32), vec3(0'f32, 0'f32, 0'f32), vec3(0'f32, 1'f32, 0'f32))) + + lines.heading("euler angle decomposition") + lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") + let pureRotAngles = pureRotationM.quat().toAngles() + let axisAngles = axisQuat.toAngles() + lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) + lines.dumpVec3("from_axis_angle.euler", axisAngles) + + lines.heading("basis directions") + lines.appendLine("notes: library-specific, N/A for libraries without canonical basis helpers") + lines.dumpVec3("canonical_right", basisRight) + lines.dumpVec3("canonical_up", basisUp) + lines.dumpVec3("canonical_forward", basisForward) + lines.dumpVec3("quat_z.right", quatRotate(quatZ, basisRight)) + lines.dumpVec3("quat_z.up", quatRotate(quatZ, basisUp)) + lines.dumpVec3("quat_z.forward", quatRotate(quatZ, basisForward)) + + lines.heading("matrix inverse") + lines.dumpMat4("transform.inverse", transformM.inverse()) + lines.dumpMat4("pure_rotation.inverse", pureRotationM.inverse()) + + lines.heading("cross product") + lines.appendLine("notes: cross(a, b) where a and b are vec3") + let crossA = vec3(1'f32, 0'f32, 0'f32) + let crossB = vec3(0'f32, 1'f32, 0'f32) + let crossC = normalize(vec3(1'f32, 2'f32, 3'f32)) + let crossD = normalize(vec3(-1'f32, 0.5'f32, 2'f32)) + lines.dumpVec3("cross(x_axis, y_axis)", cross(crossA, crossB)) + lines.dumpVec3("cross(y_axis, x_axis)", cross(crossB, crossA)) + lines.dumpVec3("cross(c, d)", cross(crossC, crossD)) + + lines.heading("slerp") + lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") + lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", slerp(quatX, quatZ, 0.25'f32)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", slerp(quatX, quatZ, 0.5'f32)) + lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", slerp(quatX, quatZ, 0.75'f32)) + + lines.heading("fromTwoVectors") + lines.appendLine("notes: quaternion that rotates vector a to vector b") + let fromA = normalize(vec3(1'f32, 0'f32, 0'f32)) + let fromB = normalize(vec3(0'f32, 1'f32, 0'f32)) + let fromC = normalize(vec3(1'f32, 2'f32, -1'f32)) + let fromD = normalize(vec3(-1'f32, 0.5'f32, 2'f32)) + lines.dumpQuat("from_x_to_y", fromTwoVectors(fromA, fromB)) + lines.dumpQuat("from_c_to_d", fromTwoVectors(fromC, fromD)) + lines.dumpVec3("verify_x_to_y", fromTwoVectors(fromA, fromB) * fromA) + lines.dumpVec3("verify_c_to_d", fromTwoVectors(fromC, fromD) * fromC) + + lines.heading("quaternion inverse") + lines.dumpQuat("from_axis_angle.inverse", quatInverse(axisQuat)) + lines.dumpQuat("verify_q_mul_qinv", quatMultiply(axisQuat, quatInverse(axisQuat))) + + lines.heading("quaternion to axis-angle") + let (aaAxis1, aaAngle1) = toAxisAngle(axisQuat) + let (aaAxis2, aaAngle2) = toAxisAngle(quatXYZ) + lines.dumpVec3("from_axis_angle.axis", aaAxis1) + lines.dumpScalar("from_axis_angle.angle", aaAngle1) + lines.dumpVec3("quat_xyz.axis", aaAxis2) + lines.dumpScalar("quat_xyz.angle", aaAngle2) + + lines.heading("basis directions") + lines.dumpVec3("forward", matA.forward()) + lines.dumpVec3("right", matA.right()) + lines.dumpVec3("up", matA.up()) writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/conformance/dump_vmath.txt b/conformance/dump_vmath.txt index 33b4545..a9e5c64 100644 --- a/conformance/dump_vmath.txt +++ b/conformance/dump_vmath.txt @@ -1,7 +1,14 @@ == dump == -notes: matrices are printed in raw in-memory order, four scalars per line +notes: matrices are printed in common column-major order -== matrix constructors and composition == +== matrix basics == +identity: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] matrix_a: [ +001.000 +005.000 +009.000 +013.000 @@ -16,17 +23,285 @@ matrix_b: -030.000 +070.000 +110.000 +150.000 -040.000 +080.000 +120.000 +160.000 ] + +== matrix multiply == matrix_a * matrix_b: +[ + +880.000 +1920.000 +2960.000 +4000.000 + +960.000 +2080.000 +3200.000 +4320.000 + +1040.000 +2240.000 +3440.000 +4640.000 + +1120.000 +2400.000 +3680.000 +4960.000 +] +matrix_b * matrix_a: [ -900.000 +2020.000 +3140.000 +4260.000 -1000.000 +2280.000 +3560.000 +4840.000 -1100.000 +2540.000 +3980.000 +5420.000 -1200.000 +2800.000 +4400.000 +6000.000 ] -matrix_b * matrix_a: + +== element access [row, col] == +transform[0, 0]: +001.000 +transform[0, 1]: +002.000 +transform[0, 2]: +003.000 +transform[0, 3]: +004.000 +transform[1, 0]: +005.000 +transform[1, 1]: +006.000 +transform[1, 2]: +007.000 +transform[1, 3]: +008.000 +transform[2, 0]: +009.000 +transform[2, 1]: +010.000 +transform[2, 2]: +011.000 +transform[2, 3]: +012.000 +transform[3, 0]: +013.000 +transform[3, 1]: +014.000 +transform[3, 2]: +015.000 +transform[3, 3]: +016.000 + +== matrix constructors and composition == +scale: [ - +880.000 +1920.000 +2960.000 +4000.000 - +960.000 +2080.000 +3200.000 +4320.000 - +1040.000 +2240.000 +3440.000 +4640.000 - +1120.000 +2400.000 +3680.000 +4960.000 + +002.000 +000.000 +000.000 +000.000 + +000.000 +003.000 +000.000 +000.000 + +000.000 +000.000 +004.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +translate: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +001.000 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +010.000 +020.000 +030.000 +001.000 +] +rotate_x: +[ + +001.000 +000.000 +000.000 +000.000 + +000.000 +000.799 +000.602 +000.000 + +000.000 -000.602 +000.799 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_y: +[ + +000.921 +000.000 +000.391 +000.000 + +000.000 +001.000 +000.000 +000.000 + -000.391 +000.000 +000.921 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +rotate_z: +[ + +000.326 +000.946 +000.000 +000.000 + -000.946 +000.326 +000.000 +000.000 + +000.000 +000.000 +001.000 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation = rotate_z * rotate_y * rotate_x: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform = translate * rotate_z * rotate_y * rotate_x * scale: +[ + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 + +010.000 +020.000 +030.000 +001.000 +] + +== matrix vector multiply == +vec3_input: <+001.250, -002.500, +003.750> +vec4_input: <+001.250, -002.500, +003.750, +001.000> +transform * vec3: <+023.998, +014.529, +037.849> +transform * vec4: <+023.998, +014.529, +037.849, +001.000> +rotate_z * vec3: <+002.771, +000.368, +003.750> +translate * vec3: <+011.250, +017.500, +033.750> + +== quaternion constructors == +quat_identity: <+000.000, +000.000, +000.000, +001.000> +quat_rotate_x: <+000.317, +000.000, +000.000, +000.948> +quat_rotate_y: <+000.000, -000.199, +000.000, +000.980> +quat_rotate_z: <+000.000, +000.000, +000.581, +000.814> +axis_normalized: <+000.267, +000.535, -000.802> +axis_angle_radians: +000.838 +from_axis_angle: <+000.109, +000.217, -000.326, +000.914> +from_axis_angle.mat4: +[ + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion multiply == +quat_x: <+000.317, +000.000, +000.000, +000.948> +quat_y: <+000.000, -000.199, +000.000, +000.980> +quat_z: <+000.000, +000.000, +000.581, +000.814> +quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> +quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> +quat_xy.mat4: +[ + +000.921 -000.235 +000.312 +000.000 + +000.000 +000.799 +000.602 +000.000 + -000.391 -000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 ] +quat_xyz.mat4: +[ + +000.300 +000.679 +000.671 +000.000 + -000.870 +000.482 -000.099 +000.000 + -000.391 -000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== quaternion vector rotate == +input: <+001.250, -002.500, +003.750> +quat_rotate(quat_x, input): <+001.250, -004.253, +001.490> +quat_rotate(quat_y, input): <-000.315, -002.500, +003.940> +quat_rotate(quat_z, input): <+002.771, +000.368, +003.750> +quat_rotate(from_axis_angle, input): <+000.482, -003.871, +002.580> +quat_z * input: <+002.771, +000.368, +003.750> + +== matrix quaternion roundtrip == +pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> +pure_rotation: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +pure_rotation.quat.mat4: +[ + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only: +[ + +000.599 +001.741 +000.781 +000.000 + -002.495 +000.113 +001.662 +000.000 + +001.870 -001.964 +002.941 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> +axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> +axis_mat.quat.mat4: +[ + +000.693 -000.549 -000.468 +000.000 + +000.643 +000.764 +000.057 +000.000 + +000.326 -000.340 +000.882 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero +hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> +hard_decomp.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] +hard_decomp.quat_from_mat.mat4: +[ + -000.843 -000.144 +000.518 +000.000 + -000.423 -000.418 -000.804 +000.000 + +000.332 -000.897 +000.291 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.002 -001.000 + +000.000 +000.000 -000.200 +000.000 +] + +== ortho matrix == +notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 +ortho: +[ + +000.100 +000.000 +000.000 +000.000 + +000.000 +000.133 +000.000 +000.000 + +000.000 +000.000 -000.020 +000.000 + +000.000 +000.000 -001.002 +001.000 +] + +== lookAt matrix == +notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) +lookAt: +[ + +000.707 -000.408 +000.577 +000.000 + +000.000 +000.816 +000.577 +000.000 + -000.707 -000.408 +000.577 +000.000 + +000.000 +000.000 -008.660 +001.000 +] + +== euler angle decomposition == +notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians +pure_rotation.quat.euler: <+000.646, -000.401, +001.239> +from_axis_angle.euler: <+000.064, +000.487, -000.670> + +== basis directions == +notes: library-specific, N/A for libraries without canonical basis helpers +canonical_right: <+001.000, +000.000, +000.000> +canonical_up: <+000.000, +001.000, +000.000> +canonical_forward: <+000.000, +000.000, +001.000> +quat_z.right: <+000.326, +000.946, +000.000> +quat_z.up: <-000.946, +000.326, +000.000> +quat_z.forward: <+000.000, +000.000, +001.000> + +== matrix inverse == +transform.inverse: +[ + +000.150 -000.277 +000.117 +000.000 + +000.435 +000.013 -000.123 +000.000 + +000.195 +000.185 +000.184 +000.000 + -016.063 -003.019 -004.227 +001.000 +] +pure_rotation.inverse: +[ + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 + +000.000 +000.000 +000.000 +001.000 +] + +== cross product == +notes: cross(a, b) where a and b are vec3 +cross(x_axis, y_axis): <+000.000, +000.000, +001.000> +cross(y_axis, x_axis): <+000.000, +000.000, -001.000> +cross(c, d): <+000.292, -000.583, +000.292> + +== slerp == +notes: slerp(a, b, t) between quat_x and quat_z +slerp(quat_x, quat_z, 0.25): <+000.247, +000.000, +000.157, +000.956> +slerp(quat_x, quat_z, 0.5): <+000.169, +000.000, +000.308, +000.936> +slerp(quat_x, quat_z, 0.75): <+000.086, +000.000, +000.451, +000.888> + +== fromTwoVectors == +notes: quaternion that rotates vector a to vector b +from_x_to_y: <+000.000, +000.000, +000.707, +000.707> +from_c_to_d: <+000.707, -000.157, +000.393, +000.567> +verify_x_to_y: <+000.000, +001.000, +000.000> +verify_c_to_d: <-000.436, +000.218, +000.873> + +== quaternion inverse == +from_axis_angle.inverse: <-000.109, -000.217, +000.326, +000.914> +verify_q_mul_qinv: <+000.000, +000.000, +000.000, +001.000> + +== quaternion to axis-angle == +from_axis_angle.axis: <+000.267, +000.535, -000.802> +from_axis_angle.angle: +000.838 +quat_xyz.axis: <+000.235, -000.549, +000.802> +quat_xyz.angle: +001.309 + +== basis directions == +forward: <+003.000, +007.000, +011.000> +right: <+001.000, +005.000, +009.000> +up: <+002.000, +006.000, +010.000> diff --git a/src/vmath.nim b/src/vmath.nim index 0647ffd..da658e4 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -338,13 +338,13 @@ elif true or defined(vmathObjArrayBased): m30, m31, m32, m33 ]) - template `[]`*[T](a: GMat2[T], i, j: int): T = a.arr[j * 2 + i] - template `[]`*[T](a: GMat3[T], i, j: int): T = a.arr[j * 3 + i] - template `[]`*[T](a: GMat4[T], i, j: int): T = a.arr[j * 4 + i] + template `[]`*[T](a: GMat2[T], i, j: int): T = a.arr[i * 2 + j] + template `[]`*[T](a: GMat3[T], i, j: int): T = a.arr[i * 3 + j] + template `[]`*[T](a: GMat4[T], i, j: int): T = a.arr[i * 4 + j] - template `[]=`*[T](a: var GMat2[T], i, j: int, v: T) = a.arr[j * 2 + i] = v - template `[]=`*[T](a: var GMat3[T], i, j: int, v: T) = a.arr[j * 3 + i] = v - template `[]=`*[T](a: var GMat4[T], i, j: int, v: T) = a.arr[j * 4 + i] = v + template `[]=`*[T](a: var GMat2[T], i, j: int, v: T) = a.arr[i * 2 + j] = v + template `[]=`*[T](a: var GMat3[T], i, j: int, v: T) = a.arr[i * 3 + j] = v + template `[]=`*[T](a: var GMat4[T], i, j: int, v: T) = a.arr[i * 4 + j] = v template `[]`*[T](a: GMat2[T], i: int): GVec2[T] = gvec2[T]( From e3a613d77226f179e19f5ee75818fbdff0007c1c Mon Sep 17 00:00:00 2001 From: treeform Date: Tue, 14 Apr 2026 17:01:28 -0700 Subject: [PATCH 15/35] more match --- conformance/dump_glm.nim | 18 +- conformance/dump_glm.txt | 40 ++--- conformance/dump_jolt.nim | 17 +- conformance/dump_jolt.txt | 27 ++- conformance/dump_vmath.nim | 31 ++-- conformance/dump_vmath.txt | 179 +++++++++---------- src/vmath.nim | 344 ++++++++++++++++++------------------- 7 files changed, 318 insertions(+), 338 deletions(-) diff --git a/conformance/dump_glm.nim b/conformance/dump_glm.nim index 6da2131..5e15773 100644 --- a/conformance/dump_glm.nim +++ b/conformance/dump_glm.nim @@ -223,15 +223,6 @@ proc main() = lines.dumpMat4("hard_decomp.mat4", hardMat) lines.dumpMat4("hard_decomp.quat_from_mat.mat4", quat(hardMat).mat4()) - lines.heading("perspective matrix") - lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - let fovyRad = 60'f32 * PI.float32 / 180'f32 - lines.dumpMat4("perspective", perspectiveRH(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) - - lines.heading("ortho matrix") - lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") - lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) - lines.heading("lookAt matrix") lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") lines.dumpMat4("lookAt", lookAtRH(vec3f(5, 5, 5), vec3f(0, 0, 0), vec3f(0, 1, 0))) @@ -290,6 +281,15 @@ proc main() = lines.dumpVec3("quat_xyz.axis", axis(quatXYZ)) lines.dumpScalar("quat_xyz.angle", angle(quatXYZ)) + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + let fovyRad = 60'f32 * PI.float32 / 180'f32 + lines.dumpMat4("perspective", perspectiveRH(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) + + lines.heading("ortho matrix") + lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") + lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) + lines.heading("basis directions") lines.appendLine("N/A") diff --git a/conformance/dump_glm.txt b/conformance/dump_glm.txt index 0f3f158..762ae1e 100644 --- a/conformance/dump_glm.txt +++ b/conformance/dump_glm.txt @@ -212,26 +212,6 @@ hard_decomp.quat_from_mat.mat4: +000.000 +000.000 +000.000 +001.000 ] -== perspective matrix == -notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 -perspective: -[ - +001.155 +000.000 +000.000 +000.000 - +000.000 +001.732 +000.000 +000.000 - +000.000 +000.000 -001.002 -000.200 - +000.000 +000.000 -001.000 +000.000 -] - -== ortho matrix == -notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 -ortho: -[ - +000.100 +000.000 +000.000 +000.000 - +000.000 +000.133 +000.000 +000.000 - +000.000 +000.000 -000.020 -001.002 - +000.000 +000.000 +000.000 +001.000 -] - == lookAt matrix == notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) lookAt: @@ -292,5 +272,25 @@ from_axis_angle.angle: +000.838 quat_xyz.axis: <+000.235, -000.549, +000.802> quat_xyz.angle: +001.309 +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.002 -000.200 + +000.000 +000.000 -001.000 +000.000 +] + +== ortho matrix == +notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 +ortho: +[ + +000.100 +000.000 +000.000 +000.000 + +000.000 +000.133 +000.000 +000.000 + +000.000 +000.000 -000.020 -001.002 + +000.000 +000.000 +000.000 +001.000 +] + == basis directions == N/A diff --git a/conformance/dump_jolt.nim b/conformance/dump_jolt.nim index a5b033f..79c9d5b 100644 --- a/conformance/dump_jolt.nim +++ b/conformance/dump_jolt.nim @@ -253,15 +253,6 @@ proc main() = lines.dumpMat4("hard_decomp.mat4", hardMat) lines.dumpMat4("hard_decomp.quat_from_mat.mat4", joltMat44FromQuat(hardMatQuat.x, hardMatQuat.y, hardMatQuat.z, hardMatQuat.w)) - lines.heading("perspective matrix") - lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - lines.appendLine("notes: Jolt uses Z range [0,1] (Vulkan/DirectX convention), values will differ from OpenGL [-1,1]") - let fovyRad = 60'f32 * PI.float32 / 180'f32 - lines.dumpMat4("perspective", joltMat44Perspective(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) - - lines.heading("ortho matrix") - lines.appendLine("N/A") - lines.heading("lookAt matrix") lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") lines.dumpMat4("lookAt", joltMat44LookAt(5, 5, 5, 0, 0, 0, 0, 1, 0)) @@ -318,6 +309,14 @@ proc main() = lines.dumpVec3("quat_xyz.axis", JoltFloat3(x: aa2.x, y: aa2.y, z: aa2.z)) lines.dumpScalar("quat_xyz.angle", aa2.w) + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + let fovyRad = 60'f32 * PI.float32 / 180'f32 + lines.dumpMat4("perspective", joltMat44Perspective(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) + + lines.heading("ortho matrix") + lines.appendLine("N/A") + lines.heading("basis directions") lines.appendLine("N/A") diff --git a/conformance/dump_jolt.txt b/conformance/dump_jolt.txt index 3300a11..d5b097a 100644 --- a/conformance/dump_jolt.txt +++ b/conformance/dump_jolt.txt @@ -212,20 +212,6 @@ hard_decomp.quat_from_mat.mat4: +000.000 +000.000 +000.000 +001.000 ] -== perspective matrix == -notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 -notes: Jolt uses Z range [0,1] (Vulkan/DirectX convention), values will differ from OpenGL [-1,1] -perspective: -[ - +001.155 +000.000 +000.000 +000.000 - +000.000 +001.732 +000.000 +000.000 - +000.000 +000.000 -001.001 -000.100 - +000.000 +000.000 -001.000 +000.000 -] - -== ortho matrix == -N/A - == lookAt matrix == notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) lookAt: @@ -286,5 +272,18 @@ from_axis_angle.angle: +000.838 quat_xyz.axis: <+000.235, -000.549, +000.802> quat_xyz.angle: +001.309 +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.001 -000.100 + +000.000 +000.000 -001.000 +000.000 +] + +== ortho matrix == +N/A + == basis directions == N/A diff --git a/conformance/dump_vmath.nim b/conformance/dump_vmath.nim index 46155eb..70fdfe7 100644 --- a/conformance/dump_vmath.nim +++ b/conformance/dump_vmath.nim @@ -65,9 +65,9 @@ proc heading(lines: var seq[string], title: string) = proc rotationOnlyCopy(value: Mat4): Mat4 = result = value - result[0, 3] = 0 - result[1, 3] = 0 - result[2, 3] = 0 + result[3, 0] = 0 + result[3, 1] = 0 + result[3, 2] = 0 proc main() = var lines: seq[string] @@ -211,14 +211,6 @@ proc main() = lines.dumpMat4("hard_decomp.mat4", hardMat) lines.dumpMat4("hard_decomp.quat_from_mat.mat4", hardMat.quat().mat4()) - lines.heading("perspective matrix") - lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - lines.dumpMat4("perspective", perspective(60'f32, 1.5'f32, 0.1'f32, 100'f32)) - - lines.heading("ortho matrix") - lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") - lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) - lines.heading("lookAt matrix") lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") lines.dumpMat4("lookAt", lookAt(vec3(5'f32, 5'f32, 5'f32), vec3(0'f32, 0'f32, 0'f32), vec3(0'f32, 1'f32, 0'f32))) @@ -230,15 +222,6 @@ proc main() = lines.dumpVec3("pure_rotation.quat.euler", pureRotAngles) lines.dumpVec3("from_axis_angle.euler", axisAngles) - lines.heading("basis directions") - lines.appendLine("notes: library-specific, N/A for libraries without canonical basis helpers") - lines.dumpVec3("canonical_right", basisRight) - lines.dumpVec3("canonical_up", basisUp) - lines.dumpVec3("canonical_forward", basisForward) - lines.dumpVec3("quat_z.right", quatRotate(quatZ, basisRight)) - lines.dumpVec3("quat_z.up", quatRotate(quatZ, basisUp)) - lines.dumpVec3("quat_z.forward", quatRotate(quatZ, basisForward)) - lines.heading("matrix inverse") lines.dumpMat4("transform.inverse", transformM.inverse()) lines.dumpMat4("pure_rotation.inverse", pureRotationM.inverse()) @@ -282,6 +265,14 @@ proc main() = lines.dumpVec3("quat_xyz.axis", aaAxis2) lines.dumpScalar("quat_xyz.angle", aaAngle2) + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + lines.dumpMat4("perspective", perspective(60'f32, 1.5'f32, 0.1'f32, 100'f32)) + + lines.heading("ortho matrix") + lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") + lines.dumpMat4("ortho", ortho(-10'f32, 10'f32, -7.5'f32, 7.5'f32, 0.1'f32, 100'f32)) + lines.heading("basis directions") lines.dumpVec3("forward", matA.forward()) lines.dumpVec3("right", matA.right()) diff --git a/conformance/dump_vmath.txt b/conformance/dump_vmath.txt index a9e5c64..18c9dfb 100644 --- a/conformance/dump_vmath.txt +++ b/conformance/dump_vmath.txt @@ -26,19 +26,19 @@ matrix_b: == matrix multiply == matrix_a * matrix_b: -[ - +880.000 +1920.000 +2960.000 +4000.000 - +960.000 +2080.000 +3200.000 +4320.000 - +1040.000 +2240.000 +3440.000 +4640.000 - +1120.000 +2400.000 +3680.000 +4960.000 -] -matrix_b * matrix_a: [ -900.000 +2020.000 +3140.000 +4260.000 -1000.000 +2280.000 +3560.000 +4840.000 -1100.000 +2540.000 +3980.000 +5420.000 -1200.000 +2800.000 +4400.000 +6000.000 ] +matrix_b * matrix_a: +[ + +880.000 +1920.000 +2960.000 +4000.000 + +960.000 +2080.000 +3200.000 +4320.000 + +1040.000 +2240.000 +3440.000 +4640.000 + +1120.000 +2400.000 +3680.000 +4960.000 +] == element access [row, col] == transform[0, 0]: +001.000 @@ -68,45 +68,45 @@ scale: ] translate: [ - +001.000 +000.000 +000.000 +000.000 - +000.000 +001.000 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +010.000 +020.000 +030.000 +001.000 + +001.000 +000.000 +000.000 +010.000 + +000.000 +001.000 +000.000 +020.000 + +000.000 +000.000 +001.000 +030.000 + +000.000 +000.000 +000.000 +001.000 ] rotate_x: [ +001.000 +000.000 +000.000 +000.000 - +000.000 +000.799 +000.602 +000.000 - +000.000 -000.602 +000.799 +000.000 + +000.000 +000.799 -000.602 +000.000 + +000.000 +000.602 +000.799 +000.000 +000.000 +000.000 +000.000 +001.000 ] rotate_y: [ - +000.921 +000.000 +000.391 +000.000 + +000.921 +000.000 -000.391 +000.000 +000.000 +001.000 +000.000 +000.000 - -000.391 +000.000 +000.921 +000.000 + +000.391 +000.000 +000.921 +000.000 +000.000 +000.000 +000.000 +001.000 ] rotate_z: [ - +000.326 +000.946 +000.000 +000.000 - -000.946 +000.326 +000.000 +000.000 + +000.326 -000.946 +000.000 +000.000 + +000.946 +000.326 +000.000 +000.000 +000.000 +000.000 +001.000 +000.000 +000.000 +000.000 +000.000 +001.000 ] pure_rotation = rotate_z * rotate_y * rotate_x: [ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform = translate * rotate_z * rotate_y * rotate_x * scale: [ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 - +010.000 +020.000 +030.000 +001.000 + +000.599 -002.495 +001.870 +010.000 + +001.741 +000.113 -001.964 +020.000 + +000.781 +001.662 +002.941 +030.000 + +000.000 +000.000 +000.000 +001.000 ] == matrix vector multiply == @@ -127,9 +127,9 @@ axis_angle_radians: +000.838 from_axis_angle: <+000.109, +000.217, -000.326, +000.914> from_axis_angle.mat4: [ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -141,16 +141,16 @@ quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> quat_xy.mat4: [ - +000.921 -000.235 +000.312 +000.000 - +000.000 +000.799 +000.602 +000.000 - -000.391 -000.554 +000.735 +000.000 + +000.921 +000.000 -000.391 +000.000 + -000.235 +000.799 -000.554 +000.000 + +000.312 +000.602 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] quat_xyz.mat4: [ - +000.300 +000.679 +000.671 +000.000 - -000.870 +000.482 -000.099 +000.000 - -000.391 -000.554 +000.735 +000.000 + +000.300 -000.870 -000.391 +000.000 + +000.679 +000.482 -000.554 +000.000 + +000.671 -000.099 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -166,32 +166,32 @@ quat_z * input: <+002.771, +000.368, +003.750> pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> pure_rotation: [ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] pure_rotation.quat.mat4: [ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform.rotation_only: [ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 + +000.599 -002.495 +001.870 +000.000 + +001.741 +000.113 -001.964 +000.000 + +000.781 +001.662 +002.941 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> axis_mat.quat.mat4: [ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero @@ -199,47 +199,27 @@ hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> hard_decomp.mat4: [ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 + -000.843 -000.423 +000.332 +000.000 + -000.144 -000.418 -000.897 +000.000 + +000.518 -000.804 +000.291 +000.000 +000.000 +000.000 +000.000 +001.000 ] hard_decomp.quat_from_mat.mat4: [ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 + -000.843 -000.423 +000.332 +000.000 + -000.144 -000.418 -000.897 +000.000 + +000.518 -000.804 +000.291 +000.000 +000.000 +000.000 +000.000 +001.000 ] -== perspective matrix == -notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 -perspective: -[ - +001.155 +000.000 +000.000 +000.000 - +000.000 +001.732 +000.000 +000.000 - +000.000 +000.000 -001.002 -001.000 - +000.000 +000.000 -000.200 +000.000 -] - -== ortho matrix == -notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 -ortho: -[ - +000.100 +000.000 +000.000 +000.000 - +000.000 +000.133 +000.000 +000.000 - +000.000 +000.000 -000.020 +000.000 - +000.000 +000.000 -001.002 +001.000 -] - == lookAt matrix == notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) lookAt: [ - +000.707 -000.408 +000.577 +000.000 - +000.000 +000.816 +000.577 +000.000 - -000.707 -000.408 +000.577 +000.000 - +000.000 +000.000 -008.660 +001.000 + +000.707 +000.000 -000.707 +000.000 + -000.408 +000.816 -000.408 +000.000 + +000.577 +000.577 +000.577 -008.660 + +000.000 +000.000 +000.000 +001.000 ] == euler angle decomposition == @@ -247,28 +227,19 @@ notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians pure_rotation.quat.euler: <+000.646, -000.401, +001.239> from_axis_angle.euler: <+000.064, +000.487, -000.670> -== basis directions == -notes: library-specific, N/A for libraries without canonical basis helpers -canonical_right: <+001.000, +000.000, +000.000> -canonical_up: <+000.000, +001.000, +000.000> -canonical_forward: <+000.000, +000.000, +001.000> -quat_z.right: <+000.326, +000.946, +000.000> -quat_z.up: <-000.946, +000.326, +000.000> -quat_z.forward: <+000.000, +000.000, +001.000> - == matrix inverse == transform.inverse: [ - +000.150 -000.277 +000.117 +000.000 - +000.435 +000.013 -000.123 +000.000 - +000.195 +000.185 +000.184 +000.000 - -016.063 -003.019 -004.227 +001.000 + +000.150 +000.435 +000.195 -016.063 + -000.277 +000.013 +000.185 -003.019 + +000.117 -000.123 +000.184 -004.227 + +000.000 +000.000 +000.000 +001.000 ] pure_rotation.inverse: [ - +000.300 -000.832 +000.467 +000.000 - +000.870 +000.038 -000.491 +000.000 - +000.391 +000.554 +000.735 +000.000 + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -301,7 +272,27 @@ from_axis_angle.angle: +000.838 quat_xyz.axis: <+000.235, -000.549, +000.802> quat_xyz.angle: +001.309 +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.002 -000.200 + +000.000 +000.000 -001.000 +000.000 +] + +== ortho matrix == +notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 +ortho: +[ + +000.100 +000.000 +000.000 +000.000 + +000.000 +000.133 +000.000 +000.000 + +000.000 +000.000 -000.020 -001.002 + +000.000 +000.000 +000.000 +001.000 +] + == basis directions == -forward: <+003.000, +007.000, +011.000> -right: <+001.000, +005.000, +009.000> -up: <+002.000, +006.000, +010.000> +forward: <+009.000, +010.000, +011.000> +right: <+001.000, +002.000, +003.000> +up: <+005.000, +006.000, +007.000> diff --git a/src/vmath.nim b/src/vmath.nim index da658e4..22fbe7e 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -997,16 +997,16 @@ proc `~=`*[T](a, b: GMat4[T]): bool = a[0] ~= b[0] and a[1] ~= b[1] and a[2] ~= b[2] and a[3] ~= b[3] proc pos*[T](a: GMat3[T]): GVec2[T] = - gvec2[T](a[0, 2], a[1, 2]) + gvec2[T](a[2, 0], a[2, 1]) proc `pos=`*[T](a: var GMat3[T], pos: GVec2[T]) = - a[0, 2] = pos.x - a[1, 2] = pos.y + 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[0, 2] - result.y = a[1, 2] + result.x = a[2, 0] + result.y = a[2, 1] result.z = a[2, 2] proc back*[T](a: GMat4[T]): GVec3[T] {.inline.} = @@ -1020,14 +1020,14 @@ proc left*[T](a: GMat4[T]): GVec3[T] {.inline.} = proc right*[T](a: GMat4[T]): GVec3[T] {.inline.} = ## Vector facing +X. result.x = a[0, 0] - result.y = a[1, 0] - result.z = a[2, 0] + result.y = a[0, 1] + result.z = a[0, 2] proc up*[T](a: GMat4[T]): GVec3[T] {.inline.} = ## Vector facing +Y. - result.x = a[0, 1] + result.x = a[1, 0] result.y = a[1, 1] - result.z = a[2, 1] + result.z = a[1, 2] proc down*[T](a: GMat4[T]): GVec3[T] {.inline.} = ## Vector facing -X. @@ -1035,13 +1035,13 @@ proc down*[T](a: GMat4[T]): GVec3[T] {.inline.} = proc pos*[T](a: GMat4[T]): GVec3[T] = ## Position of the matrix. - gvec3[T](a[0, 3], a[1, 3], a[2, 3]) + gvec3[T](a[3, 0], a[3, 1], a[3, 2]) proc `pos=`*[T](a: var GMat4[T], pos: GVec3[T]) = ## See the position of the matrix. - a[0, 3] = pos.x - a[1, 3] = pos.y - a[2, 3] = pos.z + a[3, 0] = pos.x + a[3, 1] = pos.y + a[3, 2] = pos.z proc `*`*[T](a, b: GMat3[T]): GMat3[T] = let @@ -1080,59 +1080,59 @@ proc `*`*[T](a, b: GMat3[T]): GMat3[T] = proc `*`*[T](a: GMat2[T], b: GVec2[T]): GVec2[T] = gvec2[T]( - a[0, 0] * b.x + a[0, 1] * b.y, - a[1, 0] * b.x + a[1, 1] * b.y + a[0, 0] * b.x + a[1, 0] * b.y, + a[0, 1] * b.x + a[1, 1] * b.y ) proc `*`*[T](a: GMat3[T], b: GVec2[T]): GVec2[T] = gvec2[T]( - a[0, 0] * b.x + a[0, 1] * b.y + a[0, 2], - a[1, 0] * b.x + a[1, 1] * b.y + a[1, 2] + a[0, 0] * b.x + a[1, 0] * b.y + a[2, 0], + a[0, 1] * b.x + a[1, 1] * b.y + a[2, 1] ) proc `*`*[T](a: GMat3[T], b: GVec3[T]): GVec3[T] = gvec3[T]( - a[0, 0] * b.x + a[0, 1] * b.y + a[0, 2] * b.z, - a[1, 0] * b.x + a[1, 1] * b.y + a[1, 2] * b.z, - a[2, 0] * b.x + a[2, 1] * b.y + a[2, 2] * b.z, + a[0, 0] * b.x + a[1, 0] * b.y + a[2, 0] * b.z, + a[0, 1] * b.x + a[1, 1] * b.y + a[2, 1] * b.z, + a[0, 2] * b.x + a[1, 2] * b.y + a[2, 2] * b.z, ) proc `*`*[T](a, b: GMat4[T]): GMat4[T] = let - a00 = a[0, 0] - a10 = a[1, 0] - a20 = a[2, 0] - a30 = a[3, 0] - a01 = a[0, 1] - a11 = a[1, 1] - a21 = a[2, 1] - a31 = a[3, 1] - a02 = a[0, 2] - a12 = a[1, 2] - a22 = a[2, 2] - a32 = a[3, 2] - a03 = a[0, 3] - a13 = a[1, 3] - a23 = a[2, 3] - a33 = a[3, 3] + a00 = b[0, 0] + a10 = b[1, 0] + a20 = b[2, 0] + a30 = b[3, 0] + a01 = b[0, 1] + a11 = b[1, 1] + a21 = b[2, 1] + a31 = b[3, 1] + a02 = b[0, 2] + a12 = b[1, 2] + a22 = b[2, 2] + a32 = b[3, 2] + a03 = b[0, 3] + a13 = b[1, 3] + a23 = b[2, 3] + a33 = b[3, 3] let - b00 = b[0, 0] - b10 = b[1, 0] - b20 = b[2, 0] - b30 = b[3, 0] - b01 = b[0, 1] - b11 = b[1, 1] - b21 = b[2, 1] - b31 = b[3, 1] - b02 = b[0, 2] - b12 = b[1, 2] - b22 = b[2, 2] - b32 = b[3, 2] - b03 = b[0, 3] - b13 = b[1, 3] - b23 = b[2, 3] - b33 = b[3, 3] + b00 = a[0, 0] + b10 = a[1, 0] + b20 = a[2, 0] + b30 = a[3, 0] + b01 = a[0, 1] + b11 = a[1, 1] + b21 = a[2, 1] + b31 = a[3, 1] + b02 = a[0, 2] + b12 = a[1, 2] + b22 = a[2, 2] + b32 = a[3, 2] + b03 = a[0, 3] + b13 = a[1, 3] + b23 = a[2, 3] + b33 = a[3, 3] result[0, 0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30 result[1, 0] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30 @@ -1156,34 +1156,34 @@ proc `*`*[T](a, b: GMat4[T]): GMat4[T] = proc `*`*[T](a: GMat4[T], b: GVec3[T]): GVec3[T] = gvec3[T]( - a[0, 0] * b.x + a[0, 1] * b.y + a[0, 2] * b.z + a[0, 3], - a[1, 0] * b.x + a[1, 1] * b.y + a[1, 2] * b.z + a[1, 3], - a[2, 0] * b.x + a[2, 1] * b.y + a[2, 2] * b.z + a[2, 3] + a[0, 0] * b.x + a[1, 0] * b.y + a[2, 0] * b.z + a[3, 0], + a[0, 1] * b.x + a[1, 1] * b.y + a[2, 1] * b.z + a[3, 1], + a[0, 2] * b.x + a[1, 2] * b.y + a[2, 2] * b.z + a[3, 2] ) proc `*`*[T](a: GMat4[T], b: GVec4[T]): GVec4[T] = gvec4[T]( - a[0, 0] * b.x + a[0, 1] * b.y + a[0, 2] * b.z + a[0, 3] * b.w, - a[1, 0] * b.x + a[1, 1] * b.y + a[1, 2] * b.z + a[1, 3] * b.w, - a[2, 0] * b.x + a[2, 1] * b.y + a[2, 2] * b.z + a[2, 3] * b.w, - a[3, 0] * b.x + a[3, 1] * b.y + a[3, 2] * b.z + a[3, 3] * b.w + a[0, 0] * b.x + a[1, 0] * b.y + a[2, 0] * b.z + a[3, 0] * b.w, + a[0, 1] * b.x + a[1, 1] * b.y + a[2, 1] * b.z + a[3, 1] * b.w, + a[0, 2] * b.x + a[1, 2] * b.y + a[2, 2] * b.z + a[3, 2] * b.w, + a[0, 3] * b.x + a[1, 3] * b.y + a[2, 3] * b.z + a[3, 3] * b.w ) proc transpose*[T](a: GMat3[T]): GMat3[T] = ## Return a transpose of the matrix. gmat3[T]( - a[0, 0], a[0, 1], a[0, 2], - a[1, 0], a[1, 1], a[1, 2], - a[2, 0], a[2, 1], a[2, 2] + a[0, 0], a[1, 0], a[2, 0], + a[0, 1], a[1, 1], a[2, 1], + a[0, 2], a[1, 2], a[2, 2] ) proc transpose*[T](a: GMat4[T]): GMat4[T] = ## Return a transpose of the matrix. gmat4[T]( - a[0, 0], a[0, 1], a[0, 2], a[0, 3], - a[1, 0], a[1, 1], a[1, 2], a[1, 3], - a[2, 0], a[2, 1], a[2, 2], a[2, 3], - a[3, 0], a[3, 1], a[3, 2], a[3, 3] + a[0, 0], a[1, 0], a[2, 0], a[3, 0], + a[0, 1], a[1, 1], a[2, 1], a[3, 1], + a[0, 2], a[1, 2], a[2, 2], a[3, 2], + a[0, 3], a[1, 3], a[2, 3], a[3, 3] ) proc determinant*[T](a: GMat3[T]): T = @@ -1324,23 +1324,23 @@ proc translate*[T](v: GVec2[T]): GMat3[T] = proc translate*[T](v: GVec3[T]): GMat4[T] = ## Create translation matrix. result[0, 0] = 1 - result[0, 1] = 0 - result[0, 2] = 0 - result[0, 3] = v.x - result[1, 0] = 0 - result[1, 1] = 1 - result[1, 2] = 0 - result[1, 3] = v.y - result[2, 0] = 0 + result[3, 0] = v.x + + result[0, 1] = 0 + result[1, 1] = 1 result[2, 1] = 0 + result[3, 1] = v.y + + result[0, 2] = 0 + result[1, 2] = 0 result[2, 2] = 1 - result[2, 3] = v.z + result[3, 2] = v.z - result[3, 0] = 0 - result[3, 1] = 0 - result[3, 2] = 0 + result[0, 3] = 0 + result[1, 3] = 0 + result[2, 3] = 0 result[3, 3] = 1 proc rotate*[T](angle: T): GMat3[T] = @@ -1365,69 +1365,69 @@ proc rotateX*[T](angle: T): GMat4[T] = ## Return a rotation matrix around X with angle. ## All angles assume radians. result[0, 0] = 1 - result[1, 0] = 0 - result[2, 0] = 0 - result[3, 0] = 0 - result[0, 1] = 0 - result[1, 1] = cos(angle) - result[2, 1] = sin(angle) - result[3, 1] = 0 - result[0, 2] = 0 - result[1, 2] = -sin(angle) - result[2, 2] = cos(angle) - result[3, 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[1, 0] = 0 - result[2, 0] = -sin(angle) - result[3, 0] = 0 - result[0, 1] = 0 - result[1, 1] = 1 - result[2, 1] = 0 - result[3, 1] = 0 + result[0, 2] = -sin(angle) + result[0, 3] = 0 - result[0, 2] = sin(angle) + result[1, 0] = 0 + result[1, 1] = 1 result[1, 2] = 0 - result[2, 2] = cos(angle) - result[3, 2] = 0 - - result[0, 3] = 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[1, 0] = sin(angle) - result[2, 0] = 0 - result[3, 0] = 0 + result[0, 1] = sin(angle) + result[0, 2] = 0 + result[0, 3] = 0 - result[0, 1] = -sin(angle) + result[1, 0] = -sin(angle) result[1, 1] = cos(angle) - result[2, 1] = 0 - result[3, 1] = 0 - - result[0, 2] = 0 result[1, 2] = 0 - result[2, 2] = 1 - result[3, 2] = 0 - - result[0, 3] = 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] = @@ -1459,15 +1459,15 @@ proc toAngles*[T](m: GMat4[T]): GVec3[T] = ## roll (z rotation) ## Assumes matrix has not been scaled. ## All angles assume radians. - let sy = clamp(-m[1, 2], T(-1), T(1)) + let sy = clamp(-m[2, 1], T(-1), T(1)) result.x = arcsin(sy) if abs(sy) > T(0.9999999): # Degenerate case (gimbal lock). - result.y = arctan2(-m[2, 0], m[0, 0]) + result.y = arctan2(-m[0, 2], m[0, 0]) else: # Normal case. - result.y = arctan2(m[0, 2], m[2, 2]) - result.z = arctan2(m[1, 0], m[1, 1]) + 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. @@ -1482,23 +1482,23 @@ proc frustum*[T](left, right, bottom, top, near, far: T): GMat4[T] = fn = (far - near) result[0, 0] = (near * 2) / rl - result[1, 0] = 0 - result[2, 0] = 0 - result[3, 0] = 0 - result[0, 1] = 0 + result[0, 2] = 0 + result[0, 3] = 0 + + result[1, 0] = 0 result[1, 1] = (near * 2) / tb - result[2, 1] = 0 - result[3, 1] = 0 + result[1, 2] = 0 + result[1, 3] = 0 - result[0, 2] = (right + left) / rl - result[1, 2] = (top + bottom) / tb + result[2, 0] = (right + left) / rl + result[2, 1] = (top + bottom) / tb result[2, 2] = -(far + near) / fn - result[3, 2] = -1 + result[2, 3] = -1 - result[0, 3] = 0 - result[1, 3] = 0 - result[2, 3] = -(far * near * 2) / fn + result[3, 0] = 0 + result[3, 1] = 0 + result[3, 2] = -(far * near * 2) / fn result[3, 3] = 0 proc perspective*[T](fovy, aspect, near, far: T): GMat4[T] = @@ -1516,23 +1516,23 @@ proc ortho*[T](left, right, bottom, top, near, far: T): GMat4[T] = fn: T = (far - near) result[0, 0] = T(2 / rl) - result[1, 0] = 0 - result[2, 0] = 0 - result[3, 0] = 0 - result[0, 1] = 0 - result[1, 1] = T(2 / tb) - result[2, 1] = 0 - result[3, 1] = 0 - result[0, 2] = 0 + result[0, 3] = 0 + + result[1, 0] = 0 + result[1, 1] = T(2 / tb) result[1, 2] = 0 + result[1, 3] = 0 + + result[2, 0] = 0 + result[2, 1] = 0 result[2, 2] = T(-2 / fn) - result[3, 2] = 0 + result[2, 3] = 0 - result[0, 3] = T(-(left + right) / rl) - result[1, 3] = T(-(top + bottom) / tb) - result[2, 3] = T(-(far + near) / fn) + result[3, 0] = T(-(left + right) / rl) + result[3, 1] = T(-(top + bottom) / tb) + result[3, 2] = T(-(far + near) / fn) result[3, 3] = 1 proc lookAt*[T](eye, center, up: GVec3[T]): GMat4[T] = @@ -1597,23 +1597,23 @@ proc lookAt*[T](eye, center, up: GVec3[T]): GMat4[T] = y2 *= len result[0, 0] = x0 - result[1, 0] = y0 - result[2, 0] = z0 - result[3, 0] = 0 + result[0, 1] = y0 + result[0, 2] = z0 + result[0, 3] = 0 - result[0, 1] = x1 + result[1, 0] = x1 result[1, 1] = y1 - result[2, 1] = z1 - result[3, 1] = 0 + result[1, 2] = z1 + result[1, 3] = 0 - result[0, 2] = x2 - result[1, 2] = y2 + result[2, 0] = x2 + result[2, 1] = y2 result[2, 2] = z2 - result[3, 2] = 0 + result[2, 3] = 0 - result[0, 3] = -(x0 * eyex + x1 * eyey + x2 * eyez) - result[1, 3] = -(y0 * eyex + y1 * eyey + y2 * eyez) - result[2, 3] = -(z0 * eyex + z1 * eyey + z2 * eyez) + result[3, 0] = -(x0 * eyex + x1 * eyey + x2 * eyez) + result[3, 1] = -(y0 * eyex + y1 * eyey + y2 * eyez) + result[3, 2] = -(z0 * eyex + z1 * eyey + z2 * eyez) result[3, 3] = 1 proc lookAt*[T](eye, center: GVec3[T]): GMat4[T] = @@ -1746,15 +1746,15 @@ proc quat*[T](m: GMat4[T]): GVec4[T] = ## Create a quaternion from matrix. let m00 = m[0, 0] - m01 = m[1, 0] - m02 = m[2, 0] + m01 = m[0, 1] + m02 = m[0, 2] - m10 = m[0, 1] + m10 = m[1, 0] m11 = m[1, 1] - m12 = m[2, 1] + m12 = m[1, 2] - m20 = m[0, 2] - m21 = m[1, 2] + m20 = m[2, 0] + m21 = m[2, 1] m22 = m[2, 2] fourXSquaredMinus1 = m00 - m11 - m22 @@ -1818,23 +1818,23 @@ proc mat4*[T](q: GVec4[T]): GMat4[T] = zw = q.z * q.w result[0, 0] = 1 - 2 * (yy + zz) - result[1, 0] = 0 + 2 * (xy + zw) - result[2, 0] = 0 + 2 * (xz - yw) - result[3, 0] = 0 + result[0, 1] = 0 + 2 * (xy + zw) + result[0, 2] = 0 + 2 * (xz - yw) + result[0, 3] = 0 - result[0, 1] = 0 + 2 * (xy - zw) + result[1, 0] = 0 + 2 * (xy - zw) result[1, 1] = 1 - 2 * (xx + zz) - result[2, 1] = 0 + 2 * (yz + xw) - result[3, 1] = 0 + result[1, 2] = 0 + 2 * (yz + xw) + result[1, 3] = 0 - result[0, 2] = 0 + 2 * (xz + yw) - result[1, 2] = 0 + 2 * (yz - xw) + result[2, 0] = 0 + 2 * (xz + yw) + result[2, 1] = 0 + 2 * (yz - xw) result[2, 2] = 1 - 2 * (xx + yy) - result[3, 2] = 0 - - result[0, 3] = 0 - result[1, 3] = 0 result[2, 3] = 0 + + result[3, 0] = 0 + result[3, 1] = 0 + result[3, 2] = 0 result[3, 3] = 1.0 From b987ee1dac059cb46169cfccebcafcede41e5219 Mon Sep 17 00:00:00 2001 From: treeform Date: Tue, 14 Apr 2026 19:07:48 -0700 Subject: [PATCH 16/35] even more comp --- conformance/dump_glmatrix.js | 192 ++++++++++-------- conformance/dump_glmatrix.txt | 224 ++++++++++----------- conformance/dump_glsl.comp | 21 +- conformance/dump_glsl.nim | 100 +++++----- conformance/dump_glsl.txt | 250 +++++++++++------------ conformance/dump_jolt.cpp | 326 ++++++++++++++++++++++++++++++ conformance/dump_jolt.nim | 359 +++++----------------------------- 7 files changed, 766 insertions(+), 706 deletions(-) create mode 100644 conformance/dump_jolt.cpp diff --git a/conformance/dump_glmatrix.js b/conformance/dump_glmatrix.js index 6449646..65d77c0 100644 --- a/conformance/dump_glmatrix.js +++ b/conformance/dump_glmatrix.js @@ -21,17 +21,24 @@ function fmt(value) { let abs = Math.abs(cleaned); let whole = Math.floor(abs); let frac = Math.round((abs - whole) * 1000); - if (frac === 1000) { whole++; frac = 0; } + if (frac === 1000) { + whole++; + frac = 0; + } return sign + String(whole).padStart(3, "0") + "." + String(frac).padStart(3, "0"); } const lines = []; -function appendLine(line = "") { lines.push(line); } +function appendLine(line = "") { + lines.push(line); +} function heading(title) { if (lines.length > 0) appendLine(); appendLine("== " + title + " =="); } -function dumpScalar(label, value) { appendLine(label + ": " + fmt(value)); } +function dumpScalar(label, value) { + appendLine(label + ": " + fmt(value)); +} function dumpVec3(label, v) { appendLine(label + ": <" + fmt(v[0]) + ", " + fmt(v[1]) + ", " + fmt(v[2]) + ">"); } @@ -44,31 +51,13 @@ function dumpQuat(label, q) { function dumpMat4(label, m) { appendLine(label + ":"); appendLine("["); - for (let col = 0; col < 4; col++) { - const off = col * 4; - appendLine(" " + fmt(m[off]) + " " + fmt(m[off+1]) + " " + fmt(m[off+2]) + " " + fmt(m[off+3])); - } + appendLine(" " + fmt(m[0]) + " " + fmt(m[4]) + " " + fmt(m[8]) + " " + fmt(m[12])); + appendLine(" " + fmt(m[1]) + " " + fmt(m[5]) + " " + fmt(m[9]) + " " + fmt(m[13])); + appendLine(" " + fmt(m[2]) + " " + fmt(m[6]) + " " + fmt(m[10]) + " " + fmt(m[14])); + appendLine(" " + fmt(m[3]) + " " + fmt(m[7]) + " " + fmt(m[11]) + " " + fmt(m[15])); appendLine("]"); } -// Helper: create a mat4 from row-major input (math notation). -// Row-major element (r,c) goes to column-major arr[c*4+r]. -function mat4FromRows( - m00, m01, m02, m03, - m10, m11, m12, m13, - m20, m21, m22, m23, - m30, m31, m32, m33 -) { - return mat4.fromValues( - m00, m10, m20, m30, - m01, m11, m21, m31, - m02, m12, m22, m32, - m03, m13, m23, m33 - ); -} - -// gl-matrix transforms take (out, matrix, param) — mutate out. -// Helpers to build fresh matrices from identity. function scaleMat(sx, sy, sz) { return mat4.scale(mat4.create(), mat4.create(), vec3.fromValues(sx, sy, sz)); } @@ -107,35 +96,68 @@ function mat4FromQuat(q) { } function rotationOnlyCopy(m) { const out = mat4.clone(m); - out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; return out; } -function mat3FromMat4Upper(m) { - return mat3.fromMat4(mat3.create(), m); -} function quatFromMat(m) { - const m3 = mat3FromMat4Upper(m); - return quat.fromMat3(quat.create(), m3); + return quat.fromMat3(quat.create(), mat3.fromMat4(mat3.create(), m)); +} +function quatToEulerXYZ(q) { + const x = q[0]; + const y = q[1]; + const z = q[2]; + const w = q[3]; + + const ex = Math.atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y)); + const ey = Math.asin(Math.max(-1, Math.min(1, 2 * (w * y - z * x)))); + const ez = Math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z)); + return vec3.fromValues(ex, ey, ez); +} +function basisRight(m) { + return vec3.fromValues(m[0], m[1], m[2]); +} +function basisUp(m) { + return vec3.fromValues(m[4], m[5], m[6]); +} +function basisForward(m) { + return vec3.fromValues(m[8], m[9], m[10]); +} +function quatToAxisAngle(q) { + const normalized = quat.normalize(quat.create(), q); + const w = Math.max(-1, Math.min(1, normalized[3])); + const angle = 2 * Math.acos(w); + const s = Math.sqrt(Math.max(0, 1 - w * w)); + if (s < 0.00001) { + return { + axis: vec3.fromValues(1, 0, 0), + angle: 0, + }; + } + return { + axis: vec3.fromValues(normalized[0] / s, normalized[1] / s, normalized[2] / s), + angle, + }; } - -// --- Main --- const DEG2RAD = Math.PI / 180; const angleA = 37 * DEG2RAD; const angleB = -23 * DEG2RAD; const angleC = 71 * DEG2RAD; -const matA = mat4FromRows( +const matA = mat4.fromValues( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0 ); -const matB = mat4FromRows( - 0.5, -1.0, 2.0, 0.25, - 1.5, 0.75, -0.5, 2.0, - -3.0, 4.0, 1.25, -2.5, - 0.0, 1.0, -1.5, 3.0 +const matB = mat4.fromValues( + -10.0, -20.0, -30.0, -40.0, + 50.0, 60.0, 70.0, 80.0, + 90.0, 100.0, 110.0, 120.0, + 130.0, 140.0, 150.0, 160.0 ); const vecA = vec3.fromValues(1.25, -2.5, 3.75); const vecB = vec4.fromValues(1.25, -2.5, 3.75, 1.0); @@ -165,17 +187,24 @@ const hardQuat = quatFromAxisAngle(hardAxis, hardAngle); const hardMat = mat4FromQuat(hardQuat); const rotationOnlyM = rotationOnlyCopy(transformM); -const basisRight = vec3.fromValues(1, 0, 0); -const basisUp = vec3.fromValues(0, 1, 0); -const basisForward = vec3.fromValues(0, 0, 1); +const pureRotationQuat = quatFromMat(pureRotationM); heading("dump"); -appendLine("notes: matrices are printed in raw in-memory order, four scalars per line"); +appendLine("notes: matrices are printed in common column-major order"); -heading("matrix constructors and composition"); +heading("matrix basics"); dumpMat4("identity", mat4.create()); dumpMat4("matrix_a", matA); dumpMat4("matrix_b", matB); + +heading("matrix multiply"); +dumpMat4("matrix_a * matrix_b", mul(matA, matB)); +dumpMat4("matrix_b * matrix_a", mul(matB, matA)); + +heading("element access [row, col]"); +appendLine("N/A"); + +heading("matrix constructors and composition"); dumpMat4("scale", scaleM); dumpMat4("translate", translateM); dumpMat4("rotate_x", rotateXM); @@ -184,12 +213,6 @@ dumpMat4("rotate_z", rotateZM); dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM); dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM); -heading("matrix multiply"); -dumpMat4("lhs", matA); -dumpMat4("rhs", matB); -dumpMat4("lhs * rhs", mul(matA, matB)); -dumpMat4("rhs * lhs", mul(matB, matA)); - heading("matrix vector multiply"); dumpVec3("vec3_input", vecA); dumpVec4("vec4_input", vecB); @@ -226,38 +249,32 @@ dumpVec3("quat_rotate(from_axis_angle, input)", quatRotateVec3(axisQuat, vecA)); dumpVec3("quat_z * input", quatRotateVec3(quatZ, vecA)); heading("matrix quaternion roundtrip"); -const pureRotationQuat = quatFromMat(pureRotationM); dumpQuat("pure_rotation.quat", pureRotationQuat); dumpMat4("pure_rotation", pureRotationM); dumpMat4("pure_rotation.quat.mat4", mat4FromQuat(pureRotationQuat)); dumpMat4("transform.rotation_only", rotationOnlyM); dumpQuat("transform.rotation_only.quat", quatFromMat(rotationOnlyM)); -const axisMatQuat = quatFromMat(axisMat); -dumpQuat("axis_mat.quat", axisMatQuat); -dumpMat4("axis_mat.quat.mat4", mat4FromQuat(axisMatQuat)); +dumpQuat("axis_mat.quat", quatFromMat(axisMat)); +dumpMat4("axis_mat.quat.mat4", mat4FromQuat(quatFromMat(axisMat))); appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero"); dumpQuat("hard_decomp.quat_original", hardQuat); dumpQuat("hard_decomp.quat_from_mat", quatFromMat(hardMat)); dumpMat4("hard_decomp.mat4", hardMat); dumpMat4("hard_decomp.quat_from_mat.mat4", mat4FromQuat(quatFromMat(hardMat))); -const DEG2RAD_60 = 60 * Math.PI / 180; - -heading("perspective matrix"); -appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0"); -dumpMat4("perspective", mat4.perspective(mat4.create(), DEG2RAD_60, 1.5, 0.1, 100.0)); - -heading("ortho matrix"); -appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0"); -dumpMat4("ortho", mat4.ortho(mat4.create(), -10, 10, -7.5, 7.5, 0.1, 100.0)); - heading("lookAt matrix"); appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)"); -dumpMat4("lookAt", mat4.lookAt(mat4.create(), - vec3.fromValues(5, 5, 5), vec3.fromValues(0, 0, 0), vec3.fromValues(0, 1, 0))); +dumpMat4("lookAt", mat4.lookAt( + mat4.create(), + vec3.fromValues(5, 5, 5), + vec3.fromValues(0, 0, 0), + vec3.fromValues(0, 1, 0) +)); heading("euler angle decomposition"); -appendLine("N/A"); +appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians"); +dumpVec3("pure_rotation.quat.euler", quatToEulerXYZ(pureRotationQuat)); +dumpVec3("from_axis_angle.euler", quatToEulerXYZ(axisQuat)); heading("matrix inverse"); dumpMat4("transform.inverse", mat4.invert(mat4.create(), transformM)); @@ -285,30 +302,39 @@ const fromA = vec3.fromValues(1, 0, 0); const fromB = vec3.fromValues(0, 1, 0); const fromC = vec3.normalize(vec3.create(), vec3.fromValues(1, 2, -1)); const fromD = vec3.normalize(vec3.create(), vec3.fromValues(-1, 0.5, 2)); -dumpQuat("from_x_to_y", quat.rotationTo(quat.create(), fromA, fromB)); -dumpQuat("from_c_to_d", quat.rotationTo(quat.create(), fromC, fromD)); -dumpVec3("verify_x_to_y", vec3.transformQuat(vec3.create(), fromA, quat.rotationTo(quat.create(), fromA, fromB))); -dumpVec3("verify_c_to_d", vec3.transformQuat(vec3.create(), fromC, quat.rotationTo(quat.create(), fromC, fromD))); +const fromXToY = quat.rotationTo(quat.create(), fromA, fromB); +const fromCToD = quat.rotationTo(quat.create(), fromC, fromD); +dumpQuat("from_x_to_y", fromXToY); +dumpQuat("from_c_to_d", fromCToD); +dumpVec3("verify_x_to_y", vec3.transformQuat(vec3.create(), fromA, fromXToY)); +dumpVec3("verify_c_to_d", vec3.transformQuat(vec3.create(), fromC, fromCToD)); heading("quaternion inverse"); dumpQuat("from_axis_angle.inverse", quat.invert(quat.create(), axisQuat)); dumpQuat("verify_q_mul_qinv", quat.multiply(quat.create(), axisQuat, quat.invert(quat.create(), axisQuat))); heading("quaternion to axis-angle"); -const aaAxis1 = vec3.create(); -const aaAngle1 = quat.getAxisAngle(aaAxis1, axisQuat); -const aaAxis2 = vec3.create(); -const aaAngle2 = quat.getAxisAngle(aaAxis2, quatXYZ); -dumpVec3("from_axis_angle.axis", aaAxis1); -dumpScalar("from_axis_angle.angle", aaAngle1); -dumpVec3("quat_xyz.axis", aaAxis2); -dumpScalar("quat_xyz.angle", aaAngle2); +const axisAngle1 = quatToAxisAngle(axisQuat); +const axisAngle2 = quatToAxisAngle(quatXYZ); +dumpVec3("from_axis_angle.axis", axisAngle1.axis); +dumpScalar("from_axis_angle.angle", axisAngle1.angle); +dumpVec3("quat_xyz.axis", axisAngle2.axis); +dumpScalar("quat_xyz.angle", axisAngle2.angle); -heading("basis directions"); -appendLine("N/A"); +const DEG2RAD_60 = 60 * DEG2RAD; -heading("element access [row,col]"); -appendLine("N/A"); +heading("perspective matrix"); +appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0"); +dumpMat4("perspective", mat4.perspective(mat4.create(), DEG2RAD_60, 1.5, 0.1, 100.0)); + +heading("ortho matrix"); +appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0"); +dumpMat4("ortho", mat4.ortho(mat4.create(), -10, 10, -7.5, 7.5, 0.1, 100.0)); + +heading("basis directions"); +dumpVec3("forward", basisForward(matA)); +dumpVec3("right", basisRight(matA)); +dumpVec3("up", basisUp(matA)); writeFileSync(OutputPath, lines.join("\n") + "\n"); console.log("Wrote", OutputPath); diff --git a/conformance/dump_glmatrix.txt b/conformance/dump_glmatrix.txt index a50df5d..21d5433 100644 --- a/conformance/dump_glmatrix.txt +++ b/conformance/dump_glmatrix.txt @@ -1,7 +1,7 @@ == dump == -notes: matrices are printed in raw in-memory order, four scalars per line +notes: matrices are printed in common column-major order -== matrix constructors and composition == +== matrix basics == identity: [ +001.000 +000.000 +000.000 +000.000 @@ -18,11 +18,32 @@ matrix_a: ] matrix_b: [ - +000.500 +001.500 -003.000 +000.000 - -001.000 +000.750 +004.000 +001.000 - +002.000 -000.500 +001.250 -001.500 - +000.250 +002.000 -002.500 +003.000 + -010.000 +050.000 +090.000 +130.000 + -020.000 +060.000 +100.000 +140.000 + -030.000 +070.000 +110.000 +150.000 + -040.000 +080.000 +120.000 +160.000 +] + +== matrix multiply == +matrix_a * matrix_b: +[ + -900.000 +2020.000 +3140.000 +4260.000 + -1000.000 +2280.000 +3560.000 +4840.000 + -1100.000 +2540.000 +3980.000 +5420.000 + -1200.000 +2800.000 +4400.000 +6000.000 +] +matrix_b * matrix_a: +[ + +880.000 +1920.000 +2960.000 +4000.000 + +960.000 +2080.000 +3200.000 +4320.000 + +1040.000 +2240.000 +3440.000 +4640.000 + +1120.000 +2400.000 +3680.000 +4960.000 ] + +== element access [row, col] == +N/A + +== matrix constructors and composition == scale: [ +002.000 +000.000 +000.000 +000.000 @@ -32,75 +53,45 @@ scale: ] translate: [ - +001.000 +000.000 +000.000 +000.000 - +000.000 +001.000 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +010.000 +020.000 +030.000 +001.000 + +001.000 +000.000 +000.000 +010.000 + +000.000 +001.000 +000.000 +020.000 + +000.000 +000.000 +001.000 +030.000 + +000.000 +000.000 +000.000 +001.000 ] rotate_x: [ +001.000 +000.000 +000.000 +000.000 - +000.000 +000.799 +000.602 +000.000 - +000.000 -000.602 +000.799 +000.000 + +000.000 +000.799 -000.602 +000.000 + +000.000 +000.602 +000.799 +000.000 +000.000 +000.000 +000.000 +001.000 ] rotate_y: [ - +000.921 +000.000 +000.391 +000.000 + +000.921 +000.000 -000.391 +000.000 +000.000 +001.000 +000.000 +000.000 - -000.391 +000.000 +000.921 +000.000 + +000.391 +000.000 +000.921 +000.000 +000.000 +000.000 +000.000 +001.000 ] rotate_z: [ - +000.326 +000.946 +000.000 +000.000 - -000.946 +000.326 +000.000 +000.000 + +000.326 -000.946 +000.000 +000.000 + +000.946 +000.326 +000.000 +000.000 +000.000 +000.000 +001.000 +000.000 +000.000 +000.000 +000.000 +001.000 ] pure_rotation = rotate_z * rotate_y * rotate_x: [ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform = translate * rotate_z * rotate_y * rotate_x * scale: [ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 - +010.000 +020.000 +030.000 +001.000 -] - -== matrix multiply == -lhs: -[ - +001.000 +005.000 +009.000 +013.000 - +002.000 +006.000 +010.000 +014.000 - +003.000 +007.000 +011.000 +015.000 - +004.000 +008.000 +012.000 +016.000 -] -rhs: -[ - +000.500 +001.500 -003.000 +000.000 - -001.000 +000.750 +004.000 +001.000 - +002.000 -000.500 +001.250 -001.500 - +000.250 +002.000 -002.500 +003.000 -] -lhs * rhs: -[ - -005.500 -009.500 -013.500 -017.500 - +016.500 +035.500 +054.500 +073.500 - -001.250 +003.750 +008.750 +013.750 - +008.750 +019.750 +030.750 +041.750 -] -rhs * lhs: -[ - +016.750 +026.750 -004.250 +030.500 - +018.500 +030.500 -004.500 +033.000 - +020.250 +034.250 -004.750 +035.500 - +022.000 +038.000 -005.000 +038.000 + +000.599 -002.495 +001.870 +010.000 + +001.741 +000.113 -001.964 +020.000 + +000.781 +001.662 +002.941 +030.000 + +000.000 +000.000 +000.000 +001.000 ] == matrix vector multiply == @@ -121,9 +112,9 @@ axis_angle_radians: +000.838 from_axis_angle: <+000.109, +000.217, -000.326, +000.914> from_axis_angle.mat4: [ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -135,16 +126,16 @@ quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> quat_xy.mat4: [ - +000.921 -000.235 +000.312 +000.000 - +000.000 +000.799 +000.602 +000.000 - -000.391 -000.554 +000.735 +000.000 + +000.921 +000.000 -000.391 +000.000 + -000.235 +000.799 -000.554 +000.000 + +000.312 +000.602 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] quat_xyz.mat4: [ - +000.300 +000.679 +000.671 +000.000 - -000.870 +000.482 -000.099 +000.000 - -000.391 -000.554 +000.735 +000.000 + +000.300 -000.870 -000.391 +000.000 + +000.679 +000.482 -000.554 +000.000 + +000.671 -000.099 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -160,32 +151,32 @@ quat_z * input: <+002.771, +000.368, +003.750> pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> pure_rotation: [ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] pure_rotation.quat.mat4: [ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform.rotation_only: [ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 + +000.599 -002.495 +001.870 +000.000 + +001.741 +000.113 -001.964 +000.000 + +000.781 +001.662 +002.941 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> axis_mat.quat.mat4: [ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero @@ -193,65 +184,47 @@ hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> hard_decomp.mat4: [ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 + -000.843 -000.423 +000.332 +000.000 + -000.144 -000.418 -000.897 +000.000 + +000.518 -000.804 +000.291 +000.000 +000.000 +000.000 +000.000 +001.000 ] hard_decomp.quat_from_mat.mat4: [ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 + -000.843 -000.423 +000.332 +000.000 + -000.144 -000.418 -000.897 +000.000 + +000.518 -000.804 +000.291 +000.000 +000.000 +000.000 +000.000 +001.000 ] -== perspective matrix == -notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 -perspective: -[ - +001.155 +000.000 +000.000 +000.000 - +000.000 +001.732 +000.000 +000.000 - +000.000 +000.000 -001.002 -001.000 - +000.000 +000.000 -000.200 +000.000 -] - -== ortho matrix == -notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 -ortho: -[ - +000.100 +000.000 +000.000 +000.000 - +000.000 +000.133 +000.000 +000.000 - +000.000 +000.000 -000.020 +000.000 - +000.000 +000.000 -001.002 +001.000 -] - == lookAt matrix == notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) lookAt: [ - +000.707 -000.408 +000.577 +000.000 - +000.000 +000.816 +000.577 +000.000 - -000.707 -000.408 +000.577 +000.000 - +000.000 +000.000 -008.660 +001.000 + +000.707 +000.000 -000.707 +000.000 + -000.408 +000.816 -000.408 +000.000 + +000.577 +000.577 +000.577 -008.660 + +000.000 +000.000 +000.000 +001.000 ] == euler angle decomposition == -N/A +notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians +pure_rotation.quat.euler: <+000.646, -000.401, +001.239> +from_axis_angle.euler: <+000.064, +000.487, -000.670> == matrix inverse == transform.inverse: [ - +000.150 -000.277 +000.117 +000.000 - +000.435 +000.013 -000.123 +000.000 - +000.195 +000.185 +000.184 +000.000 - -016.063 -003.019 -004.227 +001.000 + +000.150 +000.435 +000.195 -016.063 + -000.277 +000.013 +000.185 -003.019 + +000.117 -000.123 +000.184 -004.227 + +000.000 +000.000 +000.000 +001.000 ] pure_rotation.inverse: [ - +000.300 -000.832 +000.467 +000.000 - +000.870 +000.038 -000.491 +000.000 - +000.391 +000.554 +000.735 +000.000 + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -284,8 +257,27 @@ from_axis_angle.angle: +000.838 quat_xyz.axis: <+000.235, -000.549, +000.802> quat_xyz.angle: +001.309 -== basis directions == -N/A +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.002 -000.200 + +000.000 +000.000 -001.000 +000.000 +] -== element access [row,col] == -N/A +== ortho matrix == +notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 +ortho: +[ + +000.100 +000.000 +000.000 +000.000 + +000.000 +000.133 +000.000 +000.000 + +000.000 +000.000 -000.020 -001.002 + +000.000 +000.000 +000.000 +001.000 +] + +== basis directions == +forward: <+009.000, +010.000, +011.000> +right: <+001.000, +002.000, +003.000> +up: <+005.000, +006.000, +007.000> diff --git a/conformance/dump_glsl.comp b/conformance/dump_glsl.comp index 1776e19..8ebc2f5 100644 --- a/conformance/dump_glsl.comp +++ b/conformance/dump_glsl.comp @@ -18,19 +18,19 @@ mat4 identityM() { mat4 matA() { return mat4( - vec4(1.0, 5.0, 9.0, 13.0), - vec4(2.0, 6.0, 10.0, 14.0), - vec4(3.0, 7.0, 11.0, 15.0), - vec4(4.0, 8.0, 12.0, 16.0) + vec4(1.0, 2.0, 3.0, 4.0), + vec4(5.0, 6.0, 7.0, 8.0), + vec4(9.0, 10.0, 11.0, 12.0), + vec4(13.0, 14.0, 15.0, 16.0) ); } mat4 matB() { return mat4( - vec4(0.5, 1.5, -3.0, 0.0), - vec4(-1.0, 0.75, 4.0, 1.0), - vec4(2.0, -0.5, 1.25, -1.5), - vec4(0.25, 2.0, -2.5, 3.0) + vec4(-10.0, -20.0, -30.0, -40.0), + vec4(50.0, 60.0, 70.0, 80.0), + vec4(90.0, 100.0, 110.0, 120.0), + vec4(130.0, 140.0, 150.0, 160.0) ); } @@ -367,6 +367,7 @@ vec3 quatToAngles(vec4 q) { } void main() { - vec4 value = vec4(transformM()[3][3], 0.0, 0.0, 0.0); - imageStore(outputBuffer, 0, value); + uint index = gl_GlobalInvocationID.x; + mat4 value = orthoM(-10.0, 10.0, -7.5, 7.5, 0.1, 100.0); + imageStore(outputBuffer, int(index), value[int(index)]); } diff --git a/conformance/dump_glsl.nim b/conformance/dump_glsl.nim index d6a606d..a82d614 100644 --- a/conformance/dump_glsl.nim +++ b/conformance/dump_glsl.nim @@ -53,14 +53,10 @@ proc dumpQuat(lines: var seq[string], label: string, value: openArray[float32]) proc dumpMat4(lines: var seq[string], label: string, value: openArray[float32]) = lines.appendLine(label & ":") lines.appendLine("[") - for offset in countup(0, 12, 4): - lines.appendLine( - " " & - fmt(value[offset + 0]) & " " & - fmt(value[offset + 1]) & " " & - fmt(value[offset + 2]) & " " & - fmt(value[offset + 3]) - ) + lines.appendLine(" " & fmt(value[0]) & " " & fmt(value[4]) & " " & fmt(value[8]) & " " & fmt(value[12])) + lines.appendLine(" " & fmt(value[1]) & " " & fmt(value[5]) & " " & fmt(value[9]) & " " & fmt(value[13])) + lines.appendLine(" " & fmt(value[2]) & " " & fmt(value[6]) & " " & fmt(value[10]) & " " & fmt(value[14])) + lines.appendLine(" " & fmt(value[3]) & " " & fmt(value[7]) & " " & fmt(value[11]) & " " & fmt(value[15])) lines.appendLine("]") proc heading(lines: var seq[string], title: string) = @@ -90,19 +86,19 @@ mat4 identityM() { mat4 matA() { return mat4( - vec4(1.0, 5.0, 9.0, 13.0), - vec4(2.0, 6.0, 10.0, 14.0), - vec4(3.0, 7.0, 11.0, 15.0), - vec4(4.0, 8.0, 12.0, 16.0) + vec4(1.0, 2.0, 3.0, 4.0), + vec4(5.0, 6.0, 7.0, 8.0), + vec4(9.0, 10.0, 11.0, 12.0), + vec4(13.0, 14.0, 15.0, 16.0) ); } mat4 matB() { return mat4( - vec4(0.5, 1.5, -3.0, 0.0), - vec4(-1.0, 0.75, 4.0, 1.0), - vec4(2.0, -0.5, 1.25, -1.5), - vec4(0.25, 2.0, -2.5, 3.0) + vec4(-10.0, -20.0, -30.0, -40.0), + vec4(50.0, 60.0, 70.0, 80.0), + vec4(90.0, 100.0, 110.0, 120.0), + vec4(130.0, 140.0, 150.0, 160.0) ); } @@ -508,12 +504,36 @@ proc main() = var lines: seq[string] lines.heading("dump") - lines.appendLine("notes: matrices are printed in raw in-memory order, four scalars per line") + lines.appendLine("notes: matrices are printed in common column-major order") - lines.heading("matrix constructors and composition") + lines.heading("matrix basics") lines.dumpMat4("identity", runMatrix("identityM()")) lines.dumpMat4("matrix_a", runMatrix("matA()")) lines.dumpMat4("matrix_b", runMatrix("matB()")) + + lines.heading("matrix multiply") + lines.dumpMat4("matrix_a * matrix_b", runMatrix("matA() * matB()")) + lines.dumpMat4("matrix_b * matrix_a", runMatrix("matB() * matA()")) + + lines.heading("element access [row, col]") + lines.dumpScalar("transform[0, 0]", runScalar("vec4(matA()[0][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[0, 1]", runScalar("vec4(matA()[0][1], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[0, 2]", runScalar("vec4(matA()[0][2], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[0, 3]", runScalar("vec4(matA()[0][3], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[1, 0]", runScalar("vec4(matA()[1][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[1, 1]", runScalar("vec4(matA()[1][1], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[1, 2]", runScalar("vec4(matA()[1][2], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[1, 3]", runScalar("vec4(matA()[1][3], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[2, 0]", runScalar("vec4(matA()[2][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[2, 1]", runScalar("vec4(matA()[2][1], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[2, 2]", runScalar("vec4(matA()[2][2], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[2, 3]", runScalar("vec4(matA()[2][3], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[3, 0]", runScalar("vec4(matA()[3][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[3, 1]", runScalar("vec4(matA()[3][1], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[3, 2]", runScalar("vec4(matA()[3][2], 0.0, 0.0, 0.0)")) + lines.dumpScalar("transform[3, 3]", runScalar("vec4(matA()[3][3], 0.0, 0.0, 0.0)")) + + lines.heading("matrix constructors and composition") lines.dumpMat4("scale", runMatrix("scaleM()")) lines.dumpMat4("translate", runMatrix("translateM()")) lines.dumpMat4("rotate_x", runMatrix("rotateXM()")) @@ -522,12 +542,6 @@ proc main() = lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", runMatrix("pureRotationM()")) lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", runMatrix("transformM()")) - lines.heading("matrix multiply") - lines.dumpMat4("lhs", runMatrix("matA()")) - lines.dumpMat4("rhs", runMatrix("matB()")) - lines.dumpMat4("lhs * rhs", runMatrix("matA() * matB()")) - lines.dumpMat4("rhs * lhs", runMatrix("matB() * matA()")) - lines.heading("matrix vector multiply") lines.dumpVec3("vec3_input", runVec3("vecA()")) lines.dumpVec4("vec4_input", runVec4("vecB()")) @@ -577,14 +591,6 @@ proc main() = lines.dumpMat4("hard_decomp.mat4", runMatrix("hardMat()")) lines.dumpMat4("hard_decomp.quat_from_mat.mat4", runMatrix("quatMat4(mat4Quat(hardMat()))")) - lines.heading("perspective matrix") - lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - lines.dumpMat4("perspective", runMatrix("perspectiveM(60.0, 1.5, 0.1, 100.0)")) - - lines.heading("ortho matrix") - lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") - lines.dumpMat4("ortho", runMatrix("orthoM(-10.0, 10.0, -7.5, 7.5, 0.1, 100.0)")) - lines.heading("lookAt matrix") lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") lines.dumpMat4("lookAt", runMatrix("lookAtM(vec3(5.0, 5.0, 5.0), vec3(0.0), vec3(0.0, 1.0, 0.0))")) @@ -594,15 +600,6 @@ proc main() = lines.dumpVec3("pure_rotation.quat.euler", runVec3("quatToAngles(mat4Quat(pureRotationM()))")) lines.dumpVec3("from_axis_angle.euler", runVec3("quatToAngles(axisQuat())")) - lines.heading("basis directions") - lines.appendLine("notes: library-specific, N/A for libraries without canonical basis helpers") - lines.dumpVec3("canonical_right", runVec3("vec3(1.0, 0.0, 0.0)")) - lines.dumpVec3("canonical_up", runVec3("vec3(0.0, 1.0, 0.0)")) - lines.dumpVec3("canonical_forward", runVec3("vec3(0.0, 0.0, 1.0)")) - lines.dumpVec3("quat_z.right", runVec3("quatRotate(quatZ(), vec3(1.0, 0.0, 0.0))")) - lines.dumpVec3("quat_z.up", runVec3("quatRotate(quatZ(), vec3(0.0, 1.0, 0.0))")) - lines.dumpVec3("quat_z.forward", runVec3("quatRotate(quatZ(), vec3(0.0, 0.0, 1.0))")) - lines.heading("matrix inverse") lines.dumpMat4("transform.inverse", runMatrix("inverse(transformM())")) lines.dumpMat4("pure_rotation.inverse", runMatrix("inverse(pureRotationM())")) @@ -636,17 +633,16 @@ proc main() = lines.dumpVec3("quat_xyz.axis", runVec3("quatAxisOnly(quatMultiply(quatMultiply(quatX(), quatY()), quatZ()))")) lines.dumpScalar("quat_xyz.angle", runScalar("vec4(quatAngleOnly(quatMultiply(quatMultiply(quatX(), quatY()), quatZ())), 0.0, 0.0, 0.0)")) - lines.heading("element access [row,col]") - lines.appendLine("notes: [row,col] in math convention, element (i,j) = row i, col j") - lines.dumpScalar("transform[0,0]", runScalar("vec4(transformM()[0][0], 0.0, 0.0, 0.0)")) - lines.dumpScalar("transform[0,1]", runScalar("vec4(transformM()[1][0], 0.0, 0.0, 0.0)")) - lines.dumpScalar("transform[0,2]", runScalar("vec4(transformM()[2][0], 0.0, 0.0, 0.0)")) - lines.dumpScalar("transform[0,3]", runScalar("vec4(transformM()[3][0], 0.0, 0.0, 0.0)")) - lines.dumpScalar("transform[1,0]", runScalar("vec4(transformM()[0][1], 0.0, 0.0, 0.0)")) - lines.dumpScalar("transform[1,3]", runScalar("vec4(transformM()[3][1], 0.0, 0.0, 0.0)")) - lines.dumpScalar("transform[2,0]", runScalar("vec4(transformM()[0][2], 0.0, 0.0, 0.0)")) - lines.dumpScalar("transform[2,3]", runScalar("vec4(transformM()[3][2], 0.0, 0.0, 0.0)")) - lines.dumpScalar("transform[3,3]", runScalar("vec4(transformM()[3][3], 0.0, 0.0, 0.0)")) + lines.heading("perspective matrix") + lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") + lines.dumpMat4("perspective", runMatrix("perspectiveM(60.0, 1.5, 0.1, 100.0)")) + + lines.heading("ortho matrix") + lines.appendLine("notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0") + lines.dumpMat4("ortho", runMatrix("orthoM(-10.0, 10.0, -7.5, 7.5, 0.1, 100.0)")) + + lines.heading("basis directions") + lines.appendLine("N/A") writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/conformance/dump_glsl.txt b/conformance/dump_glsl.txt index 346e180..762ae1e 100644 --- a/conformance/dump_glsl.txt +++ b/conformance/dump_glsl.txt @@ -1,7 +1,7 @@ == dump == -notes: matrices are printed in raw in-memory order, four scalars per line +notes: matrices are printed in common column-major order -== matrix constructors and composition == +== matrix basics == identity: [ +001.000 +000.000 +000.000 +000.000 @@ -18,11 +18,47 @@ matrix_a: ] matrix_b: [ - +000.500 +001.500 -003.000 +000.000 - -001.000 +000.750 +004.000 +001.000 - +002.000 -000.500 +001.250 -001.500 - +000.250 +002.000 -002.500 +003.000 + -010.000 +050.000 +090.000 +130.000 + -020.000 +060.000 +100.000 +140.000 + -030.000 +070.000 +110.000 +150.000 + -040.000 +080.000 +120.000 +160.000 +] + +== matrix multiply == +matrix_a * matrix_b: +[ + -900.000 +2020.000 +3140.000 +4260.000 + -1000.000 +2280.000 +3560.000 +4840.000 + -1100.000 +2540.000 +3980.000 +5420.000 + -1200.000 +2800.000 +4400.000 +6000.000 +] +matrix_b * matrix_a: +[ + +880.000 +1920.000 +2960.000 +4000.000 + +960.000 +2080.000 +3200.000 +4320.000 + +1040.000 +2240.000 +3440.000 +4640.000 + +1120.000 +2400.000 +3680.000 +4960.000 ] + +== element access [row, col] == +transform[0, 0]: +001.000 +transform[0, 1]: +002.000 +transform[0, 2]: +003.000 +transform[0, 3]: +004.000 +transform[1, 0]: +005.000 +transform[1, 1]: +006.000 +transform[1, 2]: +007.000 +transform[1, 3]: +008.000 +transform[2, 0]: +009.000 +transform[2, 1]: +010.000 +transform[2, 2]: +011.000 +transform[2, 3]: +012.000 +transform[3, 0]: +013.000 +transform[3, 1]: +014.000 +transform[3, 2]: +015.000 +transform[3, 3]: +016.000 + +== matrix constructors and composition == scale: [ +002.000 +000.000 +000.000 +000.000 @@ -32,75 +68,45 @@ scale: ] translate: [ - +001.000 +000.000 +000.000 +000.000 - +000.000 +001.000 +000.000 +000.000 - +000.000 +000.000 +001.000 +000.000 - +010.000 +020.000 +030.000 +001.000 + +001.000 +000.000 +000.000 +010.000 + +000.000 +001.000 +000.000 +020.000 + +000.000 +000.000 +001.000 +030.000 + +000.000 +000.000 +000.000 +001.000 ] rotate_x: [ +001.000 +000.000 +000.000 +000.000 - +000.000 +000.799 +000.602 +000.000 - +000.000 -000.602 +000.799 +000.000 + +000.000 +000.799 -000.602 +000.000 + +000.000 +000.602 +000.799 +000.000 +000.000 +000.000 +000.000 +001.000 ] rotate_y: [ - +000.921 +000.000 +000.391 +000.000 + +000.921 +000.000 -000.391 +000.000 +000.000 +001.000 +000.000 +000.000 - -000.391 +000.000 +000.921 +000.000 + +000.391 +000.000 +000.921 +000.000 +000.000 +000.000 +000.000 +001.000 ] rotate_z: [ - +000.326 +000.946 +000.000 +000.000 - -000.946 +000.326 +000.000 +000.000 + +000.326 -000.946 +000.000 +000.000 + +000.946 +000.326 +000.000 +000.000 +000.000 +000.000 +001.000 +000.000 +000.000 +000.000 +000.000 +001.000 ] pure_rotation = rotate_z * rotate_y * rotate_x: [ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform = translate * rotate_z * rotate_y * rotate_x * scale: [ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 - +010.000 +020.000 +030.000 +001.000 -] - -== matrix multiply == -lhs: -[ - +001.000 +005.000 +009.000 +013.000 - +002.000 +006.000 +010.000 +014.000 - +003.000 +007.000 +011.000 +015.000 - +004.000 +008.000 +012.000 +016.000 -] -rhs: -[ - +000.500 +001.500 -003.000 +000.000 - -001.000 +000.750 +004.000 +001.000 - +002.000 -000.500 +001.250 -001.500 - +000.250 +002.000 -002.500 +003.000 -] -lhs * rhs: -[ - -005.500 -009.500 -013.500 -017.500 - +016.500 +035.500 +054.500 +073.500 - -001.250 +003.750 +008.750 +013.750 - +008.750 +019.750 +030.750 +041.750 -] -rhs * lhs: -[ - +016.750 +026.750 -004.250 +030.500 - +018.500 +030.500 -004.500 +033.000 - +020.250 +034.250 -004.750 +035.500 - +022.000 +038.000 -005.000 +038.000 + +000.599 -002.495 +001.870 +010.000 + +001.741 +000.113 -001.964 +020.000 + +000.781 +001.662 +002.941 +030.000 + +000.000 +000.000 +000.000 +001.000 ] == matrix vector multiply == @@ -121,9 +127,9 @@ axis_angle_radians: +000.838 from_axis_angle: <+000.109, +000.217, -000.326, +000.914> from_axis_angle.mat4: [ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -135,16 +141,16 @@ quat_multiply(quat_x, quat_y): <+000.311, -000.189, -000.063, +000.929> quat_multiply(quat_multiply(quat_x, quat_y), quat_z): <+000.143, -000.334, +000.488, +000.793> quat_xy.mat4: [ - +000.921 -000.235 +000.312 +000.000 - +000.000 +000.799 +000.602 +000.000 - -000.391 -000.554 +000.735 +000.000 + +000.921 +000.000 -000.391 +000.000 + -000.235 +000.799 -000.554 +000.000 + +000.312 +000.602 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] quat_xyz.mat4: [ - +000.300 +000.679 +000.671 +000.000 - -000.870 +000.482 -000.099 +000.000 - -000.391 -000.554 +000.735 +000.000 + +000.300 -000.870 -000.391 +000.000 + +000.679 +000.482 -000.554 +000.000 + +000.671 -000.099 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -160,32 +166,32 @@ quat_z * input: <+002.771, +000.368, +003.750> pure_rotation.quat: <+000.363, +000.027, +000.591, +000.720> pure_rotation: [ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] pure_rotation.quat.mat4: [ - +000.300 +000.870 +000.391 +000.000 - -000.832 +000.038 +000.554 +000.000 - +000.467 -000.491 +000.735 +000.000 + +000.300 -000.832 +000.467 +000.000 + +000.870 +000.038 -000.491 +000.000 + +000.391 +000.554 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform.rotation_only: [ - +000.599 +001.741 +000.781 +000.000 - -002.495 +000.113 +001.662 +000.000 - +001.870 -001.964 +002.941 +000.000 + +000.599 -002.495 +001.870 +000.000 + +001.741 +000.113 -001.964 +000.000 + +000.781 +001.662 +002.941 +000.000 +000.000 +000.000 +000.000 +001.000 ] transform.rotation_only.quat: <+000.840, +000.252, +000.982, +001.079> axis_mat.quat: <+000.109, +000.217, -000.326, +000.914> axis_mat.quat.mat4: [ - +000.693 -000.549 -000.468 +000.000 - +000.643 +000.764 +000.057 +000.000 - +000.326 -000.340 +000.882 +000.000 + +000.693 +000.643 +000.326 +000.000 + -000.549 +000.764 -000.340 +000.000 + -000.468 +000.057 +000.882 +000.000 +000.000 +000.000 +000.000 +001.000 ] hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero @@ -193,47 +199,27 @@ hard_decomp.quat_original: <+000.266, -000.532, +000.799, +000.087> hard_decomp.quat_from_mat: <+000.266, -000.532, +000.799, +000.087> hard_decomp.mat4: [ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 + -000.843 -000.423 +000.332 +000.000 + -000.144 -000.418 -000.897 +000.000 + +000.518 -000.804 +000.291 +000.000 +000.000 +000.000 +000.000 +001.000 ] hard_decomp.quat_from_mat.mat4: [ - -000.843 -000.144 +000.518 +000.000 - -000.423 -000.418 -000.804 +000.000 - +000.332 -000.897 +000.291 +000.000 + -000.843 -000.423 +000.332 +000.000 + -000.144 -000.418 -000.897 +000.000 + +000.518 -000.804 +000.291 +000.000 +000.000 +000.000 +000.000 +001.000 ] -== perspective matrix == -notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 -perspective: -[ - +001.155 +000.000 +000.000 +000.000 - +000.000 +001.732 +000.000 +000.000 - +000.000 +000.000 -001.002 -001.000 - +000.000 +000.000 -000.200 +000.000 -] - -== ortho matrix == -notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 -ortho: -[ - +000.100 +000.000 +000.000 +000.000 - +000.000 +000.133 +000.000 +000.000 - +000.000 +000.000 -000.020 +000.000 - +000.000 +000.000 -001.002 +001.000 -] - == lookAt matrix == notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0) lookAt: [ - +000.707 -000.408 +000.577 +000.000 - +000.000 +000.816 +000.577 +000.000 - -000.707 -000.408 +000.577 +000.000 - +000.000 +000.000 -008.660 +001.000 + +000.707 +000.000 -000.707 +000.000 + -000.408 +000.816 -000.408 +000.000 + +000.577 +000.577 +000.577 -008.660 + +000.000 +000.000 +000.000 +001.000 ] == euler angle decomposition == @@ -241,28 +227,19 @@ notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians pure_rotation.quat.euler: <+000.646, -000.401, +001.239> from_axis_angle.euler: <+000.064, +000.487, -000.670> -== basis directions == -notes: library-specific, N/A for libraries without canonical basis helpers -canonical_right: <+001.000, +000.000, +000.000> -canonical_up: <+000.000, +001.000, +000.000> -canonical_forward: <+000.000, +000.000, +001.000> -quat_z.right: <+000.326, +000.946, +000.000> -quat_z.up: <-000.946, +000.326, +000.000> -quat_z.forward: <+000.000, +000.000, +001.000> - == matrix inverse == transform.inverse: [ - +000.150 -000.277 +000.117 +000.000 - +000.435 +000.013 -000.123 +000.000 - +000.195 +000.185 +000.184 +000.000 - -016.063 -003.019 -004.227 +001.000 + +000.150 +000.435 +000.195 -016.063 + -000.277 +000.013 +000.185 -003.019 + +000.117 -000.123 +000.184 -004.227 + +000.000 +000.000 +000.000 +001.000 ] pure_rotation.inverse: [ - +000.300 -000.832 +000.467 +000.000 - +000.870 +000.038 -000.491 +000.000 - +000.391 +000.554 +000.735 +000.000 + +000.300 +000.870 +000.391 +000.000 + -000.832 +000.038 +000.554 +000.000 + +000.467 -000.491 +000.735 +000.000 +000.000 +000.000 +000.000 +001.000 ] @@ -295,14 +272,25 @@ from_axis_angle.angle: +000.838 quat_xyz.axis: <+000.235, -000.549, +000.802> quat_xyz.angle: +001.309 -== element access [row,col] == -notes: [row,col] in math convention, element (i,j) = row i, col j -transform[0,0]: +000.599 -transform[0,1]: -002.495 -transform[0,2]: +001.870 -transform[0,3]: +010.000 -transform[1,0]: +001.741 -transform[1,3]: +020.000 -transform[2,0]: +000.781 -transform[2,3]: +030.000 -transform[3,3]: +001.000 +== perspective matrix == +notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0 +perspective: +[ + +001.155 +000.000 +000.000 +000.000 + +000.000 +001.732 +000.000 +000.000 + +000.000 +000.000 -001.002 -000.200 + +000.000 +000.000 -001.000 +000.000 +] + +== ortho matrix == +notes: left=-10, right=10, bottom=-7.5, top=7.5, near=0.1, far=100.0 +ortho: +[ + +000.100 +000.000 +000.000 +000.000 + +000.000 +000.133 +000.000 +000.000 + +000.000 +000.000 -000.020 -001.002 + +000.000 +000.000 +000.000 +001.000 +] + +== basis directions == +N/A diff --git a/conformance/dump_jolt.cpp b/conformance/dump_jolt.cpp new file mode 100644 index 0000000..bdddad5 --- /dev/null +++ b/conformance/dump_jolt.cpp @@ -0,0 +1,326 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace JPH; + +static std::string CleanFloat(float value) +{ + if (std::abs(value) < 0.0000005f) + value = 0.0f; + + std::ostringstream out; + out << (value < 0.0f ? "-" : "+") + << std::setw(3) << std::setfill('0') << static_cast(std::floor(std::abs(value))) + << "." + << std::setw(3) << std::setfill('0') << static_cast(std::round((std::abs(value) - std::floor(std::abs(value))) * 1000.0f)); + + std::string result = out.str(); + if (result.size() >= 4 && result.substr(result.size() - 4) == "1000") + { + int whole = std::stoi(result.substr(1, 3)) + 1; + std::ostringstream fixed; + fixed << result[0] + << std::setw(3) << std::setfill('0') << whole + << ".000"; + return fixed.str(); + } + + return result; +} + +static void AppendLine(std::vector &lines, const std::string &line = {}) +{ + lines.push_back(line); +} + +static void DumpScalar(std::vector &lines, const std::string &label, float value) +{ + AppendLine(lines, label + ": " + CleanFloat(value)); +} + +static void DumpVec3(std::vector &lines, const std::string &label, Vec3Arg value) +{ + AppendLine(lines, label + ": <" + CleanFloat(value.GetX()) + ", " + CleanFloat(value.GetY()) + ", " + CleanFloat(value.GetZ()) + ">"); +} + +static void DumpVec4(std::vector &lines, const std::string &label, Vec4Arg value) +{ + AppendLine(lines, label + ": <" + CleanFloat(value.GetX()) + ", " + CleanFloat(value.GetY()) + ", " + CleanFloat(value.GetZ()) + ", " + CleanFloat(value.GetW()) + ">"); +} + +static void DumpQuat(std::vector &lines, const std::string &label, QuatArg value) +{ + AppendLine(lines, label + ": <" + CleanFloat(value.GetX()) + ", " + CleanFloat(value.GetY()) + ", " + CleanFloat(value.GetZ()) + ", " + CleanFloat(value.GetW()) + ">"); +} + +static void DumpMat4(std::vector &lines, const std::string &label, Mat44Arg value) +{ + static_assert(sizeof(Mat44) == sizeof(float) * 16, "Mat44 storage is expected to be 16 floats"); + float d[16]; + std::memcpy(d, &value, sizeof(d)); + + AppendLine(lines, label + ":"); + AppendLine(lines, "["); + AppendLine(lines, " " + CleanFloat(d[0]) + " " + CleanFloat(d[4]) + " " + CleanFloat(d[8]) + " " + CleanFloat(d[12])); + AppendLine(lines, " " + CleanFloat(d[1]) + " " + CleanFloat(d[5]) + " " + CleanFloat(d[9]) + " " + CleanFloat(d[13])); + AppendLine(lines, " " + CleanFloat(d[2]) + " " + CleanFloat(d[6]) + " " + CleanFloat(d[10]) + " " + CleanFloat(d[14])); + AppendLine(lines, " " + CleanFloat(d[3]) + " " + CleanFloat(d[7]) + " " + CleanFloat(d[11]) + " " + CleanFloat(d[15])); + AppendLine(lines, "]"); +} + +static void Heading(std::vector &lines, const std::string &title) +{ + if (!lines.empty()) + AppendLine(lines); + AppendLine(lines, "== " + title + " =="); +} + +static Mat44 RotationOnlyCopy(Mat44 value) +{ + value(0, 3) = 0.0f; + value(1, 3) = 0.0f; + value(2, 3) = 0.0f; + value(3, 3) = 1.0f; + return value; +} + +int main() +{ + const std::string output_path = "C:/p/vmath/conformance/dump_jolt.txt"; + + std::vector lines; + + const float angle_a = 37.0f * JPH_PI / 180.0f; + const float angle_b = -23.0f * JPH_PI / 180.0f; + const float angle_c = 71.0f * JPH_PI / 180.0f; + + const float mat_a_data[16] = { + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f + }; + Mat44 mat_a; + std::memcpy(&mat_a, mat_a_data, sizeof(mat_a)); + + const float mat_b_data[16] = { + -10.0f, -20.0f, -30.0f, -40.0f, + 50.0f, 60.0f, 70.0f, 80.0f, + 90.0f, 100.0f, 110.0f, 120.0f, + 130.0f, 140.0f, 150.0f, 160.0f + }; + Mat44 mat_b; + std::memcpy(&mat_b, mat_b_data, sizeof(mat_b)); + + const Vec3 vec_a(1.25f, -2.5f, 3.75f); + const Vec4 vec_b(1.25f, -2.5f, 3.75f, 1.0f); + + const Mat44 scale_m = Mat44::sScale(Vec3(2.0f, 3.0f, 4.0f)); + const Mat44 translate_m = Mat44::sTranslation(Vec3(10.0f, 20.0f, 30.0f)); + const Mat44 rotate_x_m = Mat44::sRotationX(angle_a); + const Mat44 rotate_y_m = Mat44::sRotationY(angle_b); + const Mat44 rotate_z_m = Mat44::sRotationZ(angle_c); + const Mat44 pure_rotation_m = rotate_z_m * rotate_y_m * rotate_x_m; + + const Vec3 axis = Vec3(1.0f, 2.0f, -3.0f).Normalized(); + const float axis_angle = 48.0f * JPH_PI / 180.0f; + const Quat axis_quat = Quat::sRotation(axis, axis_angle); + const Mat44 axis_mat = Mat44::sRotation(axis_quat); + const Mat44 transform_m = translate_m * rotate_z_m * rotate_y_m * rotate_x_m * scale_m; + + const Quat quat_x = Quat::sRotation(Vec3(1.0f, 0.0f, 0.0f), angle_a); + const Quat quat_y = Quat::sRotation(Vec3(0.0f, 1.0f, 0.0f), angle_b); + const Quat quat_z = Quat::sRotation(Vec3(0.0f, 0.0f, 1.0f), angle_c); + const Quat quat_xy = quat_x * quat_y; + const Quat quat_xyz = quat_xy * quat_z; + + const Vec3 hard_axis = Vec3(1.0f, -2.0f, 3.0f).Normalized(); + const float hard_angle = 170.0f * JPH_PI / 180.0f; + const Quat hard_quat = Quat::sRotation(hard_axis, hard_angle); + const Mat44 hard_mat = Mat44::sRotation(hard_quat); + + const Mat44 rotation_only_m = RotationOnlyCopy(transform_m); + + Heading(lines, "dump"); + AppendLine(lines, "notes: matrices are printed in common column-major order"); + + Heading(lines, "matrix basics"); + DumpMat4(lines, "identity", Mat44::sIdentity()); + DumpMat4(lines, "matrix_a", mat_a); + DumpMat4(lines, "matrix_b", mat_b); + + Heading(lines, "matrix multiply"); + DumpMat4(lines, "matrix_a * matrix_b", mat_a * mat_b); + DumpMat4(lines, "matrix_b * matrix_a", mat_b * mat_a); + + Heading(lines, "element access [row, col]"); + + DumpScalar(lines, "transform[0, 0]", mat_a(0, 0)); + DumpScalar(lines, "transform[0, 1]", mat_a(0, 1)); + DumpScalar(lines, "transform[0, 2]", mat_a(0, 2)); + DumpScalar(lines, "transform[0, 3]", mat_a(0, 3)); + DumpScalar(lines, "transform[1, 0]", mat_a(1, 0)); + DumpScalar(lines, "transform[1, 1]", mat_a(1, 1)); + DumpScalar(lines, "transform[1, 2]", mat_a(1, 2)); + DumpScalar(lines, "transform[1, 3]", mat_a(1, 3)); + DumpScalar(lines, "transform[2, 0]", mat_a(2, 0)); + DumpScalar(lines, "transform[2, 1]", mat_a(2, 1)); + DumpScalar(lines, "transform[2, 2]", mat_a(2, 2)); + DumpScalar(lines, "transform[2, 3]", mat_a(2, 3)); + DumpScalar(lines, "transform[3, 0]", mat_a(3, 0)); + DumpScalar(lines, "transform[3, 1]", mat_a(3, 1)); + DumpScalar(lines, "transform[3, 2]", mat_a(3, 2)); + DumpScalar(lines, "transform[3, 3]", mat_a(3, 3)); + + Heading(lines, "matrix constructors and composition"); + DumpMat4(lines, "scale", scale_m); + DumpMat4(lines, "translate", translate_m); + DumpMat4(lines, "rotate_x", rotate_x_m); + DumpMat4(lines, "rotate_y", rotate_y_m); + DumpMat4(lines, "rotate_z", rotate_z_m); + DumpMat4(lines, "pure_rotation = rotate_z * rotate_y * rotate_x", pure_rotation_m); + DumpMat4(lines, "transform = translate * rotate_z * rotate_y * rotate_x * scale", transform_m); + + Heading(lines, "matrix vector multiply"); + DumpVec3(lines, "vec3_input", vec_a); + DumpVec4(lines, "vec4_input", vec_b); + DumpVec3(lines, "transform * vec3", transform_m * vec_a); + DumpVec4(lines, "transform * vec4", transform_m * vec_b); + DumpVec3(lines, "rotate_z * vec3", rotate_z_m * vec_a); + DumpVec3(lines, "translate * vec3", translate_m * vec_a); + + Heading(lines, "quaternion constructors"); + DumpQuat(lines, "quat_identity", Quat::sIdentity()); + DumpQuat(lines, "quat_rotate_x", quat_x); + DumpQuat(lines, "quat_rotate_y", quat_y); + DumpQuat(lines, "quat_rotate_z", quat_z); + DumpVec3(lines, "axis_normalized", axis); + DumpScalar(lines, "axis_angle_radians", axis_angle); + DumpQuat(lines, "from_axis_angle", axis_quat); + DumpMat4(lines, "from_axis_angle.mat4", axis_mat); + + Heading(lines, "quaternion multiply"); + DumpQuat(lines, "quat_x", quat_x); + DumpQuat(lines, "quat_y", quat_y); + DumpQuat(lines, "quat_z", quat_z); + DumpQuat(lines, "quat_multiply(quat_x, quat_y)", quat_xy); + DumpQuat(lines, "quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quat_xyz); + DumpMat4(lines, "quat_xy.mat4", Mat44::sRotation(quat_xy)); + DumpMat4(lines, "quat_xyz.mat4", Mat44::sRotation(quat_xyz)); + + Heading(lines, "quaternion vector rotate"); + DumpVec3(lines, "input", vec_a); + DumpVec3(lines, "quat_rotate(quat_x, input)", quat_x * vec_a); + DumpVec3(lines, "quat_rotate(quat_y, input)", quat_y * vec_a); + DumpVec3(lines, "quat_rotate(quat_z, input)", quat_z * vec_a); + DumpVec3(lines, "quat_rotate(from_axis_angle, input)", axis_quat * vec_a); + DumpVec3(lines, "quat_z * input", quat_z * vec_a); + + Heading(lines, "matrix quaternion roundtrip"); + const Quat pure_rotation_quat = pure_rotation_m.GetQuaternion(); + DumpQuat(lines, "pure_rotation.quat", pure_rotation_quat); + DumpMat4(lines, "pure_rotation", pure_rotation_m); + DumpMat4(lines, "pure_rotation.quat.mat4", Mat44::sRotation(pure_rotation_quat)); + DumpMat4(lines, "transform.rotation_only", rotation_only_m); + DumpQuat(lines, "transform.rotation_only.quat", rotation_only_m.GetQuaternion()); + const Quat axis_mat_quat = axis_mat.GetQuaternion(); + DumpQuat(lines, "axis_mat.quat", axis_mat_quat); + DumpMat4(lines, "axis_mat.quat.mat4", Mat44::sRotation(axis_mat_quat)); + AppendLine(lines, "hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero"); + DumpQuat(lines, "hard_decomp.quat_original", hard_quat); + const Quat hard_mat_quat = hard_mat.GetQuaternion(); + DumpQuat(lines, "hard_decomp.quat_from_mat", hard_mat_quat); + DumpMat4(lines, "hard_decomp.mat4", hard_mat); + DumpMat4(lines, "hard_decomp.quat_from_mat.mat4", Mat44::sRotation(hard_mat_quat)); + + Heading(lines, "lookAt matrix"); + AppendLine(lines, "notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)"); + DumpMat4(lines, "lookAt", Mat44::sLookAt(Vec3(5.0f, 5.0f, 5.0f), Vec3(0.0f, 0.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f))); + + Heading(lines, "euler angle decomposition"); + AppendLine(lines, "notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians"); + DumpVec3(lines, "pure_rotation.quat.euler", pure_rotation_quat.GetEulerAngles()); + DumpVec3(lines, "from_axis_angle.euler", axis_quat.GetEulerAngles()); + + Heading(lines, "matrix inverse"); + DumpMat4(lines, "transform.inverse", transform_m.Inversed()); + DumpMat4(lines, "pure_rotation.inverse", pure_rotation_m.Inversed()); + + Heading(lines, "cross product"); + AppendLine(lines, "notes: cross(a, b) where a and b are vec3"); + DumpVec3(lines, "cross(x_axis, y_axis)", Vec3(1.0f, 0.0f, 0.0f).Cross(Vec3(0.0f, 1.0f, 0.0f))); + DumpVec3(lines, "cross(y_axis, x_axis)", Vec3(0.0f, 1.0f, 0.0f).Cross(Vec3(1.0f, 0.0f, 0.0f))); + const Vec3 cross_c = Vec3(1.0f, 2.0f, 3.0f).Normalized(); + const Vec3 cross_d = Vec3(-1.0f, 0.5f, 2.0f).Normalized(); + DumpVec3(lines, "cross(c, d)", cross_c.Cross(cross_d)); + + Heading(lines, "slerp"); + AppendLine(lines, "notes: slerp(a, b, t) between quat_x and quat_z"); + DumpQuat(lines, "slerp(quat_x, quat_z, 0.25)", quat_x.SLERP(quat_z, 0.25f)); + DumpQuat(lines, "slerp(quat_x, quat_z, 0.5)", quat_x.SLERP(quat_z, 0.5f)); + DumpQuat(lines, "slerp(quat_x, quat_z, 0.75)", quat_x.SLERP(quat_z, 0.75f)); + + Heading(lines, "fromTwoVectors"); + AppendLine(lines, "notes: quaternion that rotates vector a to vector b"); + const Vec3 ft_from_a(1.0f, 0.0f, 0.0f); + const Vec3 ft_from_b(0.0f, 1.0f, 0.0f); + const Vec3 ft_from_c = Vec3(1.0f, 2.0f, -1.0f).Normalized(); + const Vec3 ft_from_d = Vec3(-1.0f, 0.5f, 2.0f).Normalized(); + const Quat ft_qab = Quat::sFromTo(ft_from_a, ft_from_b); + const Quat ft_qcd = Quat::sFromTo(ft_from_c, ft_from_d); + DumpQuat(lines, "from_x_to_y", ft_qab); + DumpQuat(lines, "from_c_to_d", ft_qcd); + DumpVec3(lines, "verify_x_to_y", ft_qab * ft_from_a); + DumpVec3(lines, "verify_c_to_d", ft_qcd * ft_from_c); + + Heading(lines, "quaternion inverse"); + const Quat axis_quat_inv = axis_quat.Inversed(); + DumpQuat(lines, "from_axis_angle.inverse", axis_quat_inv); + DumpQuat(lines, "verify_q_mul_qinv", axis_quat * axis_quat_inv); + + Heading(lines, "quaternion to axis-angle"); + Vec3 aa_axis1; + float aa_angle1 = 0.0f; + axis_quat.GetAxisAngle(aa_axis1, aa_angle1); + Vec3 aa_axis2; + float aa_angle2 = 0.0f; + quat_xyz.GetAxisAngle(aa_axis2, aa_angle2); + DumpVec3(lines, "from_axis_angle.axis", aa_axis1); + DumpScalar(lines, "from_axis_angle.angle", aa_angle1); + DumpVec3(lines, "quat_xyz.axis", aa_axis2); + DumpScalar(lines, "quat_xyz.angle", aa_angle2); + + Heading(lines, "perspective matrix"); + AppendLine(lines, "notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0"); + DumpMat4(lines, "perspective", Mat44::sPerspective(60.0f * JPH_PI / 180.0f, 1.5f, 0.1f, 100.0f)); + + Heading(lines, "ortho matrix"); + AppendLine(lines, "N/A"); + + Heading(lines, "basis directions"); + AppendLine(lines, "N/A"); + + std::ofstream out(output_path, std::ios::binary); + for (size_t i = 0; i < lines.size(); ++i) + { + out << lines[i]; + if (i + 1 < lines.size()) + out << '\n'; + } + out << '\n'; + + std::cout << "Wrote " << output_path << "\n"; + return 0; +} diff --git a/conformance/dump_jolt.nim b/conformance/dump_jolt.nim index 79c9d5b..cc902fc 100644 --- a/conformance/dump_jolt.nim +++ b/conformance/dump_jolt.nim @@ -1,327 +1,58 @@ -import - std/[math, os, strutils], - jolty +import std/[os, osproc, strformat, strutils] const - OutputPath = parentDir(currentSourcePath()) / "dump_jolt.txt" + ConformanceDir = parentDir(currentSourcePath()) + RepoRoot = ConformanceDir.parentDir().parentDir() + JoltIncludeDir = RepoRoot / "jolty" / "JoltPhysics" + JoltIssueReportingCpp = JoltIncludeDir / "Jolt" / "Core" / "IssueReporting.cpp" + CppSource = ConformanceDir / "dump_jolt.cpp" +when defined(windows): + const ExeName = "dump_jolt.exe" +else: + const ExeName = "dump_jolt" -proc cleanFloat(value: float32): float32 = - if abs(value) < 0.0000005'f32: - 0'f32 +const + ExePath = ConformanceDir / ExeName + +proc quoteArg(value: string): string = + if value.contains({' ', '\t', '"'}): + "\"" & value.replace("\"", "\\\"") & "\"" else: value -proc fmt(value: float32): string = - let cleaned = cleanFloat(value) - let sign = if cleaned < 0'f32: "-" else: "+" - - var whole = int(floor(abs(cleaned).float64)) - var frac = int(round((abs(cleaned).float64 - whole.float64) * 1000.0)) - if frac == 1000: - inc whole - frac = 0 - - result = sign - let wholeText = $whole - for _ in wholeText.len ..< 3: - result.add '0' - result.add wholeText - result.add '.' - - let fracText = $frac - for _ in fracText.len ..< 3: - result.add '0' - result.add fracText - -proc appendLine(lines: var seq[string], line = "") = - lines.add line - -proc dumpScalar(lines: var seq[string], label: string, value: float32) = - lines.appendLine(label & ": " & fmt(value)) - -proc dumpVec3(lines: var seq[string], label: string, value: JoltFloat3) = - lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ">") - -proc dumpVec4(lines: var seq[string], label: string, value: JoltFloat4) = - lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ", " & fmt(value.w) & ">") - -proc dumpQuat(lines: var seq[string], label: string, value: JoltQuat) = - lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ", " & fmt(value.w) & ">") - -proc dumpMat4(lines: var seq[string], label: string, value: JoltMat44) = - lines.appendLine(label & ":") - let d = cast[array[16, float32]](value) - lines.appendLine("[") - lines.appendLine(" " & fmt(d[0]) & " " & fmt(d[4]) & " " & fmt(d[8]) & " " & fmt(d[12]) ) - lines.appendLine(" " & fmt(d[1]) & " " & fmt(d[5]) & " " & fmt(d[9]) & " " & fmt(d[13]) ) - lines.appendLine(" " & fmt(d[2]) & " " & fmt(d[6]) & " " & fmt(d[10]) & " " & fmt(d[14]) ) - lines.appendLine(" " & fmt(d[3]) & " " & fmt(d[7]) & " " & fmt(d[11]) & " " & fmt(d[15]) ) - lines.appendLine("]") - -proc heading(lines: var seq[string], title: string) = - if lines.len > 0: - lines.appendLine() - lines.appendLine("== " & title & " ==") - -proc multiply(a, b: JoltMat44): JoltMat44 = - var ma = a - var mb = b - joltMat44Multiply(addr ma, addr mb) +proc findCompiler(): string = + let envCompiler = getEnv("CXX") + if envCompiler.len > 0: + return envCompiler -proc multiply(m: JoltMat44, v: JoltFloat3): JoltFloat3 = - var mm = m - joltMat44MultiplyVec3(addr mm, v.x, v.y, v.z) + for candidate in ["g++.exe", "clang++.exe", "g++", "clang++", "c++.exe", "c++"]: + if findExe(candidate).len > 0: + return candidate -proc multiply(m: JoltMat44, v: JoltFloat4): JoltFloat4 = - var mm = m - joltMat44MultiplyVec4(addr mm, v.x, v.y, v.z, v.w) + raise newException(OSError, "Could not find a C++ compiler. Set CXX or install g++ / clang++.") -proc getQuaternion(m: JoltMat44): JoltQuat = - var mm = m - joltMat44GetQuaternion(addr mm) - -proc quatRotate(axisX, axisY, axisZ, angle: float32): JoltQuat = - joltQuatRotation(axisX, axisY, axisZ, angle) - -proc quatMultiply(a, b: JoltQuat): JoltQuat = - joltQuatMultiply(a.x, a.y, a.z, a.w, b.x, b.y, b.z, b.w) - -proc quatRotateVec3(q: JoltQuat, v: JoltFloat3): JoltFloat3 = - joltQuatRotateVec3(q.x, q.y, q.z, q.w, v.x, v.y, v.z) - -proc get(m: JoltMat44, row, col: int32): float32 = - var mm = m - joltMat44Get(addr mm, row, col) - -proc inverse(m: JoltMat44): JoltMat44 = - var mm = m - joltMat44Inverse(addr mm) - -proc rotationOnlyCopy(value: JoltMat44): JoltMat44 = - var d = cast[array[16, float32]](value) - d[12] = 0'f32 - d[13] = 0'f32 - d[14] = 0'f32 - d[15] = 1'f32 - result = cast[JoltMat44](d) +proc runOrQuit(command: string) = + let (output, exitCode) = execCmdEx(command) + if output.len > 0: + stdout.write output + if exitCode != 0: + quit exitCode proc main() = - let physics = newPhysicsSystem( - maxBodies = 1024, - maxPairs = 1024, - maxConstraints = 1024, - ) - - var lines: seq[string] - - let - angleA = 37'f32 * PI.float32 / 180'f32 - angleB = -23'f32 * PI.float32 / 180'f32 - angleC = 71'f32 * PI.float32 / 180'f32 - - matA = cast[JoltMat44]([ - 1.0f, 2.0f, 3.0f, 4.0f, - 5.0f, 6.0f, 7.0f, 8.0f, - 9.0f, 10.0f, 11.0f, 12.0f, - 13.0f, 14.0f, 15.0f, 16.0f - ]) - - matB = cast[JoltMat44]([ - -10.0f, -20.0f, -30.0f, -40.0f, - 50.0f, 60.0f, 70.0f, 80.0f, - 90.0f, 100.0f, 110.0f, 120.0f, - 130.0f, 140.0f, 150.0f, 160.0f - ]) - - vecA = JoltFloat3(x: 1.25, y: -2.5, z: 3.75) - vecB = JoltFloat4(x: 1.25, y: -2.5, z: 3.75, w: 1.0) - - scaleM = joltMat44Scale(2.0, 3.0, 4.0) - translateM = joltMat44Translation(10.0, 20.0, 30.0) - rotateXM = joltMat44RotationX(angleA) - rotateYM = joltMat44RotationY(angleB) - rotateZM = joltMat44RotationZ(angleC) - pureRotationM = multiply(multiply(rotateZM, rotateYM), rotateXM) - - axis = joltVec3Normalize(1.0, 2.0, -3.0) - axisAngle = 48'f32 * PI.float32 / 180'f32 - axisQuat = quatRotate(axis.x, axis.y, axis.z, axisAngle) - axisMat = joltMat44FromQuat(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - transformM = multiply(multiply(multiply(multiply(translateM, rotateZM), rotateYM), rotateXM), scaleM) - - quatX = quatRotate(1.0, 0.0, 0.0, angleA) - quatY = quatRotate(0.0, 1.0, 0.0, angleB) - quatZ = quatRotate(0.0, 0.0, 1.0, angleC) - quatXY = quatMultiply(quatX, quatY) - quatXYZ = quatMultiply(quatXY, quatZ) - - hardAxis = joltVec3Normalize(1.0, -2.0, 3.0) - hardAngle = 170'f32 * PI.float32 / 180'f32 - hardQuat = quatRotate(hardAxis.x, hardAxis.y, hardAxis.z, hardAngle) - hardMat = joltMat44FromQuat(hardQuat.x, hardQuat.y, hardQuat.z, hardQuat.w) - - rotationOnlyM = rotationOnlyCopy(transformM) - - lines.heading("dump") - lines.appendLine("notes: matrices are printed in common column-major order") - - lines.heading("matrix basics") - lines.dumpMat4("identity", joltMat44Identity()) - lines.dumpMat4("matrix_a", matA) - lines.dumpMat4("matrix_b", matB) - - lines.heading("matrix multiply") - lines.dumpMat4("matrix_a * matrix_b", multiply(matA, matB)) - lines.dumpMat4("matrix_b * matrix_a", multiply(matB, matA)) - - lines.heading("element access [row, col]") - lines.dumpScalar("transform[0, 0]", matA.get(0, 0)) - lines.dumpScalar("transform[0, 1]", matA.get(0, 1)) - lines.dumpScalar("transform[0, 2]", matA.get(0, 2)) - lines.dumpScalar("transform[0, 3]", matA.get(0, 3)) - lines.dumpScalar("transform[1, 0]", matA.get(1, 0)) - lines.dumpScalar("transform[1, 1]", matA.get(1, 1)) - lines.dumpScalar("transform[1, 2]", matA.get(1, 2)) - lines.dumpScalar("transform[1, 3]", matA.get(1, 3)) - lines.dumpScalar("transform[2, 0]", matA.get(2, 0)) - lines.dumpScalar("transform[2, 1]", matA.get(2, 1)) - lines.dumpScalar("transform[2, 2]", matA.get(2, 2)) - lines.dumpScalar("transform[2, 3]", matA.get(2, 3)) - lines.dumpScalar("transform[3, 0]", matA.get(3, 0)) - lines.dumpScalar("transform[3, 1]", matA.get(3, 1)) - lines.dumpScalar("transform[3, 2]", matA.get(3, 2)) - lines.dumpScalar("transform[3, 3]", matA.get(3, 3)) - - lines.heading("matrix constructors and composition") - lines.dumpMat4("scale", scaleM) - lines.dumpMat4("translate", translateM) - lines.dumpMat4("rotate_x", rotateXM) - lines.dumpMat4("rotate_y", rotateYM) - lines.dumpMat4("rotate_z", rotateZM) - lines.dumpMat4("pure_rotation = rotate_z * rotate_y * rotate_x", pureRotationM) - lines.dumpMat4("transform = translate * rotate_z * rotate_y * rotate_x * scale", transformM) - - lines.heading("matrix vector multiply") - lines.dumpVec3("vec3_input", vecA) - lines.dumpVec4("vec4_input", vecB) - lines.dumpVec3("transform * vec3", multiply(transformM, vecA)) - lines.dumpVec4("transform * vec4", multiply(transformM, vecB)) - lines.dumpVec3("rotate_z * vec3", multiply(rotateZM, vecA)) - lines.dumpVec3("translate * vec3", multiply(translateM, vecA)) - - lines.heading("quaternion constructors") - lines.dumpQuat("quat_identity", joltQuatIdentity()) - lines.dumpQuat("quat_rotate_x", quatX) - lines.dumpQuat("quat_rotate_y", quatY) - lines.dumpQuat("quat_rotate_z", quatZ) - lines.dumpVec3("axis_normalized", axis) - lines.dumpScalar("axis_angle_radians", axisAngle) - lines.dumpQuat("from_axis_angle", axisQuat) - lines.dumpMat4("from_axis_angle.mat4", axisMat) - - lines.heading("quaternion multiply") - lines.dumpQuat("quat_x", quatX) - lines.dumpQuat("quat_y", quatY) - lines.dumpQuat("quat_z", quatZ) - lines.dumpQuat("quat_multiply(quat_x, quat_y)", quatXY) - lines.dumpQuat("quat_multiply(quat_multiply(quat_x, quat_y), quat_z)", quatXYZ) - lines.dumpMat4("quat_xy.mat4", joltMat44FromQuat(quatXY.x, quatXY.y, quatXY.z, quatXY.w)) - lines.dumpMat4("quat_xyz.mat4", joltMat44FromQuat(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w)) - - lines.heading("quaternion vector rotate") - lines.dumpVec3("input", vecA) - lines.dumpVec3("quat_rotate(quat_x, input)", quatRotateVec3(quatX, vecA)) - lines.dumpVec3("quat_rotate(quat_y, input)", quatRotateVec3(quatY, vecA)) - lines.dumpVec3("quat_rotate(quat_z, input)", quatRotateVec3(quatZ, vecA)) - lines.dumpVec3("quat_rotate(from_axis_angle, input)", quatRotateVec3(axisQuat, vecA)) - lines.dumpVec3("quat_z * input", quatRotateVec3(quatZ, vecA)) - - lines.heading("matrix quaternion roundtrip") - let pureRotationQuat = getQuaternion(pureRotationM) - lines.dumpQuat("pure_rotation.quat", pureRotationQuat) - lines.dumpMat4("pure_rotation", pureRotationM) - lines.dumpMat4("pure_rotation.quat.mat4", joltMat44FromQuat(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w)) - lines.dumpMat4("transform.rotation_only", rotationOnlyM) - lines.dumpQuat("transform.rotation_only.quat", getQuaternion(rotationOnlyM)) - let axisMatQuat = getQuaternion(axisMat) - lines.dumpQuat("axis_mat.quat", axisMatQuat) - lines.dumpMat4("axis_mat.quat.mat4", joltMat44FromQuat(axisMatQuat.x, axisMatQuat.y, axisMatQuat.z, axisMatQuat.w)) - lines.appendLine("hard_decomp.note: 170 degrees around (1,-2,3) normalized - w near zero") - lines.dumpQuat("hard_decomp.quat_original", hardQuat) - let hardMatQuat = getQuaternion(hardMat) - lines.dumpQuat("hard_decomp.quat_from_mat", hardMatQuat) - lines.dumpMat4("hard_decomp.mat4", hardMat) - lines.dumpMat4("hard_decomp.quat_from_mat.mat4", joltMat44FromQuat(hardMatQuat.x, hardMatQuat.y, hardMatQuat.z, hardMatQuat.w)) - - lines.heading("lookAt matrix") - lines.appendLine("notes: eye=(5,5,5), center=(0,0,0), up=(0,1,0)") - lines.dumpMat4("lookAt", joltMat44LookAt(5, 5, 5, 0, 0, 0, 0, 1, 0)) - - lines.heading("euler angle decomposition") - lines.appendLine("notes: euler angles as vec3(pitch/x, yaw/y, roll/z) in radians") - let pureRotEuler = joltQuatGetEulerAngles(pureRotationQuat.x, pureRotationQuat.y, pureRotationQuat.z, pureRotationQuat.w) - let axisEuler = joltQuatGetEulerAngles(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - lines.dumpVec3("pure_rotation.quat.euler", pureRotEuler) - lines.dumpVec3("from_axis_angle.euler", axisEuler) - - lines.heading("matrix inverse") - lines.dumpMat4("transform.inverse", inverse(transformM)) - lines.dumpMat4("pure_rotation.inverse", inverse(pureRotationM)) - - lines.heading("cross product") - lines.appendLine("notes: cross(a, b) where a and b are vec3") - lines.dumpVec3("cross(x_axis, y_axis)", joltVec3Cross(1, 0, 0, 0, 1, 0)) - lines.dumpVec3("cross(y_axis, x_axis)", joltVec3Cross(0, 1, 0, 1, 0, 0)) - let crossC = joltVec3Normalize(1, 2, 3) - let crossD = joltVec3Normalize(-1, 0.5, 2) - lines.dumpVec3("cross(c, d)", joltVec3Cross(crossC.x, crossC.y, crossC.z, crossD.x, crossD.y, crossD.z)) - - lines.heading("slerp") - lines.appendLine("notes: slerp(a, b, t) between quat_x and quat_z") - lines.dumpQuat("slerp(quat_x, quat_z, 0.25)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.25)) - lines.dumpQuat("slerp(quat_x, quat_z, 0.5)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.5)) - lines.dumpQuat("slerp(quat_x, quat_z, 0.75)", joltQuatSlerp(quatX.x, quatX.y, quatX.z, quatX.w, quatZ.x, quatZ.y, quatZ.z, quatZ.w, 0.75)) - - lines.heading("fromTwoVectors") - lines.appendLine("notes: quaternion that rotates vector a to vector b") - let ftFromA = JoltFloat3(x: 1, y: 0, z: 0) - let ftFromB = JoltFloat3(x: 0, y: 1, z: 0) - let ftFromC = joltVec3Normalize(1, 2, -1) - let ftFromD = joltVec3Normalize(-1, 0.5, 2) - let ftQAB = joltQuatFromTo(ftFromA.x, ftFromA.y, ftFromA.z, ftFromB.x, ftFromB.y, ftFromB.z) - let ftQCD = joltQuatFromTo(ftFromC.x, ftFromC.y, ftFromC.z, ftFromD.x, ftFromD.y, ftFromD.z) - lines.dumpQuat("from_x_to_y", ftQAB) - lines.dumpQuat("from_c_to_d", ftQCD) - lines.dumpVec3("verify_x_to_y", quatRotateVec3(ftQAB, ftFromA)) - lines.dumpVec3("verify_c_to_d", quatRotateVec3(ftQCD, ftFromC)) - - lines.heading("quaternion inverse") - let axisQuatInv = joltQuatInverse(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - lines.dumpQuat("from_axis_angle.inverse", axisQuatInv) - let verifyInv = quatMultiply(axisQuat, axisQuatInv) - lines.dumpQuat("verify_q_mul_qinv", verifyInv) - - lines.heading("quaternion to axis-angle") - let aa1 = joltQuatGetAxisAngle(axisQuat.x, axisQuat.y, axisQuat.z, axisQuat.w) - let aa2 = joltQuatGetAxisAngle(quatXYZ.x, quatXYZ.y, quatXYZ.z, quatXYZ.w) - lines.dumpVec3("from_axis_angle.axis", JoltFloat3(x: aa1.x, y: aa1.y, z: aa1.z)) - lines.dumpScalar("from_axis_angle.angle", aa1.w) - lines.dumpVec3("quat_xyz.axis", JoltFloat3(x: aa2.x, y: aa2.y, z: aa2.z)) - lines.dumpScalar("quat_xyz.angle", aa2.w) - - lines.heading("perspective matrix") - lines.appendLine("notes: fovy=60 degrees, aspect=1.5, near=0.1, far=100.0") - let fovyRad = 60'f32 * PI.float32 / 180'f32 - lines.dumpMat4("perspective", joltMat44Perspective(fovyRad, 1.5'f32, 0.1'f32, 100'f32)) - - lines.heading("ortho matrix") - lines.appendLine("N/A") - - lines.heading("basis directions") - lines.appendLine("N/A") - - writeFile(OutputPath, lines.join("\n") & "\n") - physics.destroy() - echo "Wrote ", OutputPath + let compiler = findCompiler() + let compileCmd = [ + quoteArg(compiler), + "-std=c++17", + "-DJPH_DOUBLE_PRECISION", + "-I" & quoteArg(JoltIncludeDir), + quoteArg(CppSource), + quoteArg(JoltIssueReportingCpp), + "-o", + quoteArg(ExePath) + ].join(" ") + + echo fmt"Building {CppSource} with {compiler}" + runOrQuit(compileCmd) + runOrQuit(quoteArg(ExePath)) main() From 2ddb96d17b21a20bb67f953705911b5744a8022d Mon Sep 17 00:00:00 2001 From: treeform Date: Tue, 14 Apr 2026 20:11:03 -0700 Subject: [PATCH 17/35] f --- README.md | 32 ++++++++++++++++++++------------ src/vmath.nim | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b4dae26..530205e 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,15 @@ This is the same system used in the GLTF file format. ## How does vmath compare to other libraries? -vmath follows the standard glTF / OpenGL conventions: right-handed coordinate system, column-major matrix storage, and standard math `[row, col]` indexing. These are the dominant conventions used across graphics and game engines. +vmath follows the standard glTF / OpenGL conventions: right-handed coordinate system, column-major matrix storage, and math-style `[row, col]` indexing. -We run identical math operations across vmath, GLSL, [nim-glm](https://github.com/nickelsworth/nim-glm), [gl-matrix](https://github.com/toji/gl-matrix), and [Jolt Physics](https://github.com/jrouwe/JoltPhysics) and compare all results. See [experiments/](experiments/) for the dump scripts. +We run identical math operations across vmath, GLSL, [nim-glm](https://github.com/nickelsworth/nim-glm), [gl-matrix](https://github.com/toji/gl-matrix), and [Jolt Physics](https://github.com/jrouwe/JoltPhysics) and compare the generated dumps in [conformance/](conformance/). + +The current takeaway is: + +* Math results match across vmath, GLSL, nim-GLM, gl-matrix, and Jolt for the tested constructors, transforms, quaternions, inverses, cross products, slerp, and `fromTwoVectors`. +* API and indexing conventions still differ even when the math matches. +* Jolt is the main convention outlier: it matches the tested math, but its matrix indexing/order conventions line up with the DirectX/HLSL side rather than the math-style `[row, col]` convention used by vmath. | Feature | vmath | GLSL | nim-GLM | gl-matrix | Jolt Physics | |----------------------------|:-----:|:----:|:-------:|:---------:|:------------:| @@ -137,21 +143,23 @@ We run identical math operations across vmath, GLSL, [nim-glm](https://github.co | Perspective matrix | ✅ | ✅ | ✅ | ✅ | ❌ | | Ortho matrix | ✅ | ✅ | ✅ | ✅ | N/A | | Euler angle decomposition | ✅ | ✅ | ✅ | N/A | ✅ | -| Element access `[row,col]` | ✅ | ✅ | ✅ | N/A | ✅ | +| Element access | ✅ | ✅ | ✅ | N/A | ✅ | + +❌ **Perspective matrix**: Jolt Physics uses Z range `[0, 1]` (Vulkan/DirectX convention) while vmath uses Z range `[-1, 1]` (OpenGL convention). The X and Y scaling match, but Z-related elements differ. -❌ **Perspective matrix**: Jolt Physics uses Z range [0, 1] (Vulkan/DirectX convention) while vmath uses Z range [-1, 1] (OpenGL convention). The X and Y scaling match, but Z-related elements differ. +**Element access note**: GLSL matrix indexing is `m[column][row]`, not math-style `[row, col]`. gl-matrix also does not expose built-in 2D matrix indexing; it uses a flat 16-element array and callers map indices manually. Jolt supports element access too, but its indexing/order convention differs from vmath's math-style `[row, col]` interpretation. # 2.x.x to 3.0.0 vmath breaking changes: -Significant work went into aligning vmath with the dominant conventions used across the game and graphics industry. vmath now matches GLM, gl-matrix, and Jolt on all core operations (see comparison table above). +Version `3.0.0` realigned vmath around standard graphics conventions so its behavior matches the math used by GLSL, GLM, gl-matrix, and the rest of the conformance suite much more closely. -* **Matrix indexing convention changed from `[col, row]` to `[row, col]`** (standard math notation). Code using `m[0, 3]` to access the translation X component should change to `m[0, 3]` (same syntax, but the meaning of indices swapped). -* **`mat4(1..16)` no longer transposes** the input. Arguments are now stored directly to memory in column-major order, identical to `gmat4(1..16)`. If you were passing row-major values, you need to transpose them. -* **Matrix multiplication is now standard `A * B`** (apply B first, then A). Previously it was reversed. -* **`mat4 * vec3` now computes `M * v`** instead of `M^T * v`. -* **2D `rotate(angle)` now produces a standard CCW rotation matrix.** -* **`fromTwoVectors(a, b)` now correctly rotates `a` into `b`** (was reversed). -* **`toAngles` / `fromAngles` use standard Y-X-Z decomposition** with correct signs. +* **Matrix indexing changed from `[col, row]` to `[row, col]`.** The syntax is the same, but the meaning of the two indices swapped to match standard math notation. +* **`mat4(1..16)` no longer transposes its arguments.** Values are now stored directly in column-major order. If your old code passed row-major literals, transpose them before constructing the matrix. +* **Matrix multiplication now uses standard `A * B` composition.** That means `B` is applied first, then `A`. +* **`mat4 * vec3` now means `M * v`.** Older code that relied on the transposed behavior will need to be updated. +* **2D `rotate(angle)` now produces the standard counter-clockwise rotation matrix.** +* **`fromTwoVectors(a, b)` now rotates `a` into `b`.** Previous behavior had that direction reversed. +* **`toAngles` / `fromAngles` now use the standard Y-X-Z convention with corrected signs.** # 1.x.x to 2.0.0 vmath breaking changes: * New right-hand-Z-forward coordinate system and functions that care about diff --git a/src/vmath.nim b/src/vmath.nim index 22fbe7e..d234c65 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -1840,9 +1840,22 @@ proc mat4*[T](q: GVec4[T]): GMat4[T] = proc mat4*(m: DMat4): Mat4 {.inline.} = ## Convert a double precision matrix to a single precision matrix. - for r in 0 ..< 4: - for c in 0 ..< 4: - result[r, c] = float32(m[r, c]) + result[0, 0] = float32(m[0, 0]) + result[0, 1] = float32(m[0, 1]) + result[0, 2] = float32(m[0, 2]) + result[0, 3] = float32(m[0, 3]) + result[1, 0] = float32(m[1, 0]) + result[1, 1] = float32(m[1, 1]) + result[1, 2] = float32(m[1, 2]) + result[1, 3] = float32(m[1, 3]) + result[2, 0] = float32(m[2, 0]) + result[2, 1] = float32(m[2, 1]) + result[2, 2] = float32(m[2, 2]) + result[2, 3] = float32(m[2, 3]) + result[3, 0] = float32(m[3, 0]) + result[3, 1] = float32(m[3, 1]) + result[3, 2] = float32(m[3, 2]) + result[3, 3] = float32(m[3, 3]) proc mat4*(m: Mat4): Mat4 {.inline.} = ## Convert a double precision matrix to a single precision matrix. @@ -1850,9 +1863,22 @@ proc mat4*(m: Mat4): Mat4 {.inline.} = proc dmat4*(m: Mat4): DMat4 {.inline.} = ## Convert a single precision matrix to a double precision matrix. - for r in 0 ..< 4: - for c in 0 ..< 4: - result[r, c] = float64(m[r, c]) + result[0, 0] = float64(m[0, 0]) + result[0, 1] = float64(m[0, 1]) + result[0, 2] = float64(m[0, 2]) + result[0, 3] = float64(m[0, 3]) + result[1, 0] = float64(m[1, 0]) + result[1, 1] = float64(m[1, 1]) + result[1, 2] = float64(m[1, 2]) + result[1, 3] = float64(m[1, 3]) + result[2, 0] = float64(m[2, 0]) + result[2, 1] = float64(m[2, 1]) + result[2, 2] = float64(m[2, 2]) + result[2, 3] = float64(m[2, 3]) + result[3, 0] = float64(m[3, 0]) + result[3, 1] = float64(m[3, 1]) + result[3, 2] = float64(m[3, 2]) + result[3, 3] = float64(m[3, 3]) proc dmat4*(m: DMat4): DMat4 {.inline.} = ## Convert a double precision matrix to a double precision matrix. From 8cbd489bb6d8eafa2c2fc07f58b76736fa543da8 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 06:06:41 -0700 Subject: [PATCH 18/35] f --- conformance/dump_glm.nim | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/conformance/dump_glm.nim b/conformance/dump_glm.nim index 5e15773..b499482 100644 --- a/conformance/dump_glm.nim +++ b/conformance/dump_glm.nim @@ -63,17 +63,6 @@ proc heading(lines: var seq[string], title: string) = lines.appendLine() lines.appendLine("== " & title & " ==") -proc mat4FromRows( - m00, m01, m02, m03: float32, - m10, m11, m12, m13: float32, - m20, m21, m22, m23: float32, - m30, m31, m32, m33: float32 -): Mat4f = - result[0] = vec4f(m00, m10, m20, m30) - result[1] = vec4f(m01, m11, m21, m31) - result[2] = vec4f(m02, m12, m22, m32) - result[3] = vec4f(m03, m13, m23, m33) - proc transformVec3ByMat4(matrix: Mat4f, value: Vec3f): Vec3f = (matrix * vec4f(value, 1'f32)).xyz From 4e3602c651ff33c41489302ef328512de9dc15cb Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 06:21:10 -0700 Subject: [PATCH 19/35] do the native print too. --- conformance/dump_glm.nim | 7 +++++++ conformance/dump_glm.txt | 12 ++++++++++++ conformance/dump_glmatrix.js | 7 +++++++ conformance/dump_glmatrix.txt | 4 ++++ conformance/dump_glsl.nim | 3 +++ conformance/dump_glsl.txt | 3 +++ conformance/dump_jolt.cpp | 11 +++++++++++ conformance/dump_jolt.txt | 4 ++++ conformance/dump_vmath.nim | 7 +++++++ conformance/dump_vmath.txt | 14 ++++++++++++++ 10 files changed, 72 insertions(+) diff --git a/conformance/dump_glm.nim b/conformance/dump_glm.nim index b499482..14414a9 100644 --- a/conformance/dump_glm.nim +++ b/conformance/dump_glm.nim @@ -58,6 +58,9 @@ proc dumpMat4(lines: var seq[string], label: string, value: Mat4f) = lines.appendLine(" " & fmt(d[3]) & " " & fmt(d[7]) & " " & fmt(d[11]) & " " & fmt(d[15]) ) lines.appendLine("]") +proc dumpMat4Default(lines: var seq[string], label: string, value: Mat4f) = + lines.appendLine(label & ": " & $value) + proc heading(lines: var seq[string], title: string) = if lines.len > 0: lines.appendLine() @@ -282,6 +285,10 @@ proc main() = lines.heading("basis directions") lines.appendLine("N/A") + lines.heading("default matrix printer") + lines.dumpMat4Default("matrix_a.default", matA) + lines.dumpMat4Default("transform.default", transformM) + writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/conformance/dump_glm.txt b/conformance/dump_glm.txt index 762ae1e..22ae563 100644 --- a/conformance/dump_glm.txt +++ b/conformance/dump_glm.txt @@ -294,3 +294,15 @@ ortho: == basis directions == N/A + +== default matrix printer == +matrix_a.default: / 1.0 5.0 9.0 13.0 \ +| 2.0 6.0 10.0 14.0 | +| 3.0 7.0 11.0 15.0 | + \ 4.0 8.0 12.0 16.0 / + +transform.default: / 0.5993741 -2.4950442 1.8697325 10.0 \ +| 1.7407088 0.11302096 -1.9639301 20.0 | +| 0.78146225 1.6619208 2.9405916 30.0 | + \ 0.0 0.0 0.0 1.0 / + diff --git a/conformance/dump_glmatrix.js b/conformance/dump_glmatrix.js index 65d77c0..484bb2a 100644 --- a/conformance/dump_glmatrix.js +++ b/conformance/dump_glmatrix.js @@ -57,6 +57,9 @@ function dumpMat4(label, m) { appendLine(" " + fmt(m[3]) + " " + fmt(m[7]) + " " + fmt(m[11]) + " " + fmt(m[15])); appendLine("]"); } +function dumpMat4Default(label, m) { + appendLine(label + ": " + mat4.str(m)); +} function scaleMat(sx, sy, sz) { return mat4.scale(mat4.create(), mat4.create(), vec3.fromValues(sx, sy, sz)); @@ -336,5 +339,9 @@ dumpVec3("forward", basisForward(matA)); dumpVec3("right", basisRight(matA)); dumpVec3("up", basisUp(matA)); +heading("default matrix printer"); +dumpMat4Default("matrix_a.default", matA); +dumpMat4Default("transform.default", transformM); + writeFileSync(OutputPath, lines.join("\n") + "\n"); console.log("Wrote", OutputPath); diff --git a/conformance/dump_glmatrix.txt b/conformance/dump_glmatrix.txt index 21d5433..cbc8e68 100644 --- a/conformance/dump_glmatrix.txt +++ b/conformance/dump_glmatrix.txt @@ -281,3 +281,7 @@ ortho: forward: <+009.000, +010.000, +011.000> right: <+001.000, +002.000, +003.000> up: <+005.000, +006.000, +007.000> + +== default matrix printer == +matrix_a.default: mat4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) +transform.default: mat4(0.5993741750717163, 1.740708827972412, 0.7814622521400452, 0, -2.495043992996216, 0.11302084475755692, 1.6619211435317993, 0, 1.8697327375411987, -1.9639301300048828, 2.940591335296631, 0, 10, 20, 30, 1) diff --git a/conformance/dump_glsl.nim b/conformance/dump_glsl.nim index a82d614..8cf8363 100644 --- a/conformance/dump_glsl.nim +++ b/conformance/dump_glsl.nim @@ -644,6 +644,9 @@ proc main() = lines.heading("basis directions") lines.appendLine("N/A") + lines.heading("default matrix printer") + lines.appendLine("N/A") + writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/conformance/dump_glsl.txt b/conformance/dump_glsl.txt index 762ae1e..bd70419 100644 --- a/conformance/dump_glsl.txt +++ b/conformance/dump_glsl.txt @@ -294,3 +294,6 @@ ortho: == basis directions == N/A + +== default matrix printer == +N/A diff --git a/conformance/dump_jolt.cpp b/conformance/dump_jolt.cpp index bdddad5..1f96295 100644 --- a/conformance/dump_jolt.cpp +++ b/conformance/dump_jolt.cpp @@ -80,6 +80,13 @@ static void DumpMat4(std::vector &lines, const std::string &label, AppendLine(lines, "]"); } +static void DumpMat4Default(std::vector &lines, const std::string &label, Mat44Arg value) +{ + std::ostringstream out; + out << value; + AppendLine(lines, label + ": " + out.str()); +} + static void Heading(std::vector &lines, const std::string &title) { if (!lines.empty()) @@ -312,6 +319,10 @@ int main() Heading(lines, "basis directions"); AppendLine(lines, "N/A"); + Heading(lines, "default matrix printer"); + DumpMat4Default(lines, "matrix_a.default", mat_a); + DumpMat4Default(lines, "transform.default", transform_m); + std::ofstream out(output_path, std::ios::binary); for (size_t i = 0; i < lines.size(); ++i) { diff --git a/conformance/dump_jolt.txt b/conformance/dump_jolt.txt index d5b097a..5fecae0 100644 --- a/conformance/dump_jolt.txt +++ b/conformance/dump_jolt.txt @@ -287,3 +287,7 @@ N/A == basis directions == N/A + +== default matrix printer == +matrix_a.default: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 +transform.default: 0.599374, 1.74071, 0.781462, 0, -2.49504, 0.113021, 1.66192, 0, 1.86973, -1.96393, 2.94059, 0, 10, 20, 30, 1 diff --git a/conformance/dump_vmath.nim b/conformance/dump_vmath.nim index 70fdfe7..0670976 100644 --- a/conformance/dump_vmath.nim +++ b/conformance/dump_vmath.nim @@ -58,6 +58,9 @@ proc dumpMat4(lines: var seq[string], label: string, value: Mat4) = lines.appendLine(" " & fmt(d[3]) & " " & fmt(d[7]) & " " & fmt(d[11]) & " " & fmt(d[15]) ) lines.appendLine("]") +proc dumpMat4Default(lines: var seq[string], label: string, value: Mat4) = + lines.appendLine(label & ": " & $value) + proc heading(lines: var seq[string], title: string) = if lines.len > 0: lines.appendLine() @@ -278,6 +281,10 @@ proc main() = lines.dumpVec3("right", matA.right()) lines.dumpVec3("up", matA.up()) + lines.heading("default matrix printer") + lines.dumpMat4Default("matrix_a.default", matA) + lines.dumpMat4Default("transform.default", transformM) + writeFile(OutputPath, lines.join("\n") & "\n") echo "Wrote ", OutputPath diff --git a/conformance/dump_vmath.txt b/conformance/dump_vmath.txt index 18c9dfb..0f0d011 100644 --- a/conformance/dump_vmath.txt +++ b/conformance/dump_vmath.txt @@ -296,3 +296,17 @@ ortho: forward: <+009.000, +010.000, +011.000> right: <+001.000, +002.000, +003.000> up: <+005.000, +006.000, +007.000> + +== default matrix printer == +matrix_a.default: mat4( + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 +) +transform.default: mat4( + 0.5993741, 1.7407088, 0.78146225, 0.0, + -2.4950442, 0.11302096, 1.6619208, 0.0, + 1.8697325, -1.9639301, 2.9405916, 0.0, + 10.0, 20.0, 30.0, 1.0 +) From d38e0e14e98c1c6426663f33f6d6b280a8daa720 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 08:27:26 -0700 Subject: [PATCH 20/35] f --- conformance/dump_glsl.comp | 87 ++---- conformance/dump_glsl.nim | 543 +++++++++++-------------------------- 2 files changed, 186 insertions(+), 444 deletions(-) diff --git a/conformance/dump_glsl.comp b/conformance/dump_glsl.comp index 8ebc2f5..a85caf0 100644 --- a/conformance/dump_glsl.comp +++ b/conformance/dump_glsl.comp @@ -2,95 +2,62 @@ layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; layout (rgba32f, binding = 0) uniform writeonly imageBuffer outputBuffer; +uniform mat4 uIdentityM; +uniform mat4 uMatA; +uniform mat4 uMatB; +uniform mat4 uScaleM; +uniform mat4 uTranslateM; +uniform mat4 uRotateXM; +uniform mat4 uRotateYM; +uniform mat4 uRotateZM; +uniform mat4 uAxisMat; +uniform mat4 uHardMat; +uniform vec3 uVecA; +uniform vec4 uVecB; + const float angleA = radians(37.0); const float angleB = radians(-23.0); const float angleC = radians(71.0); const float axisAngle = radians(48.0); mat4 identityM() { - return mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); + return uIdentityM; } mat4 matA() { - return mat4( - vec4(1.0, 2.0, 3.0, 4.0), - vec4(5.0, 6.0, 7.0, 8.0), - vec4(9.0, 10.0, 11.0, 12.0), - vec4(13.0, 14.0, 15.0, 16.0) - ); + return uMatA; } mat4 matB() { - return mat4( - vec4(-10.0, -20.0, -30.0, -40.0), - vec4(50.0, 60.0, 70.0, 80.0), - vec4(90.0, 100.0, 110.0, 120.0), - vec4(130.0, 140.0, 150.0, 160.0) - ); + return uMatB; } vec3 vecA() { - return vec3(1.25, -2.5, 3.75); + return uVecA; } vec4 vecB() { - return vec4(1.25, -2.5, 3.75, 1.0); + return uVecB; } mat4 scaleM() { - return mat4( - vec4(2.0, 0.0, 0.0, 0.0), - vec4(0.0, 3.0, 0.0, 0.0), - vec4(0.0, 0.0, 4.0, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); + return uScaleM; } mat4 translateM() { - return mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(10.0, 20.0, 30.0, 1.0) - ); + return uTranslateM; } mat4 rotateXM() { - float s = sin(angleA); - float c = cos(angleA); - return mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, c, s, 0.0), - vec4(0.0, -s, c, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); + return uRotateXM; } mat4 rotateYM() { - float s = sin(angleB); - float c = cos(angleB); - return mat4( - vec4(c, 0.0, -s, 0.0), - vec4(0.0, 1.0, 0.0, 0.0), - vec4(s, 0.0, c, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); + return uRotateYM; } mat4 rotateZM() { - float s = sin(angleC); - float c = cos(angleC); - return mat4( - vec4(c, s, 0.0, 0.0), - vec4(-s, c, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); + return uRotateZM; } mat4 pureRotationM() { @@ -216,7 +183,7 @@ vec4 mat4Quat(mat4 m) { } mat4 axisMat() { - return quatMat4(axisQuat()); + return uAxisMat; } mat4 transformM() { @@ -237,7 +204,7 @@ vec4 hardQuat() { } mat4 hardMat() { - return quatMat4(hardQuat()); + return uHardMat; } mat4 frustumM(float left, float right, float bottom, float top, float nearV, float farV) { @@ -367,7 +334,5 @@ vec3 quatToAngles(vec4 q) { } void main() { - uint index = gl_GlobalInvocationID.x; - mat4 value = orthoM(-10.0, 10.0, -7.5, 7.5, 0.1, 100.0); - imageStore(outputBuffer, int(index), value[int(index)]); + __MAIN_BODY__ } diff --git a/conformance/dump_glsl.nim b/conformance/dump_glsl.nim index 8cf8363..3d02ea3 100644 --- a/conformance/dump_glsl.nim +++ b/conformance/dump_glsl.nim @@ -64,383 +64,166 @@ proc heading(lines: var seq[string], title: string) = lines.appendLine() lines.appendLine("== " & title & " ==") -proc glslPrelude(): string = - result = """ -#version 430 -layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -layout (rgba32f, binding = 0) uniform writeonly imageBuffer outputBuffer; - -const float angleA = radians(37.0); -const float angleB = radians(-23.0); -const float angleC = radians(71.0); -const float axisAngle = radians(48.0); - -mat4 identityM() { - return mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); -} - -mat4 matA() { - return mat4( - vec4(1.0, 2.0, 3.0, 4.0), - vec4(5.0, 6.0, 7.0, 8.0), - vec4(9.0, 10.0, 11.0, 12.0), - vec4(13.0, 14.0, 15.0, 16.0) - ); -} - -mat4 matB() { - return mat4( - vec4(-10.0, -20.0, -30.0, -40.0), - vec4(50.0, 60.0, 70.0, 80.0), - vec4(90.0, 100.0, 110.0, 120.0), - vec4(130.0, 140.0, 150.0, 160.0) - ); -} - -vec3 vecA() { - return vec3(1.25, -2.5, 3.75); -} - -vec4 vecB() { - return vec4(1.25, -2.5, 3.75, 1.0); -} - -mat4 scaleM() { - return mat4( - vec4(2.0, 0.0, 0.0, 0.0), - vec4(0.0, 3.0, 0.0, 0.0), - vec4(0.0, 0.0, 4.0, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); -} - -mat4 translateM() { - return mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(10.0, 20.0, 30.0, 1.0) - ); -} - -mat4 rotateXM() { - float s = sin(angleA); - float c = cos(angleA); - return mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, c, s, 0.0), - vec4(0.0, -s, c, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); -} - -mat4 rotateYM() { - float s = sin(angleB); - float c = cos(angleB); - return mat4( - vec4(c, 0.0, -s, 0.0), - vec4(0.0, 1.0, 0.0, 0.0), - vec4(s, 0.0, c, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); -} - -mat4 rotateZM() { - float s = sin(angleC); - float c = cos(angleC); - return mat4( - vec4(c, s, 0.0, 0.0), - vec4(-s, c, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); -} - -mat4 pureRotationM() { - return rotateZM() * rotateYM() * rotateXM(); -} - -vec3 axisNormalized() { - return normalize(vec3(1.0, 2.0, -3.0)); -} - -vec4 quatIdentity() { - return vec4(0.0, 0.0, 0.0, 1.0); -} - -vec4 quatAxisAngle(vec3 axis, float angle) { - vec3 a = normalize(axis); - float s = sin(angle * 0.5); - return vec4(a * s, cos(angle * 0.5)); -} - -vec4 quatX() { - return quatAxisAngle(vec3(1.0, 0.0, 0.0), angleA); -} - -vec4 quatY() { - return quatAxisAngle(vec3(0.0, 1.0, 0.0), angleB); -} - -vec4 quatZ() { - return quatAxisAngle(vec3(0.0, 0.0, 1.0), angleC); -} - -vec4 axisQuat() { - return quatAxisAngle(axisNormalized(), axisAngle); -} - -vec4 quatMultiply(vec4 a, vec4 b) { - return vec4( - a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, - a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z, - a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x, - a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z - ); -} - -vec3 quatRotate(vec4 q, vec3 v) { - vec3 uv = cross(q.xyz, v); - vec3 uuv = cross(q.xyz, uv); - return v + ((uv * q.w) + uuv) * 2.0; -} - -mat4 quatMat4(vec4 q) { - float xx = q.x * q.x; - float xy = q.x * q.y; - float xz = q.x * q.z; - float xw = q.x * q.w; - float yy = q.y * q.y; - float yz = q.y * q.z; - float yw = q.y * q.w; - float zz = q.z * q.z; - float zw = q.z * q.w; - - return mat4( - vec4(1.0 - 2.0 * (yy + zz), 2.0 * (xy + zw), 2.0 * (xz - yw), 0.0), - vec4(2.0 * (xy - zw), 1.0 - 2.0 * (xx + zz), 2.0 * (yz + xw), 0.0), - vec4(2.0 * (xz + yw), 2.0 * (yz - xw), 1.0 - 2.0 * (xx + yy), 0.0), - vec4(0.0, 0.0, 0.0, 1.0) - ); -} - -vec4 mat4Quat(mat4 m) { - float m00 = m[0][0]; - float m11 = m[1][1]; - float m22 = m[2][2]; - - float fourXSquaredMinus1 = m00 - m11 - m22; - float fourYSquaredMinus1 = m11 - m00 - m22; - float fourZSquaredMinus1 = m22 - m00 - m11; - float fourWSquaredMinus1 = m00 + m11 + m22; - - int biggestIndex = 0; - float fourBiggestSquaredMinus1 = fourWSquaredMinus1; - if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { - fourBiggestSquaredMinus1 = fourXSquaredMinus1; - biggestIndex = 1; - } - if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { - fourBiggestSquaredMinus1 = fourYSquaredMinus1; - biggestIndex = 2; - } - if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { - fourBiggestSquaredMinus1 = fourZSquaredMinus1; - biggestIndex = 3; - } - - float biggestVal = sqrt(fourBiggestSquaredMinus1 + 1.0) * 0.5; - float mult = 0.25 / biggestVal; - vec4 q = vec4(0.0); - - if (biggestIndex == 0) { - q.w = biggestVal; - q.x = (m[1][2] - m[2][1]) * mult; - q.y = (m[2][0] - m[0][2]) * mult; - q.z = (m[0][1] - m[1][0]) * mult; - } else if (biggestIndex == 1) { - q.w = (m[1][2] - m[2][1]) * mult; - q.x = biggestVal; - q.y = (m[0][1] + m[1][0]) * mult; - q.z = (m[2][0] + m[0][2]) * mult; - } else if (biggestIndex == 2) { - q.w = (m[2][0] - m[0][2]) * mult; - q.x = (m[0][1] + m[1][0]) * mult; - q.y = biggestVal; - q.z = (m[1][2] + m[2][1]) * mult; - } else { - q.w = (m[0][1] - m[1][0]) * mult; - q.x = (m[2][0] + m[0][2]) * mult; - q.y = (m[1][2] + m[2][1]) * mult; - q.z = biggestVal; - } - - return q; -} - -mat4 axisMat() { - return quatMat4(axisQuat()); -} - -mat4 transformM() { - return translateM() * rotateZM() * rotateYM() * rotateXM() * scaleM(); -} - -mat4 rotationOnly(mat4 m) { - m[3] = vec4(0.0, 0.0, 0.0, 1.0); - return m; -} - -vec3 hardAxis() { - return normalize(vec3(1.0, -2.0, 3.0)); -} - -vec4 hardQuat() { - return quatAxisAngle(hardAxis(), radians(170.0)); -} - -mat4 hardMat() { - return quatMat4(hardQuat()); -} - -mat4 frustumM(float left, float right, float bottom, float top, float nearV, float farV) { - float rl = right - left; - float tb = top - bottom; - float fn = farV - nearV; - return mat4( - vec4((nearV * 2.0) / rl, 0.0, 0.0, 0.0), - vec4(0.0, (nearV * 2.0) / tb, 0.0, 0.0), - vec4((right + left) / rl, (top + bottom) / tb, -(farV + nearV) / fn, -1.0), - vec4(0.0, 0.0, -(farV * nearV * 2.0) / fn, 0.0) - ); -} - -mat4 perspectiveM(float fovyDeg, float aspect, float nearV, float farV) { - float top = nearV * tan(fovyDeg * 3.14159265359 / 360.0); - float right = top * aspect; - return frustumM(-right, right, -top, top, nearV, farV); -} - -mat4 orthoM(float left, float right, float bottom, float top, float nearV, float farV) { - float rl = right - left; - float tb = top - bottom; - float fn = farV - nearV; - return mat4( - vec4(2.0 / rl, 0.0, 0.0, 0.0), - vec4(0.0, 2.0 / tb, 0.0, 0.0), - vec4(0.0, 0.0, -2.0 / fn, 0.0), - vec4(-(left + right) / rl, -(top + bottom) / tb, -(farV + nearV) / fn, 1.0) - ); -} - -mat4 lookAtM(vec3 eye, vec3 center, vec3 up) { - if (all(equal(eye, center))) { - return identityM(); - } - - vec3 z = normalize(eye - center); - vec3 x = cross(up, z); - if (length(x) == 0.0) { - x = vec3(0.0); - } else { - x = normalize(x); - } - vec3 y = cross(z, x); - - return mat4( - vec4(x.x, y.x, z.x, 0.0), - vec4(x.y, y.y, z.y, 0.0), - vec4(x.z, y.z, z.z, 0.0), - vec4(-dot(x, eye), -dot(y, eye), -dot(z, eye), 1.0) - ); -} - -vec4 quatInverse(vec4 q) { - float d = dot(q, q); - return vec4(-q.x / d, -q.y / d, -q.z / d, q.w / d); -} - -vec3 orthogonalV(vec3 v) { - vec3 av = abs(v); - vec3 other; - if (av.x < av.y) { - other = av.x < av.z ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 0.0, 1.0); - } else { - other = av.y < av.z ? vec3(0.0, 1.0, 0.0) : vec3(0.0, 0.0, 1.0); - } - return cross(av, other); -} - -vec4 fromTwoVectorsQ(vec3 a, vec3 b) { - vec3 u = normalize(b); - vec3 v = normalize(a); - if (all(equal(u, -v))) { - vec3 q = normalize(orthogonalV(u)); - return vec4(q.x, q.y, q.z, 0.0); - } - vec3 halfV = normalize(u + v); - vec3 q = cross(v, halfV); - float w = dot(v, halfV); - return vec4(q.x, q.y, q.z, w); -} - -vec4 slerpQ(vec4 a, vec4 b, float t) { - vec4 z = b; - float cosTheta = dot(a, b); - if (cosTheta < 0.0) { - z = -b; - cosTheta = -cosTheta; - } - if (cosTheta > 1.0 - 1e-6) { - return vec4( - a.x + (z.x - a.x) * t, - a.y + (z.y - a.y) * t, - a.z + (z.z - a.z) * t, - a.w + (z.w - a.w) * t - ); - } else { - float angle = acos(cosTheta); - return (sin((1.0 - t) * angle) * a + sin(t * angle) * z) / sin(angle); - } -} - -vec3 quatAxisOnly(vec4 q) { - float cosAngle = q.w; - float sinAngle = sqrt(max(0.0, 1.0 - cosAngle * cosAngle)); - if (abs(sinAngle) < 0.0005) { - sinAngle = 1.0; - } - return vec3(q.x / sinAngle, q.y / sinAngle, q.z / sinAngle); -} - -float quatAngleOnly(vec4 q) { - return acos(q.w) * 2.0; -} - -vec3 quatToAngles(vec4 q) { - float x = q.x; - float y = q.y; - float z = q.z; - float w = q.w; - return vec3( - atan(2.0 * (w * x + y * z), 1.0 - 2.0 * (x * x + y * y)), - asin(2.0 * (w * y - z * x)), - atan(2.0 * (w * z + x * y), 1.0 - 2.0 * (y * y + z * z)) - ); -} -""" +proc shaderTemplate(mainBody: string): string = + readFile(ShaderPath).replace("__MAIN_BODY__", mainBody) + +proc setUniformMat4(program: GLuint, name: string, value: openArray[float32]) = + let location = glGetUniformLocation(program, name) + if location < 0: + return + doAssert value.len == 16 + glUniformMatrix4fv(location, 1, GLboolean(GL_FALSE), cast[ptr GLfloat](unsafeAddr value[0])) + +proc setUniformVec3(program: GLuint, name: string, value: openArray[float32]) = + let location = glGetUniformLocation(program, name) + if location < 0: + return + doAssert value.len == 3 + glUniform3f(location, value[0], value[1], value[2]) + +proc setUniformVec4(program: GLuint, name: string, value: openArray[float32]) = + let location = glGetUniformLocation(program, name) + if location < 0: + return + doAssert value.len == 4 + glUniform4f(location, value[0], value[1], value[2], value[3]) + +proc uploadDumpUniforms(program: GLuint) = + let + angleA = degToRad(37.0f) + angleB = degToRad(-23.0f) + angleC = degToRad(71.0f) + axisAngle = degToRad(48.0f) + hardAngle = degToRad(170.0f) + rotateXSin = sin(angleA) + rotateXCos = cos(angleA) + rotateYSin = sin(angleB) + rotateYCos = cos(angleB) + rotateZSin = sin(angleC) + rotateZCos = cos(angleC) + axisLenInv = 1.0f / sqrt(1.0f * 1.0f + 2.0f * 2.0f + (-3.0f) * (-3.0f)) + axisX = 1.0f * axisLenInv + axisY = 2.0f * axisLenInv + axisZ = -3.0f * axisLenInv + axisQuatSin = sin(axisAngle * 0.5f) + axisQuatCos = cos(axisAngle * 0.5f) + axisQuat = [ + axisX * axisQuatSin, + axisY * axisQuatSin, + axisZ * axisQuatSin, + axisQuatCos + ] + hardAxisLenInv = 1.0f / sqrt(1.0f * 1.0f + (-2.0f) * (-2.0f) + 3.0f * 3.0f) + hardAxisX = 1.0f * hardAxisLenInv + hardAxisY = -2.0f * hardAxisLenInv + hardAxisZ = 3.0f * hardAxisLenInv + hardQuatSin = sin(hardAngle * 0.5f) + hardQuatCos = cos(hardAngle * 0.5f) + hardQuat = [ + hardAxisX * hardQuatSin, + hardAxisY * hardQuatSin, + hardAxisZ * hardQuatSin, + hardQuatCos + ] + axisQuatXX = axisQuat[0] * axisQuat[0] + axisQuatXY = axisQuat[0] * axisQuat[1] + axisQuatXZ = axisQuat[0] * axisQuat[2] + axisQuatXW = axisQuat[0] * axisQuat[3] + axisQuatYY = axisQuat[1] * axisQuat[1] + axisQuatYZ = axisQuat[1] * axisQuat[2] + axisQuatYW = axisQuat[1] * axisQuat[3] + axisQuatZZ = axisQuat[2] * axisQuat[2] + axisQuatZW = axisQuat[2] * axisQuat[3] + hardQuatXX = hardQuat[0] * hardQuat[0] + hardQuatXY = hardQuat[0] * hardQuat[1] + hardQuatXZ = hardQuat[0] * hardQuat[2] + hardQuatXW = hardQuat[0] * hardQuat[3] + hardQuatYY = hardQuat[1] * hardQuat[1] + hardQuatYZ = hardQuat[1] * hardQuat[2] + hardQuatYW = hardQuat[1] * hardQuat[3] + hardQuatZZ = hardQuat[2] * hardQuat[2] + hardQuatZW = hardQuat[2] * hardQuat[3] + uIdentityM = [ + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ] + uMatA = [ + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f + ] + uMatB = [ + -10.0f, -20.0f, -30.0f, -40.0f, + 50.0f, 60.0f, 70.0f, 80.0f, + 90.0f, 100.0f, 110.0f, 120.0f, + 130.0f, 140.0f, 150.0f, 160.0f + ] + uScaleM = [ + 2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 3.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 4.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ] + uTranslateM = [ + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 10.0f, 20.0f, 30.0f, 1.0f + ] + uRotateXM = [ + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, rotateXCos, rotateXSin, 0.0f, + 0.0f, -rotateXSin, rotateXCos, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ] + uRotateYM = [ + rotateYCos, 0.0f, -rotateYSin, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + rotateYSin, 0.0f, rotateYCos, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ] + uRotateZM = [ + rotateZCos, rotateZSin, 0.0f, 0.0f, + -rotateZSin, rotateZCos, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ] + uAxisMat = [ + 1.0f - 2.0f * (axisQuatYY + axisQuatZZ), 2.0f * (axisQuatXY + axisQuatZW), 2.0f * (axisQuatXZ - axisQuatYW), 0.0f, + 2.0f * (axisQuatXY - axisQuatZW), 1.0f - 2.0f * (axisQuatXX + axisQuatZZ), 2.0f * (axisQuatYZ + axisQuatXW), 0.0f, + 2.0f * (axisQuatXZ + axisQuatYW), 2.0f * (axisQuatYZ - axisQuatXW), 1.0f - 2.0f * (axisQuatXX + axisQuatYY), 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ] + uHardMat = [ + 1.0f - 2.0f * (hardQuatYY + hardQuatZZ), 2.0f * (hardQuatXY + hardQuatZW), 2.0f * (hardQuatXZ - hardQuatYW), 0.0f, + 2.0f * (hardQuatXY - hardQuatZW), 1.0f - 2.0f * (hardQuatXX + hardQuatZZ), 2.0f * (hardQuatYZ + hardQuatXW), 0.0f, + 2.0f * (hardQuatXZ + hardQuatYW), 2.0f * (hardQuatYZ - hardQuatXW), 1.0f - 2.0f * (hardQuatXX + hardQuatYY), 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ] + uVecA = [1.25f, -2.5f, 3.75f] + uVecB = [1.25f, -2.5f, 3.75f, 1.0f] + setUniformMat4(program, "uIdentityM", uIdentityM) + setUniformMat4(program, "uMatA", uMatA) + setUniformMat4(program, "uMatB", uMatB) + setUniformMat4(program, "uScaleM", uScaleM) + setUniformMat4(program, "uTranslateM", uTranslateM) + setUniformMat4(program, "uRotateXM", uRotateXM) + setUniformMat4(program, "uRotateYM", uRotateYM) + setUniformMat4(program, "uRotateZM", uRotateZM) + setUniformMat4(program, "uAxisMat", uAxisMat) + setUniformMat4(program, "uHardMat", uHardMat) + setUniformVec3(program, "uVecA", uVecA) + setUniformVec4(program, "uVecB", uVecB) proc runComputeShader(shaderSrc: string, invocationCount: int): seq[float32] = - writeFile(ShaderPath, shaderSrc) initOffscreenWindow() let shaderId = compileComputeShader((ShaderPath, shaderSrc)) glUseProgram(shaderId) + uploadDumpUniforms(shaderId) var outputBufferId: GLuint @@ -470,31 +253,25 @@ proc runComputeShader(shaderSrc: string, invocationCount: int): seq[float32] = glDeleteProgram(shaderId) proc runMatrix(expr: string): seq[float32] = - let shaderSrc = glslPrelude() & "\n" & fmt""" -void main() {{ + let shaderSrc = shaderTemplate(fmt""" uint index = gl_GlobalInvocationID.x; mat4 value = {expr}; imageStore(outputBuffer, int(index), value[int(index)]); -}} -""" +""") runComputeShader(shaderSrc, 4) proc runVec3(expr: string): seq[float32] = - let shaderSrc = glslPrelude() & "\n" & fmt""" -void main() {{ + let shaderSrc = shaderTemplate(fmt""" vec3 value = {expr}; imageStore(outputBuffer, 0, vec4(value, 0.0)); -}} -""" +""") runComputeShader(shaderSrc, 1) proc runVec4(expr: string): seq[float32] = - let shaderSrc = glslPrelude() & "\n" & fmt""" -void main() {{ + let shaderSrc = shaderTemplate(fmt""" vec4 value = {expr}; imageStore(outputBuffer, 0, value); -}} -""" +""") runComputeShader(shaderSrc, 1) proc runScalar(expr: string): float32 = From 9390666070a5b5fe3c21b812efa7b3e9ff240b6e Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 08:52:37 -0700 Subject: [PATCH 21/35] undo --- src/vmath.nim | 58 +++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/vmath.nim b/src/vmath.nim index d234c65..eac172d 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -121,16 +121,16 @@ when defined(vmathArrayBased): [m30, m31, m32, m33] ] - template `[]`*[T](a: GMat234[T], i, j: int): T = a[j][i] + template `[]`*[T](a: GMat234[T], i, j: int): T = a[i][j] template `[]=`*[T](a: var GMat2[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (j * 2 + i) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (i * 2 + j) * sizeof(T))[] = v template `[]=`*[T](a: var GMat3[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (j * 3 + i) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (i * 3 + j) * sizeof(T))[] = v template `[]=`*[T](a: var GMat4[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (j * 4 + i) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (i * 4 + j) * sizeof(T))[] = v elif defined(vmathObjBased): type @@ -207,42 +207,42 @@ elif defined(vmathObjBased): result.m30 = m30; result.m31 = m31; result.m32 = m32; result.m33 = m33 template `[]`*[T](a: GMat2[T], i, j: int): T = - cast[array[4, T]](a)[j * 2 + i] + cast[array[4, T]](a)[i * 2 + j] template `[]`*[T](a: GMat3[T], i, j: int): T = - cast[array[9, T]](a)[j * 3 + i] + cast[array[9, T]](a)[i * 3 + j] template `[]`*[T](a: GMat4[T], i, j: int): T = - cast[array[16, T]](a)[j * 4 + i] + cast[array[16, T]](a)[i * 4 + j] template `[]=`*[T](a: var GMat2[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (j * 2 + i) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (i * 2 + j) * sizeof(T))[] = v template `[]=`*[T](a: var GMat3[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (j * 3 + i) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (i * 3 + j) * sizeof(T))[] = v template `[]=`*[T](a: var GMat4[T], i, j: int, v: T) = - cast[ptr T](cast[ByteAddress](a.addr) + (j * 4 + i) * sizeof(T))[] = v + cast[ptr T](cast[ByteAddress](a.addr) + (i * 4 + j) * sizeof(T))[] = v template `[]`*[T](a: GMat2[T], i: int): GVec2[T] = gvec2[T]( - a[0, i], - a[1, i] + a[i, 0], + a[i, 1] ) template `[]`*[T](a: GMat3[T], i: int): GVec3[T] = gvec3[T]( - a[0, i], - a[1, i], - a[2, i] + a[i, 0], + a[i, 1], + a[i, 2] ) template `[]`*[T](a: GMat4[T], i: int): GVec4[T] = gvec4[T]( - a[0, i], - a[1, i], - a[2, i], - a[3, i] + a[i, 0], + a[i, 1], + a[i, 2], + a[i, 3] ) elif true or defined(vmathObjArrayBased): @@ -303,7 +303,7 @@ elif true or defined(vmathObjArrayBased): GMat3*[T] {.bycopy.} = object arr: array[9, T] GMat4*[T] {.bycopy.} = object - arr*: array[16, T] + arr: array[16, T] proc gmat2*[T]( m00, m01, @@ -348,23 +348,23 @@ elif true or defined(vmathObjArrayBased): template `[]`*[T](a: GMat2[T], i: int): GVec2[T] = gvec2[T]( - a[0, i], - a[1, i] + a[i, 0], + a[i, 1] ) template `[]`*[T](a: GMat3[T], i: int): GVec3[T] = gvec3[T]( - a[0, i], - a[1, i], - a[2, i] + a[i, 0], + a[i, 1], + a[i, 2] ) template `[]`*[T](a: GMat4[T], i: int): GVec4[T] = gvec4[T]( - a[0, i], - a[1, i], - a[2, i], - a[3, i] + a[i, 0], + a[i, 1], + a[i, 2], + a[i, 3] ) type From 657e5d20a953d25d93279fb302dabb3500466d7e Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 09:11:44 -0700 Subject: [PATCH 22/35] no change --- conformance/dump_vmath.nim | 2 +- src/vmath.nim | 292 +++++++++++++++++-------------------- 2 files changed, 136 insertions(+), 158 deletions(-) diff --git a/conformance/dump_vmath.nim b/conformance/dump_vmath.nim index 0670976..a3e5500 100644 --- a/conformance/dump_vmath.nim +++ b/conformance/dump_vmath.nim @@ -1,6 +1,6 @@ import std/[math, os, strutils], - vmath {.all.} + ../src/vmath {.all.} const OutputPath = parentDir(currentSourcePath()) / "dump_vmath.txt" diff --git a/src/vmath.nim b/src/vmath.nim index eac172d..377b769 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -925,14 +925,17 @@ template genMatConstructor*(lower, upper, T: untyped) = m00, m01, m10, m11: T ): `upper 2` = - gmat2[T](m00, m01, m10, m11) + result[0, 0] = m00; result[0, 1] = m01 + result[1, 0] = m10; result[1, 1] = m11 proc `lower 3`*( m00, m01, m02, m10, m11, m12, m20, m21, m22: T ): `upper 3` = - gmat3[T](m00, m01, m02, m10, m11, m12, m20, m21, m22) + result[0, 0] = m00; result[0, 1] = m01; result[0, 2] = m02 + result[1, 0] = m10; result[1, 1] = m11; result[1, 2] = m12 + result[2, 0] = m20; result[2, 1] = m21; result[2, 2] = m22 proc `lower 4`*( m00, m01, m02, m03, @@ -940,7 +943,17 @@ template genMatConstructor*(lower, upper, T: untyped) = m20, m21, m22, m23, m30, m31, m32, m33: T ): `upper 4` = - gmat4[T](m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) + result[0, 0] = m00; result[0, 1] = m01 + result[0, 2] = m02; result[0, 3] = m03 + + result[1, 0] = m10; result[1, 1] = m11 + result[1, 2] = m12; result[1, 3] = m13 + + result[2, 0] = m20; result[2, 1] = m21 + result[2, 2] = m22; result[2, 3] = m23 + + result[3, 0] = m30; result[3, 1] = m31 + result[3, 2] = m32; result[3, 3] = m33 proc `lower 2`*(a, b: GVec2[T]): `upper 2` = gmat2[T]( @@ -997,7 +1010,7 @@ proc `~=`*[T](a, b: GMat4[T]): bool = a[0] ~= b[0] and a[1] ~= b[1] and a[2] ~= b[2] and a[3] ~= b[3] proc pos*[T](a: GMat3[T]): GVec2[T] = - gvec2[T](a[2, 0], a[2, 1]) + gvec2[T](a[2].x, a[2].y) proc `pos=`*[T](a: var GMat3[T], pos: GVec2[T]) = a[2, 0] = pos.x @@ -1014,14 +1027,14 @@ proc back*[T](a: GMat4[T]): GVec3[T] {.inline.} = -a.forward() proc left*[T](a: GMat4[T]): GVec3[T] {.inline.} = - ## Vector facing -X. - -a.right() + ## 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. - result.x = a[0, 0] - result.y = a[0, 1] - result.z = a[0, 2] + ## Vector facing -X. + -a.left() proc up*[T](a: GMat4[T]): GVec3[T] {.inline.} = ## Vector facing +Y. @@ -1044,39 +1057,17 @@ proc `pos=`*[T](a: var GMat4[T], pos: GVec3[T]) = a[3, 2] = pos.z proc `*`*[T](a, b: GMat3[T]): GMat3[T] = - let - a00 = a[0, 0] - a10 = a[1, 0] - a20 = a[2, 0] - a01 = a[0, 1] - a11 = a[1, 1] - a21 = a[2, 1] - a02 = a[0, 2] - a12 = a[1, 2] - a22 = a[2, 2] + result[0, 0] = b[0, 0] * a[0, 0] + b[0, 1] * a[1, 0] + b[0, 2] * a[2, 0] + result[0, 1] = b[0, 0] * a[0, 1] + b[0, 1] * a[1, 1] + b[0, 2] * a[2, 1] + result[0, 2] = b[0, 0] * a[0, 2] + b[0, 1] * a[1, 2] + b[0, 2] * a[2, 2] - let - b00 = b[0, 0] - b10 = b[1, 0] - b20 = b[2, 0] - b01 = b[0, 1] - b11 = b[1, 1] - b21 = b[2, 1] - b02 = b[0, 2] - b12 = b[1, 2] - b22 = b[2, 2] - - result[0, 0] = a00 * b00 + a01 * b10 + a02 * b20 - result[1, 0] = a10 * b00 + a11 * b10 + a12 * b20 - result[2, 0] = a20 * b00 + a21 * b10 + a22 * b20 + result[1, 0] = b[1, 0] * a[0, 0] + b[1, 1] * a[1, 0] + b[1, 2] * a[2, 0] + result[1, 1] = b[1, 0] * a[0, 1] + b[1, 1] * a[1, 1] + b[1, 2] * a[2, 1] + result[1, 2] = b[1, 0] * a[0, 2] + b[1, 1] * a[1, 2] + b[1, 2] * a[2, 2] - result[0, 1] = a00 * b01 + a01 * b11 + a02 * b21 - result[1, 1] = a10 * b01 + a11 * b11 + a12 * b21 - result[2, 1] = a20 * b01 + a21 * b11 + a22 * b21 - - result[0, 2] = a00 * b02 + a01 * b12 + a02 * b22 - result[1, 2] = a10 * b02 + a11 * b12 + a12 * b22 - result[2, 2] = a20 * b02 + a21 * b12 + a22 * b22 + result[2, 0] = b[2, 0] * a[0, 0] + b[2, 1] * a[1, 0] + b[2, 2] * a[2, 0] + result[2, 1] = b[2, 0] * a[0, 1] + b[2, 1] * a[1, 1] + b[2, 2] * a[2, 1] + result[2, 2] = b[2, 0] * a[0, 2] + b[2, 1] * a[1, 2] + b[2, 2] * a[2, 2] proc `*`*[T](a: GMat2[T], b: GVec2[T]): GVec2[T] = gvec2[T]( @@ -1099,60 +1090,60 @@ proc `*`*[T](a: GMat3[T], b: GVec3[T]): GVec3[T] = proc `*`*[T](a, b: GMat4[T]): GMat4[T] = let - a00 = b[0, 0] - a10 = b[1, 0] - a20 = b[2, 0] - a30 = b[3, 0] - a01 = b[0, 1] - a11 = b[1, 1] - a21 = b[2, 1] - a31 = b[3, 1] - a02 = b[0, 2] - a12 = b[1, 2] - a22 = b[2, 2] - a32 = b[3, 2] - a03 = b[0, 3] - a13 = b[1, 3] - a23 = b[2, 3] - a33 = b[3, 3] + a00 = a[0, 0] + a01 = a[0, 1] + a02 = a[0, 2] + a03 = a[0, 3] + a10 = a[1, 0] + a11 = a[1, 1] + a12 = a[1, 2] + a13 = a[1, 3] + a20 = a[2, 0] + a21 = a[2, 1] + a22 = a[2, 2] + a23 = a[2, 3] + a30 = a[3, 0] + a31 = a[3, 1] + a32 = a[3, 2] + a33 = a[3, 3] let - b00 = a[0, 0] - b10 = a[1, 0] - b20 = a[2, 0] - b30 = a[3, 0] - b01 = a[0, 1] - b11 = a[1, 1] - b21 = a[2, 1] - b31 = a[3, 1] - b02 = a[0, 2] - b12 = a[1, 2] - b22 = a[2, 2] - b32 = a[3, 2] - b03 = a[0, 3] - b13 = a[1, 3] - b23 = a[2, 3] - b33 = a[3, 3] - - result[0, 0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30 - result[1, 0] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30 - result[2, 0] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30 - result[3, 0] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30 - - result[0, 1] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31 - result[1, 1] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31 - result[2, 1] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31 - result[3, 1] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31 - - result[0, 2] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32 - result[1, 2] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32 - result[2, 2] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32 - result[3, 2] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32 - - result[0, 3] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33 - result[1, 3] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33 - result[2, 3] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33 - result[3, 3] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33 + b00 = b[0, 0] + b01 = b[0, 1] + b02 = b[0, 2] + b03 = b[0, 3] + b10 = b[1, 0] + b11 = b[1, 1] + b12 = b[1, 2] + b13 = b[1, 3] + b20 = b[2, 0] + b21 = b[2, 1] + b22 = b[2, 2] + b23 = b[2, 3] + b30 = b[3, 0] + b31 = b[3, 1] + b32 = b[3, 2] + b33 = b[3, 3] + + result[0, 0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30 + result[0, 1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31 + result[0, 2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32 + result[0, 3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33 + + result[1, 0] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30 + result[1, 1] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31 + result[1, 2] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32 + result[1, 3] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33 + + result[2, 0] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30 + result[2, 1] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31 + result[2, 2] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32 + result[2, 3] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33 + + result[3, 0] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30 + result[3, 1] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31 + result[3, 2] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32 + result[3, 3] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33 proc `*`*[T](a: GMat4[T], b: GVec3[T]): GVec3[T] = gvec3[T]( @@ -1170,7 +1161,7 @@ proc `*`*[T](a: GMat4[T], b: GVec4[T]): GVec4[T] = ) proc transpose*[T](a: GMat3[T]): GMat3[T] = - ## Return a transpose of the matrix. + ## Return an transpose of the matrix. gmat3[T]( a[0, 0], a[1, 0], a[2, 0], a[0, 1], a[1, 1], a[2, 1], @@ -1178,7 +1169,7 @@ proc transpose*[T](a: GMat3[T]): GMat3[T] = ) proc transpose*[T](a: GMat4[T]): GMat4[T] = - ## Return a transpose of the matrix. + ## Return an transpose of the matrix. gmat4[T]( a[0, 0], a[1, 0], a[2, 0], a[3, 0], a[0, 1], a[1, 1], a[2, 1], a[3, 1], @@ -1189,29 +1180,29 @@ proc transpose*[T](a: GMat4[T]): GMat4[T] = proc determinant*[T](a: GMat3[T]): T = ## Compute a determinant of the matrix. ( - a[0, 0] * (a[1, 1] * a[2, 2] - a[1, 2] * a[2, 1]) - - a[1, 0] * (a[0, 1] * a[2, 2] - a[2, 1] * a[0, 2]) + - a[2, 0] * (a[0, 1] * a[1, 2] - a[1, 1] * a[0, 2]) + a[0, 0] * (a[1, 1] * a[2, 2] - a[2, 1] * a[1, 2]) - + a[0, 1] * (a[1, 0] * a[2, 2] - a[1, 2] * a[2, 0]) + + a[0, 2] * (a[1, 0] * a[2, 1] - a[1, 1] * a[2, 0]) ) proc determinant*[T](a: GMat4[T]): T = ## Compute a determinant of the matrix. let a00 = a[0, 0] - a01 = a[1, 0] - a02 = a[2, 0] - a03 = a[3, 0] - a10 = a[0, 1] + a01 = a[0, 1] + a02 = a[0, 2] + a03 = a[0, 3] + a10 = a[1, 0] a11 = a[1, 1] - a12 = a[2, 1] - a13 = a[3, 1] - a20 = a[0, 2] - a21 = a[1, 2] + a12 = a[1, 2] + a13 = a[1, 3] + a20 = a[2, 0] + a21 = a[2, 1] a22 = a[2, 2] - a23 = a[3, 2] - a30 = a[0, 3] - a31 = a[1, 3] - a32 = a[2, 3] + a23 = a[2, 3] + a30 = a[3, 0] + a31 = a[3, 1] + a32 = a[3, 2] a33 = a[3, 3] ( a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + @@ -1227,36 +1218,36 @@ proc inverse*[T](a: GMat3[T]): GMat3[T] = let invDet = 1 / a.determinant - result[0, 0] = +(a[1, 1] * a[2, 2] - a[1, 2] * a[2, 1]) * invDet + result[0, 0] = +(a[1, 1] * a[2, 2] - a[2, 1] * a[1, 2]) * invDet result[0, 1] = -(a[0, 1] * a[2, 2] - a[0, 2] * a[2, 1]) * invDet result[0, 2] = +(a[0, 1] * a[1, 2] - a[0, 2] * a[1, 1]) * invDet result[1, 0] = -(a[1, 0] * a[2, 2] - a[1, 2] * a[2, 0]) * invDet result[1, 1] = +(a[0, 0] * a[2, 2] - a[0, 2] * a[2, 0]) * invDet - result[1, 2] = -(a[0, 0] * a[1, 2] - a[0, 2] * a[1, 0]) * invDet + result[1, 2] = -(a[0, 0] * a[1, 2] - a[1, 0] * a[0, 2]) * invDet result[2, 0] = +(a[1, 0] * a[2, 1] - a[2, 0] * a[1, 1]) * invDet - result[2, 1] = -(a[0, 0] * a[2, 1] - a[0, 1] * a[2, 0]) * invDet - result[2, 2] = +(a[0, 0] * a[1, 1] - a[0, 1] * a[1, 0]) * invDet + result[2, 1] = -(a[0, 0] * a[2, 1] - a[2, 0] * a[0, 1]) * invDet + result[2, 2] = +(a[0, 0] * a[1, 1] - a[1, 0] * a[0, 1]) * invDet proc inverse*[T](a: GMat4[T]): GMat4[T] = ## Return an inverse of the matrix. let a00 = a[0, 0] - a01 = a[1, 0] - a02 = a[2, 0] - a03 = a[3, 0] - a10 = a[0, 1] + a01 = a[0, 1] + a02 = a[0, 2] + a03 = a[0, 3] + a10 = a[1, 0] a11 = a[1, 1] - a12 = a[2, 1] - a13 = a[3, 1] - a20 = a[0, 2] - a21 = a[1, 2] + a12 = a[1, 2] + a13 = a[1, 3] + a20 = a[2, 0] + a21 = a[2, 1] a22 = a[2, 2] - a23 = a[3, 2] - a30 = a[0, 3] - a31 = a[1, 3] - a32 = a[2, 3] + a23 = a[2, 3] + a30 = a[3, 0] + a31 = a[3, 1] + a32 = a[3, 2] a33 = a[3, 3] let @@ -1277,23 +1268,23 @@ proc inverse*[T](a: GMat4[T]): GMat4[T] = let invDet = 1 / a.determinant result[0, 0] = (+a11 * b11 - a12 * b10 + a13 * b09) * invDet - result[1, 0] = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet - result[2, 0] = (+a31 * b05 - a32 * b04 + a33 * b03) * invDet - result[3, 0] = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet + result[0, 1] = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet + result[0, 2] = (+a31 * b05 - a32 * b04 + a33 * b03) * invDet + result[0, 3] = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet - result[0, 1] = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet + result[1, 0] = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet result[1, 1] = (+a00 * b11 - a02 * b08 + a03 * b07) * invDet - result[2, 1] = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet - result[3, 1] = (+a20 * b05 - a22 * b02 + a23 * b01) * invDet + result[1, 2] = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet + result[1, 3] = (+a20 * b05 - a22 * b02 + a23 * b01) * invDet - result[0, 2] = (+a10 * b10 - a11 * b08 + a13 * b06) * invDet - result[1, 2] = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet + result[2, 0] = (+a10 * b10 - a11 * b08 + a13 * b06) * invDet + result[2, 1] = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet result[2, 2] = (+a30 * b04 - a31 * b02 + a33 * b00) * invDet - result[3, 2] = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet + result[2, 3] = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet - result[0, 3] = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet - result[1, 3] = (+a00 * b09 - a01 * b07 + a02 * b06) * invDet - result[2, 3] = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet + result[3, 0] = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet + result[3, 1] = (+a00 * b09 - a01 * b07 + a02 * b06) * invDet + result[3, 2] = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet result[3, 3] = (+a20 * b03 - a21 * b01 + a22 * b00) * invDet proc scale*[T](v: GVec2[T]): GMat3[T] = @@ -1323,25 +1314,12 @@ proc translate*[T](v: GVec2[T]): GMat3[T] = proc translate*[T](v: GVec3[T]): GMat4[T] = ## Create translation matrix. - result[0, 0] = 1 - result[1, 0] = 0 - result[2, 0] = 0 - result[3, 0] = v.x - - result[0, 1] = 0 - result[1, 1] = 1 - result[2, 1] = 0 - result[3, 1] = v.y - - result[0, 2] = 0 - result[1, 2] = 0 - result[2, 2] = 1 - result[3, 2] = v.z - - result[0, 3] = 0 - result[1, 3] = 0 - result[2, 3] = 0 - result[3, 3] = 1 + gmat4[T]( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + v.x, v.y, v.z, 1 + ) proc rotate*[T](angle: T): GMat3[T] = ## Create a 2D rotation matrix by an angle. From c76975623963625fdab8f82f229fcb9bb00b9672 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 10:17:58 -0700 Subject: [PATCH 23/35] testing more things! --- conformance/dump_glm.nim | 117 +++++++++++++ conformance/dump_glm.txt | 165 ++++++++++++++++++- conformance/dump_glmatrix.js | 99 ++++++++++- conformance/dump_glmatrix.txt | 136 ++++++++++++++++ conformance/dump_glsl.comp | 67 +++++++- conformance/dump_glsl.nim | 298 +++++++++++++++++++++++++++++----- conformance/dump_glsl.txt | 147 +++++++++++++++++ conformance/dump_jolt.cpp | 43 ++++- conformance/dump_jolt.txt | 39 +++++ conformance/dump_vmath.nim | 98 +++++++++++ conformance/dump_vmath.txt | 149 ++++++++++++++++- src/vmath.nim | 28 +++- 12 files changed, 1324 insertions(+), 62 deletions(-) diff --git a/conformance/dump_glm.nim b/conformance/dump_glm.nim index 14414a9..8cb1b1e 100644 --- a/conformance/dump_glm.nim +++ b/conformance/dump_glm.nim @@ -39,6 +39,9 @@ proc appendLine(lines: var seq[string], line = "") = proc dumpScalar(lines: var seq[string], label: string, value: float32) = lines.appendLine(label & ": " & fmt(value)) +proc dumpVec2(lines: var seq[string], label: string, value: Vec2f) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ">") + proc dumpVec3(lines: var seq[string], label: string, value: Vec3f) = lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ">") @@ -48,6 +51,23 @@ proc dumpVec4(lines: var seq[string], label: string, value: Vec4f) = proc dumpQuat(lines: var seq[string], label: string, value: Quatf) = lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ", " & fmt(value.w) & ">") +proc dumpMat2(lines: var seq[string], label: string, value: Mat2f) = + lines.appendLine(label & ":") + let d = cast[array[4, float32]](value) + lines.appendLine("[") + lines.appendLine(" " & fmt(d[0]) & " " & fmt(d[2])) + lines.appendLine(" " & fmt(d[1]) & " " & fmt(d[3])) + lines.appendLine("]") + +proc dumpMat3(lines: var seq[string], label: string, value: Mat3f) = + lines.appendLine(label & ":") + let d = cast[array[9, float32]](value) + lines.appendLine("[") + lines.appendLine(" " & fmt(d[0]) & " " & fmt(d[3]) & " " & fmt(d[6])) + lines.appendLine(" " & fmt(d[1]) & " " & fmt(d[4]) & " " & fmt(d[7])) + lines.appendLine(" " & fmt(d[2]) & " " & fmt(d[5]) & " " & fmt(d[8])) + lines.appendLine("]") + proc dumpMat4(lines: var seq[string], label: string, value: Mat4f) = lines.appendLine(label & ":") let d = cast[array[16, float32]](value) @@ -66,6 +86,10 @@ proc heading(lines: var seq[string], title: string) = lines.appendLine() lines.appendLine("== " & title & " ==") +proc transformVec2ByMat3(matrix: Mat3f, value: Vec2f): Vec2f = + let r = matrix * vec3f(value.x, value.y, 1'f32) + vec2f(r.x, r.y) + proc transformVec3ByMat4(matrix: Mat4f, value: Vec3f): Vec3f = (matrix * vec4f(value, 1'f32)).xyz @@ -99,6 +123,17 @@ proc main() = vecA = vec3f(1.25, -2.5, 3.75) vecB = vec4f(1.25, -2.5, 3.75, 1.0) + mat2A = cast[Mat2f]([1.0f, 2.0f, 3.0f, 4.0f]) + mat2B = cast[Mat2f]([5.0f, -6.0f, 7.0f, -8.0f]) + vec2A = vec2f(1.25, -2.5) + + mat3A = cast[Mat3f]([1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f]) + mat3B = cast[Mat3f]([-1.0f, 3.0f, 5.0f, 7.0f, -2.0f, 4.0f, 6.0f, 8.0f, -3.0f]) + vec2B = vec2f(3.0, -1.5) + vec3C = vec3f(1.0, -2.0, 3.0) + + rotAngle2D = 45'f32 * PI.float32 / 180'f32 + scaleM = mat4f(1).scale(2'f32, 3'f32, 4'f32) translateM = mat4f(1).translate(10'f32, 20'f32, 30'f32) rotateXM = mat4f(1).rotateX(angleA) @@ -155,6 +190,88 @@ proc main() = lines.dumpScalar("transform[3, 2]", matA[3, 2]) lines.dumpScalar("transform[3, 3]", matA[3, 3]) + lines.heading("mat2 basics") + lines.dumpMat2("identity", mat2f()) + lines.dumpMat2("mat2_a", mat2A) + lines.dumpMat2("mat2_b", mat2B) + + lines.heading("mat2 multiply") + lines.dumpMat2("mat2_a * mat2_b", mat2A * mat2B) + lines.dumpMat2("mat2_b * mat2_a", mat2B * mat2A) + + lines.heading("mat2 element access [row, col]") + lines.dumpScalar("mat2[0, 0]", mat2A[0, 0]) + lines.dumpScalar("mat2[0, 1]", mat2A[0, 1]) + lines.dumpScalar("mat2[1, 0]", mat2A[1, 0]) + lines.dumpScalar("mat2[1, 1]", mat2A[1, 1]) + + lines.heading("mat2 vector multiply") + lines.dumpVec2("vec2_input", vec2A) + lines.dumpVec2("mat2_a * vec2", mat2A * vec2A) + + lines.heading("mat2 transpose") + lines.dumpMat2("mat2_a.transpose", transpose(mat2A)) + + lines.heading("mat2 inverse") + lines.dumpMat2("mat2_a.inverse", inverse(mat2A)) + + lines.heading("mat3 basics") + lines.dumpMat3("identity", mat3f()) + lines.dumpMat3("mat3_a", mat3A) + lines.dumpMat3("mat3_b", mat3B) + + lines.heading("mat3 multiply") + lines.dumpMat3("mat3_a * mat3_b", mat3A * mat3B) + lines.dumpMat3("mat3_b * mat3_a", mat3B * mat3A) + + lines.heading("mat3 element access [row, col]") + lines.dumpScalar("mat3[0, 0]", mat3A[0, 0]) + lines.dumpScalar("mat3[0, 1]", mat3A[0, 1]) + lines.dumpScalar("mat3[0, 2]", mat3A[0, 2]) + lines.dumpScalar("mat3[1, 0]", mat3A[1, 0]) + lines.dumpScalar("mat3[1, 1]", mat3A[1, 1]) + lines.dumpScalar("mat3[1, 2]", mat3A[1, 2]) + lines.dumpScalar("mat3[2, 0]", mat3A[2, 0]) + lines.dumpScalar("mat3[2, 1]", mat3A[2, 1]) + lines.dumpScalar("mat3[2, 2]", mat3A[2, 2]) + + lines.heading("mat3 vector multiply") + lines.dumpVec2("vec2_input", vec2B) + lines.dumpVec3("vec3_input", vec3C) + lines.dumpVec2("mat3_a * vec2", transformVec2ByMat3(mat3A, vec2B)) + lines.dumpVec3("mat3_a * vec3", mat3A * vec3C) + + lines.heading("mat3 transpose") + lines.dumpMat3("mat3_a.transpose", transpose(mat3A)) + + lines.heading("mat3 inverse") + lines.dumpMat3("mat3_a.inverse", inverse(mat3A)) + + lines.heading("mat3 constructors") + block: + let + s = mat3f(1'f32) + var scale2D = s + scale2D[0, 0] = 2'f32 + scale2D[1, 1] = 3'f32 + lines.dumpMat3("scale2d", scale2D) + + var translate2D = mat3f(1'f32) + translate2D[2, 0] = 5'f32 + translate2D[2, 1] = 10'f32 + lines.dumpMat3("translate2d", translate2D) + + lines.dumpScalar("rotate_angle_radians", rotAngle2D) + let c = cos(rotAngle2D) + let s2 = sin(rotAngle2D) + var rotate2D = mat3f(1'f32) + rotate2D[0, 0] = c + rotate2D[0, 1] = s2 + rotate2D[1, 0] = -s2 + rotate2D[1, 1] = c + lines.dumpMat3("rotate2d", rotate2D) + lines.dumpMat3("translate * rotate * scale", translate2D * rotate2D * scale2D) + lines.heading("matrix constructors and composition") lines.dumpMat4("scale", scaleM) lines.dumpMat4("translate", translateM) diff --git a/conformance/dump_glm.txt b/conformance/dump_glm.txt index 22ae563..65c2d2a 100644 --- a/conformance/dump_glm.txt +++ b/conformance/dump_glm.txt @@ -58,6 +58,153 @@ transform[3, 1]: +014.000 transform[3, 2]: +015.000 transform[3, 3]: +016.000 +== mat2 basics == +identity: +[ + +001.000 +000.000 + +000.000 +001.000 +] +mat2_a: +[ + +001.000 +003.000 + +002.000 +004.000 +] +mat2_b: +[ + +005.000 +007.000 + -006.000 -008.000 +] + +== mat2 multiply == +mat2_a * mat2_b: +[ + -013.000 -017.000 + -014.000 -018.000 +] +mat2_b * mat2_a: +[ + +019.000 +043.000 + -022.000 -050.000 +] + +== mat2 element access [row, col] == +mat2[0, 0]: +001.000 +mat2[0, 1]: +002.000 +mat2[1, 0]: +003.000 +mat2[1, 1]: +004.000 + +== mat2 vector multiply == +vec2_input: <+001.250, -002.500> +mat2_a * vec2: <-006.250, -007.500> + +== mat2 transpose == +mat2_a.transpose: +[ + +001.000 +002.000 + +003.000 +004.000 +] + +== mat2 inverse == +mat2_a.inverse: +[ + -002.000 +001.500 + +001.000 -000.500 +] + +== mat3 basics == +identity: +[ + +001.000 +000.000 +000.000 + +000.000 +001.000 +000.000 + +000.000 +000.000 +001.000 +] +mat3_a: +[ + +001.000 +004.000 +007.000 + +002.000 +005.000 +008.000 + +003.000 +006.000 +010.000 +] +mat3_b: +[ + -001.000 +007.000 +006.000 + +003.000 -002.000 +008.000 + +005.000 +004.000 -003.000 +] + +== mat3 multiply == +mat3_a * mat3_b: +[ + +046.000 +027.000 +017.000 + +053.000 +036.000 +028.000 + +065.000 +049.000 +036.000 +] +mat3_b * mat3_a: +[ + +031.000 +067.000 +109.000 + +023.000 +050.000 +085.000 + +004.000 +022.000 +037.000 +] + +== mat3 element access [row, col] == +mat3[0, 0]: +001.000 +mat3[0, 1]: +002.000 +mat3[0, 2]: +003.000 +mat3[1, 0]: +004.000 +mat3[1, 1]: +005.000 +mat3[1, 2]: +006.000 +mat3[2, 0]: +007.000 +mat3[2, 1]: +008.000 +mat3[2, 2]: +010.000 + +== mat3 vector multiply == +vec2_input: <+003.000, -001.500> +vec3_input: <+001.000, -002.000, +003.000> +mat3_a * vec2: <+004.000, +006.500> +mat3_a * vec3: <+014.000, +016.000, +021.000> + +== mat3 transpose == +mat3_a.transpose: +[ + +001.000 +002.000 +003.000 + +004.000 +005.000 +006.000 + +007.000 +008.000 +010.000 +] + +== mat3 inverse == +mat3_a.inverse: +[ + -000.667 -000.667 +001.000 + -001.333 +003.667 -002.000 + +001.000 -002.000 +001.000 +] + +== mat3 constructors == +scale2d: +[ + +002.000 +000.000 +000.000 + +000.000 +003.000 +000.000 + +000.000 +000.000 +001.000 +] +translate2d: +[ + +001.000 +000.000 +005.000 + +000.000 +001.000 +010.000 + +000.000 +000.000 +001.000 +] +rotate_angle_radians: +000.785 +rotate2d: +[ + +000.707 -000.707 +000.000 + +000.707 +000.707 +000.000 + +000.000 +000.000 +001.000 +] +translate * rotate * scale: +[ + +001.414 -002.121 +005.000 + +001.414 +002.121 +010.000 + +000.000 +000.000 +001.000 +] + == matrix constructors and composition == scale: [ @@ -296,13 +443,13 @@ ortho: N/A == default matrix printer == -matrix_a.default: / 1.0 5.0 9.0 13.0 \ -| 2.0 6.0 10.0 14.0 | -| 3.0 7.0 11.0 15.0 | - \ 4.0 8.0 12.0 16.0 / - -transform.default: / 0.5993741 -2.4950442 1.8697325 10.0 \ -| 1.7407088 0.11302096 -1.9639301 20.0 | -| 0.78146225 1.6619208 2.9405916 30.0 | - \ 0.0 0.0 0.0 1.0 / +matrix_a.default: ⎡1.0 5.0 9.0 13.0⎤ +⎢2.0 6.0 10.0 14.0⎥ +⎢3.0 7.0 11.0 15.0⎥ +⎣4.0 8.0 12.0 16.0⎦ + +transform.default: ⎡0.5993741 -2.4950442 1.8697325 10.0⎤ +⎢1.7407088 0.11302096 -1.9639301 20.0⎥ +⎢0.78146225 1.6619208 2.9405916 30.0⎥ +⎣0.0 0.0 0.0 1.0⎦ diff --git a/conformance/dump_glmatrix.js b/conformance/dump_glmatrix.js index 484bb2a..83073db 100644 --- a/conformance/dump_glmatrix.js +++ b/conformance/dump_glmatrix.js @@ -1,5 +1,7 @@ -import * as mat4 from "../../gl-matrix/src/mat4.js"; +import * as mat2 from "../../gl-matrix/src/mat2.js"; import * as mat3 from "../../gl-matrix/src/mat3.js"; +import * as mat4 from "../../gl-matrix/src/mat4.js"; +import * as vec2 from "../../gl-matrix/src/vec2.js"; import * as vec3 from "../../gl-matrix/src/vec3.js"; import * as vec4 from "../../gl-matrix/src/vec4.js"; import * as quat from "../../gl-matrix/src/quat.js"; @@ -39,6 +41,9 @@ function heading(title) { function dumpScalar(label, value) { appendLine(label + ": " + fmt(value)); } +function dumpVec2(label, v) { + appendLine(label + ": <" + fmt(v[0]) + ", " + fmt(v[1]) + ">"); +} function dumpVec3(label, v) { appendLine(label + ": <" + fmt(v[0]) + ", " + fmt(v[1]) + ", " + fmt(v[2]) + ">"); } @@ -48,6 +53,21 @@ function dumpVec4(label, v) { function dumpQuat(label, q) { appendLine(label + ": <" + fmt(q[0]) + ", " + fmt(q[1]) + ", " + fmt(q[2]) + ", " + fmt(q[3]) + ">"); } +function dumpMat2(label, m) { + appendLine(label + ":"); + appendLine("["); + appendLine(" " + fmt(m[0]) + " " + fmt(m[2])); + appendLine(" " + fmt(m[1]) + " " + fmt(m[3])); + appendLine("]"); +} +function dumpMat3(label, m) { + appendLine(label + ":"); + appendLine("["); + appendLine(" " + fmt(m[0]) + " " + fmt(m[3]) + " " + fmt(m[6])); + appendLine(" " + fmt(m[1]) + " " + fmt(m[4]) + " " + fmt(m[7])); + appendLine(" " + fmt(m[2]) + " " + fmt(m[5]) + " " + fmt(m[8])); + appendLine("]"); +} function dumpMat4(label, m) { appendLine(label + ":"); appendLine("["); @@ -150,21 +170,32 @@ const angleA = 37 * DEG2RAD; const angleB = -23 * DEG2RAD; const angleC = 71 * DEG2RAD; -const matA = mat4.fromValues( +const matA = new Float32Array([ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0 -); -const matB = mat4.fromValues( +]); +const matB = new Float32Array([ -10.0, -20.0, -30.0, -40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0, 110.0, 120.0, 130.0, 140.0, 150.0, 160.0 -); +]); const vecA = vec3.fromValues(1.25, -2.5, 3.75); const vecB = vec4.fromValues(1.25, -2.5, 3.75, 1.0); +const mat2A = new Float32Array([1.0, 2.0, 3.0, 4.0]); +const mat2B = new Float32Array([5.0, -6.0, 7.0, -8.0]); +const vec2A = vec2.fromValues(1.25, -2.5); + +const mat3A = new Float32Array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0]); +const mat3B = new Float32Array([-1.0, 3.0, 5.0, 7.0, -2.0, 4.0, 6.0, 8.0, -3.0]); +const vec2B = vec2.fromValues(3.0, -1.5); +const vec3C = vec3.fromValues(1.0, -2.0, 3.0); + +const rotAngle2D = 45 * DEG2RAD; + const scaleM = scaleMat(2.0, 3.0, 4.0); const translateM = translateMat(10.0, 20.0, 30.0); const rotateXM = rotateXMat(angleA); @@ -207,6 +238,64 @@ dumpMat4("matrix_b * matrix_a", mul(matB, matA)); heading("element access [row, col]"); appendLine("N/A"); +heading("mat2 basics"); +dumpMat2("identity", mat2.create()); +dumpMat2("mat2_a", mat2A); +dumpMat2("mat2_b", mat2B); + +heading("mat2 multiply"); +dumpMat2("mat2_a * mat2_b", mat2.multiply(mat2.create(), mat2A, mat2B)); +dumpMat2("mat2_b * mat2_a", mat2.multiply(mat2.create(), mat2B, mat2A)); + +heading("mat2 element access [row, col]"); +appendLine("N/A"); + +heading("mat2 vector multiply"); +dumpVec2("vec2_input", vec2A); +dumpVec2("mat2_a * vec2", vec2.transformMat2(vec2.create(), vec2A, mat2A)); + +heading("mat2 transpose"); +dumpMat2("mat2_a.transpose", mat2.transpose(mat2.create(), mat2A)); + +heading("mat2 inverse"); +dumpMat2("mat2_a.inverse", mat2.invert(mat2.create(), mat2A)); + +heading("mat3 basics"); +dumpMat3("identity", mat3.create()); +dumpMat3("mat3_a", mat3A); +dumpMat3("mat3_b", mat3B); + +heading("mat3 multiply"); +dumpMat3("mat3_a * mat3_b", mat3.multiply(mat3.create(), mat3A, mat3B)); +dumpMat3("mat3_b * mat3_a", mat3.multiply(mat3.create(), mat3B, mat3A)); + +heading("mat3 element access [row, col]"); +appendLine("N/A"); + +heading("mat3 vector multiply"); +dumpVec2("vec2_input", vec2B); +dumpVec3("vec3_input", vec3C); +dumpVec2("mat3_a * vec2", vec2.transformMat3(vec2.create(), vec2B, mat3A)); +dumpVec3("mat3_a * vec3", vec3.transformMat3(vec3.create(), vec3C, mat3A)); + +heading("mat3 transpose"); +dumpMat3("mat3_a.transpose", mat3.transpose(mat3.create(), mat3A)); + +heading("mat3 inverse"); +dumpMat3("mat3_a.inverse", mat3.invert(mat3.create(), mat3A)); + +heading("mat3 constructors"); +{ + const scale2D = mat3.fromScaling(mat3.create(), vec2.fromValues(2.0, 3.0)); + dumpMat3("scale2d", scale2D); + const translate2D = mat3.fromTranslation(mat3.create(), vec2.fromValues(5.0, 10.0)); + dumpMat3("translate2d", translate2D); + dumpScalar("rotate_angle_radians", rotAngle2D); + const rotate2D = mat3.fromRotation(mat3.create(), rotAngle2D); + dumpMat3("rotate2d", rotate2D); + dumpMat3("translate * rotate * scale", mat3.multiply(mat3.create(), translate2D, mat3.multiply(mat3.create(), rotate2D, scale2D))); +} + heading("matrix constructors and composition"); dumpMat4("scale", scaleM); dumpMat4("translate", translateM); diff --git a/conformance/dump_glmatrix.txt b/conformance/dump_glmatrix.txt index cbc8e68..e1c1c36 100644 --- a/conformance/dump_glmatrix.txt +++ b/conformance/dump_glmatrix.txt @@ -43,6 +43,142 @@ matrix_b * matrix_a: == element access [row, col] == N/A +== mat2 basics == +identity: +[ + +001.000 +000.000 + +000.000 +001.000 +] +mat2_a: +[ + +001.000 +003.000 + +002.000 +004.000 +] +mat2_b: +[ + +005.000 +007.000 + -006.000 -008.000 +] + +== mat2 multiply == +mat2_a * mat2_b: +[ + -013.000 -017.000 + -014.000 -018.000 +] +mat2_b * mat2_a: +[ + +019.000 +043.000 + -022.000 -050.000 +] + +== mat2 element access [row, col] == +N/A + +== mat2 vector multiply == +vec2_input: <+001.250, -002.500> +mat2_a * vec2: <-006.250, -007.500> + +== mat2 transpose == +mat2_a.transpose: +[ + +001.000 +002.000 + +003.000 +004.000 +] + +== mat2 inverse == +mat2_a.inverse: +[ + -002.000 +001.500 + +001.000 -000.500 +] + +== mat3 basics == +identity: +[ + +001.000 +000.000 +000.000 + +000.000 +001.000 +000.000 + +000.000 +000.000 +001.000 +] +mat3_a: +[ + +001.000 +004.000 +007.000 + +002.000 +005.000 +008.000 + +003.000 +006.000 +010.000 +] +mat3_b: +[ + -001.000 +007.000 +006.000 + +003.000 -002.000 +008.000 + +005.000 +004.000 -003.000 +] + +== mat3 multiply == +mat3_a * mat3_b: +[ + +046.000 +027.000 +017.000 + +053.000 +036.000 +028.000 + +065.000 +049.000 +036.000 +] +mat3_b * mat3_a: +[ + +031.000 +067.000 +109.000 + +023.000 +050.000 +085.000 + +004.000 +022.000 +037.000 +] + +== mat3 element access [row, col] == +N/A + +== mat3 vector multiply == +vec2_input: <+003.000, -001.500> +vec3_input: <+001.000, -002.000, +003.000> +mat3_a * vec2: <+004.000, +006.500> +mat3_a * vec3: <+014.000, +016.000, +021.000> + +== mat3 transpose == +mat3_a.transpose: +[ + +001.000 +002.000 +003.000 + +004.000 +005.000 +006.000 + +007.000 +008.000 +010.000 +] + +== mat3 inverse == +mat3_a.inverse: +[ + -000.667 -000.667 +001.000 + -001.333 +003.667 -002.000 + +001.000 -002.000 +001.000 +] + +== mat3 constructors == +scale2d: +[ + +002.000 +000.000 +000.000 + +000.000 +003.000 +000.000 + +000.000 +000.000 +001.000 +] +translate2d: +[ + +001.000 +000.000 +005.000 + +000.000 +001.000 +010.000 + +000.000 +000.000 +001.000 +] +rotate_angle_radians: +000.785 +rotate2d: +[ + +000.707 -000.707 +000.000 + +000.707 +000.707 +000.000 + +000.000 +000.000 +001.000 +] +translate * rotate * scale: +[ + +001.414 -002.121 +005.000 + +001.414 +002.121 +010.000 + +000.000 +000.000 +001.000 +] + == matrix constructors and composition == scale: [ diff --git a/conformance/dump_glsl.comp b/conformance/dump_glsl.comp index a85caf0..2bf820c 100644 --- a/conformance/dump_glsl.comp +++ b/conformance/dump_glsl.comp @@ -1,6 +1,5 @@ -#version 430 -layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -layout (rgba32f, binding = 0) uniform writeonly imageBuffer outputBuffer; +#version 410 +out vec4 fragColor; uniform mat4 uIdentityM; uniform mat4 uMatA; @@ -14,11 +13,19 @@ uniform mat4 uAxisMat; uniform mat4 uHardMat; uniform vec3 uVecA; uniform vec4 uVecB; +uniform mat2 uMat2A; +uniform mat2 uMat2B; +uniform mat3 uMat3A; +uniform mat3 uMat3B; +uniform vec2 uVec2A; +uniform vec2 uVec2B; +uniform vec3 uVec3C; const float angleA = radians(37.0); const float angleB = radians(-23.0); const float angleC = radians(71.0); const float axisAngle = radians(48.0); +const float rotAngle2D = radians(45.0); mat4 identityM() { return uIdentityM; @@ -40,6 +47,34 @@ vec4 vecB() { return uVecB; } +mat2 mat2A() { + return uMat2A; +} + +mat2 mat2B() { + return uMat2B; +} + +mat3 mat3A() { + return uMat3A; +} + +mat3 mat3B() { + return uMat3B; +} + +vec2 vec2A() { + return uVec2A; +} + +vec2 vec2B() { + return uVec2B; +} + +vec3 vec3C() { + return uVec3C; +} + mat4 scaleM() { return uScaleM; } @@ -333,6 +368,32 @@ vec3 quatToAngles(vec4 q) { ); } +mat3 scale2D(vec2 s) { + return mat3( + vec3(s.x, 0.0, 0.0), + vec3(0.0, s.y, 0.0), + vec3(0.0, 0.0, 1.0) + ); +} + +mat3 translate2D(vec2 t) { + return mat3( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(t.x, t.y, 1.0) + ); +} + +mat3 rotate2D(float angle) { + float c = cos(angle); + float s = sin(angle); + return mat3( + vec3(c, s, 0.0), + vec3(-s, c, 0.0), + vec3(0.0, 0.0, 1.0) + ); +} + void main() { __MAIN_BODY__ } diff --git a/conformance/dump_glsl.nim b/conformance/dump_glsl.nim index 3d02ea3..c37fbc5 100644 --- a/conformance/dump_glsl.nim +++ b/conformance/dump_glsl.nim @@ -1,12 +1,83 @@ import std/[math, os, strformat, strutils], opengl, - shady/compute + windy, + vmath const OutputPath = parentDir(currentSourcePath()) / "dump_glsl.txt" ShaderPath = parentDir(currentSourcePath()) / "dump_glsl.comp" + VertexShaderSrc = """ +#version 410 +void main() { + float x = (gl_VertexID == 1) ? 3.0 : -1.0; + float y = (gl_VertexID == 2) ? 3.0 : -1.0; + gl_Position = vec4(x, y, 0.0, 1.0); +} +""" + +var + contextInitialized = false + window: Window + vao: GLuint + +proc ensureContext() = + if contextInitialized: + return + window = newWindow( + title = "GLSL dump", + size = ivec2(100, 100), + visible = false, + openglVersion = OpenGL4Dot1 + ) + window.makeContextCurrent() + loadExtensions() + glGenVertexArrays(1, vao.addr) + glBindVertexArray(vao) + glDisable(GL_DEPTH_TEST) + glDisable(GL_BLEND) + contextInitialized = true + +proc compileShader(shaderType: GLenum, source: string): GLuint = + result = glCreateShader(shaderType) + var srcArray = allocCStringArray([source]) + glShaderSource(result, 1, srcArray, nil) + deallocCStringArray(srcArray) + glCompileShader(result) + var status: GLint + glGetShaderiv(result, GL_COMPILE_STATUS, status.addr) + if status == 0: + var logLen: GLint + glGetShaderiv(result, GL_INFO_LOG_LENGTH, logLen.addr) + var log = newString(logLen) + glGetShaderInfoLog(result, logLen, nil, log.cstring) + echo "Shader compile error:" + echo log + echo "Source:" + echo source + quit(1) + +proc compileProgram(vertSrc, fragSrc: string): GLuint = + let vertShader = compileShader(GL_VERTEX_SHADER, vertSrc) + let fragShader = compileShader(GL_FRAGMENT_SHADER, fragSrc) + result = glCreateProgram() + glAttachShader(result, vertShader) + glAttachShader(result, fragShader) + glLinkProgram(result) + var status: GLint + glGetProgramiv(result, GL_LINK_STATUS, status.addr) + if status == 0: + var logLen: GLint + glGetProgramiv(result, GL_INFO_LOG_LENGTH, logLen.addr) + var log = newString(logLen) + glGetProgramInfoLog(result, logLen, nil, log.cstring) + echo "Program link error:" + echo log + quit(1) + glDeleteShader(vertShader) + glDeleteShader(fragShader) + proc cleanFloat(value: float32): float32 = if abs(value) < 0.0000005'f32: 0'f32 @@ -41,6 +112,9 @@ proc appendLine(lines: var seq[string], line = "") = proc dumpScalar(lines: var seq[string], label: string, value: float32) = lines.appendLine(label & ": " & fmt(value)) +proc dumpVec2(lines: var seq[string], label: string, value: openArray[float32]) = + lines.appendLine(label & ": <" & fmt(value[0]) & ", " & fmt(value[1]) & ">") + proc dumpVec3(lines: var seq[string], label: string, value: openArray[float32]) = lines.appendLine(label & ": <" & fmt(value[0]) & ", " & fmt(value[1]) & ", " & fmt(value[2]) & ">") @@ -50,6 +124,21 @@ proc dumpVec4(lines: var seq[string], label: string, value: openArray[float32]) proc dumpQuat(lines: var seq[string], label: string, value: openArray[float32]) = lines.appendLine(label & ": <" & fmt(value[0]) & ", " & fmt(value[1]) & ", " & fmt(value[2]) & ", " & fmt(value[3]) & ">") +proc dumpMat2(lines: var seq[string], label: string, value: openArray[float32]) = + lines.appendLine(label & ":") + lines.appendLine("[") + lines.appendLine(" " & fmt(value[0]) & " " & fmt(value[2])) + lines.appendLine(" " & fmt(value[1]) & " " & fmt(value[3])) + lines.appendLine("]") + +proc dumpMat3(lines: var seq[string], label: string, value: openArray[float32]) = + lines.appendLine(label & ":") + lines.appendLine("[") + lines.appendLine(" " & fmt(value[0]) & " " & fmt(value[3]) & " " & fmt(value[6])) + lines.appendLine(" " & fmt(value[1]) & " " & fmt(value[4]) & " " & fmt(value[7])) + lines.appendLine(" " & fmt(value[2]) & " " & fmt(value[5]) & " " & fmt(value[8])) + lines.appendLine("]") + proc dumpMat4(lines: var seq[string], label: string, value: openArray[float32]) = lines.appendLine(label & ":") lines.appendLine("[") @@ -74,6 +163,27 @@ proc setUniformMat4(program: GLuint, name: string, value: openArray[float32]) = doAssert value.len == 16 glUniformMatrix4fv(location, 1, GLboolean(GL_FALSE), cast[ptr GLfloat](unsafeAddr value[0])) +proc setUniformMat3(program: GLuint, name: string, value: openArray[float32]) = + let location = glGetUniformLocation(program, name) + if location < 0: + return + doAssert value.len == 9 + glUniformMatrix3fv(location, 1, GLboolean(GL_FALSE), cast[ptr GLfloat](unsafeAddr value[0])) + +proc setUniformMat2(program: GLuint, name: string, value: openArray[float32]) = + let location = glGetUniformLocation(program, name) + if location < 0: + return + doAssert value.len == 4 + glUniformMatrix2fv(location, 1, GLboolean(GL_FALSE), cast[ptr GLfloat](unsafeAddr value[0])) + +proc setUniformVec2(program: GLuint, name: string, value: openArray[float32]) = + let location = glGetUniformLocation(program, name) + if location < 0: + return + doAssert value.len == 2 + glUniform2f(location, value[0], value[1]) + proc setUniformVec3(program: GLuint, name: string, value: openArray[float32]) = let location = glGetUniformLocation(program, name) if location < 0: @@ -205,6 +315,13 @@ proc uploadDumpUniforms(program: GLuint) = ] uVecA = [1.25f, -2.5f, 3.75f] uVecB = [1.25f, -2.5f, 3.75f, 1.0f] + uMat2A = [1.0f, 2.0f, 3.0f, 4.0f] + uMat2B = [5.0f, -6.0f, 7.0f, -8.0f] + uVec2A = [1.25f, -2.5f] + uMat3A = [1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f] + uMat3B = [-1.0f, 3.0f, 5.0f, 7.0f, -2.0f, 4.0f, 6.0f, 8.0f, -3.0f] + uVec2B = [3.0f, -1.5f] + uVec3C = [1.0f, -2.0f, 3.0f] setUniformMat4(program, "uIdentityM", uIdentityM) setUniformMat4(program, "uMatA", uMatA) setUniformMat4(program, "uMatB", uMatB) @@ -217,62 +334,95 @@ proc uploadDumpUniforms(program: GLuint) = setUniformMat4(program, "uHardMat", uHardMat) setUniformVec3(program, "uVecA", uVecA) setUniformVec4(program, "uVecB", uVecB) - -proc runComputeShader(shaderSrc: string, invocationCount: int): seq[float32] = - initOffscreenWindow() - - let shaderId = compileComputeShader((ShaderPath, shaderSrc)) - glUseProgram(shaderId) - uploadDumpUniforms(shaderId) - - var - outputBufferId: GLuint - outputTextureId: GLuint - glGenBuffers(1, outputBufferId.addr) - glBindBuffer(GL_TEXTURE_BUFFER, outputBufferId) - glBufferData(GL_TEXTURE_BUFFER, invocationCount * 4 * sizeof(float32), nil, GL_STATIC_DRAW) - - glGenTextures(1, outputTextureId.addr) - glActiveTexture(GL_TEXTURE0) - glBindTexture(GL_TEXTURE_BUFFER, outputTextureId) - glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, outputBufferId) - glBindImageTexture(0, outputTextureId, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F) - - glDispatchCompute(invocationCount.GLuint, 1, 1) - glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT or GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) - - result.setLen(invocationCount * 4) - let mapped = cast[ptr UncheckedArray[float32]](glMapNamedBuffer(outputBufferId, GL_READ_ONLY)) - copyMem(result[0].addr, mapped, result.len * sizeof(float32)) - discard glUnmapNamedBuffer(outputBufferId) - - glBindTexture(GL_TEXTURE_BUFFER, 0) - glBindBuffer(GL_TEXTURE_BUFFER, 0) - glDeleteTextures(1, outputTextureId.addr) - glDeleteBuffers(1, outputBufferId.addr) - glDeleteProgram(shaderId) + setUniformMat2(program, "uMat2A", uMat2A) + setUniformMat2(program, "uMat2B", uMat2B) + setUniformMat3(program, "uMat3A", uMat3A) + setUniformMat3(program, "uMat3B", uMat3B) + setUniformVec2(program, "uVec2A", uVec2A) + setUniformVec2(program, "uVec2B", uVec2B) + setUniformVec3(program, "uVec3C", uVec3C) + +proc runShader(fragSrc: string, pixelCount: int): seq[float32] = + ensureContext() + + let program = compileProgram(VertexShaderSrc, fragSrc) + glUseProgram(program) + uploadDumpUniforms(program) + + var fbo, texture: GLuint + glGenFramebuffers(1, fbo.addr) + glBindFramebuffer(GL_FRAMEBUFFER, fbo) + + glGenTextures(1, texture.addr) + glBindTexture(GL_TEXTURE_2D, texture) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F.GLint, pixelCount.GLsizei, 1.GLsizei, 0.GLint, GL_RGBA, cGL_FLOAT, nil) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST.GLint) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST.GLint) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0) + + let status = glCheckFramebufferStatus(GL_FRAMEBUFFER) + if status != GL_FRAMEBUFFER_COMPLETE: + echo "Framebuffer incomplete: ", status.uint32 + quit(1) + + glViewport(0, 0, pixelCount.GLsizei, 1) + glDrawArrays(GL_TRIANGLES, 0, 3) + + result.setLen(pixelCount * 4) + glReadPixels(0, 0, pixelCount.GLsizei, 1.GLsizei, GL_RGBA, cGL_FLOAT, result[0].addr) + + glBindFramebuffer(GL_FRAMEBUFFER, 0) + glDeleteTextures(1, texture.addr) + glDeleteFramebuffers(1, fbo.addr) + glDeleteProgram(program) proc runMatrix(expr: string): seq[float32] = let shaderSrc = shaderTemplate(fmt""" - uint index = gl_GlobalInvocationID.x; + int index = int(gl_FragCoord.x); mat4 value = {expr}; - imageStore(outputBuffer, int(index), value[int(index)]); + fragColor = value[index]; """) - runComputeShader(shaderSrc, 4) + runShader(shaderSrc, 4) + +proc runMat3(expr: string): seq[float32] = + let shaderSrc = shaderTemplate(fmt""" + int index = int(gl_FragCoord.x); + mat3 value = {expr}; + fragColor = vec4(value[index], 0.0); +""") + let raw = runShader(shaderSrc, 3) + result = @[raw[0], raw[1], raw[2], raw[4], raw[5], raw[6], raw[8], raw[9], raw[10]] + +proc runMat2(expr: string): seq[float32] = + let shaderSrc = shaderTemplate(fmt""" + int index = int(gl_FragCoord.x); + mat2 value = {expr}; + fragColor = vec4(value[index], 0.0, 0.0); +""") + let raw = runShader(shaderSrc, 2) + result = @[raw[0], raw[1], raw[4], raw[5]] proc runVec3(expr: string): seq[float32] = let shaderSrc = shaderTemplate(fmt""" vec3 value = {expr}; - imageStore(outputBuffer, 0, vec4(value, 0.0)); + fragColor = vec4(value, 0.0); """) - runComputeShader(shaderSrc, 1) + runShader(shaderSrc, 1) proc runVec4(expr: string): seq[float32] = let shaderSrc = shaderTemplate(fmt""" vec4 value = {expr}; - imageStore(outputBuffer, 0, value); + fragColor = value; """) - runComputeShader(shaderSrc, 1) + runShader(shaderSrc, 1) + +proc runVec2(expr: string): seq[float32] = + let shaderSrc = shaderTemplate(fmt""" + vec2 value = {expr}; + fragColor = vec4(value, 0.0, 0.0); +""") + let raw = runShader(shaderSrc, 1) + result = @[raw[0], raw[1]] proc runScalar(expr: string): float32 = runVec4(expr).toOpenArray(0, 3)[0] @@ -310,6 +460,70 @@ proc main() = lines.dumpScalar("transform[3, 2]", runScalar("vec4(matA()[3][2], 0.0, 0.0, 0.0)")) lines.dumpScalar("transform[3, 3]", runScalar("vec4(matA()[3][3], 0.0, 0.0, 0.0)")) + lines.heading("mat2 basics") + lines.dumpMat2("identity", runMat2("mat2(1.0)")) + lines.dumpMat2("mat2_a", runMat2("mat2A()")) + lines.dumpMat2("mat2_b", runMat2("mat2B()")) + + lines.heading("mat2 multiply") + lines.dumpMat2("mat2_a * mat2_b", runMat2("mat2A() * mat2B()")) + lines.dumpMat2("mat2_b * mat2_a", runMat2("mat2B() * mat2A()")) + + lines.heading("mat2 element access [row, col]") + lines.dumpScalar("mat2[0, 0]", runScalar("vec4(mat2A()[0][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat2[0, 1]", runScalar("vec4(mat2A()[0][1], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat2[1, 0]", runScalar("vec4(mat2A()[1][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat2[1, 1]", runScalar("vec4(mat2A()[1][1], 0.0, 0.0, 0.0)")) + + lines.heading("mat2 vector multiply") + lines.dumpVec2("vec2_input", runVec2("vec2A()")) + lines.dumpVec2("mat2_a * vec2", runVec2("mat2A() * vec2A()")) + + lines.heading("mat2 transpose") + lines.dumpMat2("mat2_a.transpose", runMat2("transpose(mat2A())")) + + lines.heading("mat2 inverse") + lines.dumpMat2("mat2_a.inverse", runMat2("inverse(mat2A())")) + + lines.heading("mat3 basics") + lines.dumpMat3("identity", runMat3("mat3(1.0)")) + lines.dumpMat3("mat3_a", runMat3("mat3A()")) + lines.dumpMat3("mat3_b", runMat3("mat3B()")) + + lines.heading("mat3 multiply") + lines.dumpMat3("mat3_a * mat3_b", runMat3("mat3A() * mat3B()")) + lines.dumpMat3("mat3_b * mat3_a", runMat3("mat3B() * mat3A()")) + + lines.heading("mat3 element access [row, col]") + lines.dumpScalar("mat3[0, 0]", runScalar("vec4(mat3A()[0][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat3[0, 1]", runScalar("vec4(mat3A()[0][1], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat3[0, 2]", runScalar("vec4(mat3A()[0][2], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat3[1, 0]", runScalar("vec4(mat3A()[1][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat3[1, 1]", runScalar("vec4(mat3A()[1][1], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat3[1, 2]", runScalar("vec4(mat3A()[1][2], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat3[2, 0]", runScalar("vec4(mat3A()[2][0], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat3[2, 1]", runScalar("vec4(mat3A()[2][1], 0.0, 0.0, 0.0)")) + lines.dumpScalar("mat3[2, 2]", runScalar("vec4(mat3A()[2][2], 0.0, 0.0, 0.0)")) + + lines.heading("mat3 vector multiply") + lines.dumpVec2("vec2_input", runVec2("vec2B()")) + lines.dumpVec3("vec3_input", runVec3("vec3C()")) + lines.dumpVec2("mat3_a * vec2", runVec2("(mat3A() * vec3(vec2B(), 1.0)).xy")) + lines.dumpVec3("mat3_a * vec3", runVec3("mat3A() * vec3C()")) + + lines.heading("mat3 transpose") + lines.dumpMat3("mat3_a.transpose", runMat3("transpose(mat3A())")) + + lines.heading("mat3 inverse") + lines.dumpMat3("mat3_a.inverse", runMat3("inverse(mat3A())")) + + lines.heading("mat3 constructors") + lines.dumpMat3("scale2d", runMat3("scale2D(vec2(2.0, 3.0))")) + lines.dumpMat3("translate2d", runMat3("translate2D(vec2(5.0, 10.0))")) + lines.dumpScalar("rotate_angle_radians", runScalar("vec4(rotAngle2D, 0.0, 0.0, 0.0)")) + lines.dumpMat3("rotate2d", runMat3("rotate2D(rotAngle2D)")) + lines.dumpMat3("translate * rotate * scale", runMat3("translate2D(vec2(5.0, 10.0)) * rotate2D(rotAngle2D) * scale2D(vec2(2.0, 3.0))")) + lines.heading("matrix constructors and composition") lines.dumpMat4("scale", runMatrix("scaleM()")) lines.dumpMat4("translate", runMatrix("translateM()")) diff --git a/conformance/dump_glsl.txt b/conformance/dump_glsl.txt index bd70419..7080815 100644 --- a/conformance/dump_glsl.txt +++ b/conformance/dump_glsl.txt @@ -58,6 +58,153 @@ transform[3, 1]: +014.000 transform[3, 2]: +015.000 transform[3, 3]: +016.000 +== mat2 basics == +identity: +[ + +001.000 +000.000 + +000.000 +001.000 +] +mat2_a: +[ + +001.000 +003.000 + +002.000 +004.000 +] +mat2_b: +[ + +005.000 +007.000 + -006.000 -008.000 +] + +== mat2 multiply == +mat2_a * mat2_b: +[ + -013.000 -017.000 + -014.000 -018.000 +] +mat2_b * mat2_a: +[ + +019.000 +043.000 + -022.000 -050.000 +] + +== mat2 element access [row, col] == +mat2[0, 0]: +001.000 +mat2[0, 1]: +002.000 +mat2[1, 0]: +003.000 +mat2[1, 1]: +004.000 + +== mat2 vector multiply == +vec2_input: <+001.250, -002.500> +mat2_a * vec2: <-006.250, -007.500> + +== mat2 transpose == +mat2_a.transpose: +[ + +001.000 +002.000 + +003.000 +004.000 +] + +== mat2 inverse == +mat2_a.inverse: +[ + -002.000 +001.500 + +001.000 -000.500 +] + +== mat3 basics == +identity: +[ + +001.000 +000.000 +000.000 + +000.000 +001.000 +000.000 + +000.000 +000.000 +001.000 +] +mat3_a: +[ + +001.000 +004.000 +007.000 + +002.000 +005.000 +008.000 + +003.000 +006.000 +010.000 +] +mat3_b: +[ + -001.000 +007.000 +006.000 + +003.000 -002.000 +008.000 + +005.000 +004.000 -003.000 +] + +== mat3 multiply == +mat3_a * mat3_b: +[ + +046.000 +027.000 +017.000 + +053.000 +036.000 +028.000 + +065.000 +049.000 +036.000 +] +mat3_b * mat3_a: +[ + +031.000 +067.000 +109.000 + +023.000 +050.000 +085.000 + +004.000 +022.000 +037.000 +] + +== mat3 element access [row, col] == +mat3[0, 0]: +001.000 +mat3[0, 1]: +002.000 +mat3[0, 2]: +003.000 +mat3[1, 0]: +004.000 +mat3[1, 1]: +005.000 +mat3[1, 2]: +006.000 +mat3[2, 0]: +007.000 +mat3[2, 1]: +008.000 +mat3[2, 2]: +010.000 + +== mat3 vector multiply == +vec2_input: <+003.000, -001.500> +vec3_input: <+001.000, -002.000, +003.000> +mat3_a * vec2: <+004.000, +006.500> +mat3_a * vec3: <+014.000, +016.000, +021.000> + +== mat3 transpose == +mat3_a.transpose: +[ + +001.000 +002.000 +003.000 + +004.000 +005.000 +006.000 + +007.000 +008.000 +010.000 +] + +== mat3 inverse == +mat3_a.inverse: +[ + -000.667 -000.667 +001.000 + -001.333 +003.667 -002.000 + +001.000 -002.000 +001.000 +] + +== mat3 constructors == +scale2d: +[ + +002.000 +000.000 +000.000 + +000.000 +003.000 +000.000 + +000.000 +000.000 +001.000 +] +translate2d: +[ + +001.000 +000.000 +005.000 + +000.000 +001.000 +010.000 + +000.000 +000.000 +001.000 +] +rotate_angle_radians: +000.785 +rotate2d: +[ + +000.707 -000.707 +000.000 + +000.707 +000.707 +000.000 + +000.000 +000.000 +001.000 +] +translate * rotate * scale: +[ + +001.414 -002.121 +005.000 + +001.414 +002.121 +010.000 + +000.000 +000.000 +001.000 +] + == matrix constructors and composition == scale: [ diff --git a/conformance/dump_jolt.cpp b/conformance/dump_jolt.cpp index 1f96295..711834d 100644 --- a/conformance/dump_jolt.cpp +++ b/conformance/dump_jolt.cpp @@ -105,7 +105,9 @@ static Mat44 RotationOnlyCopy(Mat44 value) int main() { - const std::string output_path = "C:/p/vmath/conformance/dump_jolt.txt"; + // Derive output path from __FILE__ so it works regardless of cwd + std::string source_path(__FILE__); + std::string output_path = source_path.substr(0, source_path.rfind('.')) + ".txt"; std::vector lines; @@ -191,6 +193,45 @@ int main() DumpScalar(lines, "transform[3, 2]", mat_a(3, 2)); DumpScalar(lines, "transform[3, 3]", mat_a(3, 3)); + Heading(lines, "mat2 basics"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat2 multiply"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat2 element access [row, col]"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat2 vector multiply"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat2 transpose"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat2 inverse"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat3 basics"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat3 multiply"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat3 element access [row, col]"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat3 vector multiply"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat3 transpose"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat3 inverse"); + AppendLine(lines, "N/A"); + + Heading(lines, "mat3 constructors"); + AppendLine(lines, "N/A"); + Heading(lines, "matrix constructors and composition"); DumpMat4(lines, "scale", scale_m); DumpMat4(lines, "translate", translate_m); diff --git a/conformance/dump_jolt.txt b/conformance/dump_jolt.txt index 5fecae0..38944a2 100644 --- a/conformance/dump_jolt.txt +++ b/conformance/dump_jolt.txt @@ -58,6 +58,45 @@ transform[3, 1]: +008.000 transform[3, 2]: +012.000 transform[3, 3]: +016.000 +== mat2 basics == +N/A + +== mat2 multiply == +N/A + +== mat2 element access [row, col] == +N/A + +== mat2 vector multiply == +N/A + +== mat2 transpose == +N/A + +== mat2 inverse == +N/A + +== mat3 basics == +N/A + +== mat3 multiply == +N/A + +== mat3 element access [row, col] == +N/A + +== mat3 vector multiply == +N/A + +== mat3 transpose == +N/A + +== mat3 inverse == +N/A + +== mat3 constructors == +N/A + == matrix constructors and composition == scale: [ diff --git a/conformance/dump_vmath.nim b/conformance/dump_vmath.nim index a3e5500..052983e 100644 --- a/conformance/dump_vmath.nim +++ b/conformance/dump_vmath.nim @@ -39,6 +39,9 @@ proc appendLine(lines: var seq[string], line = "") = proc dumpScalar(lines: var seq[string], label: string, value: float32) = lines.appendLine(label & ": " & fmt(value)) +proc dumpVec2(lines: var seq[string], label: string, value: Vec2) = + lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ">") + proc dumpVec3(lines: var seq[string], label: string, value: Vec3) = lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ">") @@ -48,6 +51,23 @@ proc dumpVec4(lines: var seq[string], label: string, value: Vec4) = proc dumpQuat(lines: var seq[string], label: string, value: Quat) = lines.appendLine(label & ": <" & fmt(value.x) & ", " & fmt(value.y) & ", " & fmt(value.z) & ", " & fmt(value.w) & ">") +proc dumpMat2(lines: var seq[string], label: string, value: Mat2) = + lines.appendLine(label & ":") + let d = cast[array[4, float32]](value) + lines.appendLine("[") + lines.appendLine(" " & fmt(d[0]) & " " & fmt(d[2])) + lines.appendLine(" " & fmt(d[1]) & " " & fmt(d[3])) + lines.appendLine("]") + +proc dumpMat3(lines: var seq[string], label: string, value: Mat3) = + lines.appendLine(label & ":") + let d = cast[array[9, float32]](value) + lines.appendLine("[") + lines.appendLine(" " & fmt(d[0]) & " " & fmt(d[3]) & " " & fmt(d[6])) + lines.appendLine(" " & fmt(d[1]) & " " & fmt(d[4]) & " " & fmt(d[7])) + lines.appendLine(" " & fmt(d[2]) & " " & fmt(d[5]) & " " & fmt(d[8])) + lines.appendLine("]") + proc dumpMat4(lines: var seq[string], label: string, value: Mat4) = lines.appendLine(label & ":") let d = cast[array[16, float32]](value) @@ -121,6 +141,20 @@ proc main() = hardQuat = fromAxisAngle(hardAxis, hardAngle) hardMat = hardQuat.mat4() + mat2A = cast[Mat2]([1.0f, 2.0f, 3.0f, 4.0f]) + mat2B = cast[Mat2]([5.0f, -6.0f, 7.0f, -8.0f]) + vec2A = vec2(1.25, -2.5) + + mat3A = cast[Mat3]([1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f]) + mat3B = cast[Mat3]([-1.0f, 3.0f, 5.0f, 7.0f, -2.0f, 4.0f, 6.0f, 8.0f, -3.0f]) + vec2B = vec2(3.0, -1.5) + vec3C = vec3(1.0, -2.0, 3.0) + + rotAngle2D = 45'f32.toRadians + scale2D = scale(vec2(2.0, 3.0)) + translate2D = translate(vec2(5.0, 10.0)) + rotate2D = rotate(rotAngle2D) + rotationOnlyM = rotationOnlyCopy(transformM) basisRight = vec3(1.0, 0.0, 0.0) basisUp = vec3(0.0, 1.0, 0.0) @@ -156,6 +190,70 @@ proc main() = lines.dumpScalar("transform[3, 2]", matA[3, 2]) lines.dumpScalar("transform[3, 3]", matA[3, 3]) + lines.heading("mat2 basics") + lines.dumpMat2("identity", mat2()) + lines.dumpMat2("mat2_a", mat2A) + lines.dumpMat2("mat2_b", mat2B) + + lines.heading("mat2 multiply") + lines.dumpMat2("mat2_a * mat2_b", mat2A * mat2B) + lines.dumpMat2("mat2_b * mat2_a", mat2B * mat2A) + + lines.heading("mat2 element access [row, col]") + lines.dumpScalar("mat2[0, 0]", mat2A[0, 0]) + lines.dumpScalar("mat2[0, 1]", mat2A[0, 1]) + lines.dumpScalar("mat2[1, 0]", mat2A[1, 0]) + lines.dumpScalar("mat2[1, 1]", mat2A[1, 1]) + + lines.heading("mat2 vector multiply") + lines.dumpVec2("vec2_input", vec2A) + lines.dumpVec2("mat2_a * vec2", mat2A * vec2A) + + lines.heading("mat2 transpose") + lines.dumpMat2("mat2_a.transpose", transpose(mat2A)) + + lines.heading("mat2 inverse") + lines.dumpMat2("mat2_a.inverse", inverse(mat2A)) + + lines.heading("mat3 basics") + lines.dumpMat3("identity", mat3()) + lines.dumpMat3("mat3_a", mat3A) + lines.dumpMat3("mat3_b", mat3B) + + lines.heading("mat3 multiply") + lines.dumpMat3("mat3_a * mat3_b", mat3A * mat3B) + lines.dumpMat3("mat3_b * mat3_a", mat3B * mat3A) + + lines.heading("mat3 element access [row, col]") + lines.dumpScalar("mat3[0, 0]", mat3A[0, 0]) + lines.dumpScalar("mat3[0, 1]", mat3A[0, 1]) + lines.dumpScalar("mat3[0, 2]", mat3A[0, 2]) + lines.dumpScalar("mat3[1, 0]", mat3A[1, 0]) + lines.dumpScalar("mat3[1, 1]", mat3A[1, 1]) + lines.dumpScalar("mat3[1, 2]", mat3A[1, 2]) + lines.dumpScalar("mat3[2, 0]", mat3A[2, 0]) + lines.dumpScalar("mat3[2, 1]", mat3A[2, 1]) + lines.dumpScalar("mat3[2, 2]", mat3A[2, 2]) + + lines.heading("mat3 vector multiply") + lines.dumpVec2("vec2_input", vec2B) + lines.dumpVec3("vec3_input", vec3C) + lines.dumpVec2("mat3_a * vec2", mat3A * vec2B) + lines.dumpVec3("mat3_a * vec3", mat3A * vec3C) + + lines.heading("mat3 transpose") + lines.dumpMat3("mat3_a.transpose", transpose(mat3A)) + + lines.heading("mat3 inverse") + lines.dumpMat3("mat3_a.inverse", inverse(mat3A)) + + lines.heading("mat3 constructors") + lines.dumpMat3("scale2d", scale2D) + lines.dumpMat3("translate2d", translate2D) + lines.dumpScalar("rotate_angle_radians", rotAngle2D) + lines.dumpMat3("rotate2d", rotate2D) + lines.dumpMat3("translate * rotate * scale", translate2D * rotate2D * scale2D) + lines.heading("matrix constructors and composition") lines.dumpMat4("scale", scaleM) lines.dumpMat4("translate", translateM) diff --git a/conformance/dump_vmath.txt b/conformance/dump_vmath.txt index 0f0d011..d891e9f 100644 --- a/conformance/dump_vmath.txt +++ b/conformance/dump_vmath.txt @@ -58,6 +58,153 @@ transform[3, 1]: +014.000 transform[3, 2]: +015.000 transform[3, 3]: +016.000 +== mat2 basics == +identity: +[ + +001.000 +000.000 + +000.000 +001.000 +] +mat2_a: +[ + +001.000 +003.000 + +002.000 +004.000 +] +mat2_b: +[ + +005.000 +007.000 + -006.000 -008.000 +] + +== mat2 multiply == +mat2_a * mat2_b: +[ + -013.000 -017.000 + -014.000 -018.000 +] +mat2_b * mat2_a: +[ + +019.000 +043.000 + -022.000 -050.000 +] + +== mat2 element access [row, col] == +mat2[0, 0]: +001.000 +mat2[0, 1]: +002.000 +mat2[1, 0]: +003.000 +mat2[1, 1]: +004.000 + +== mat2 vector multiply == +vec2_input: <+001.250, -002.500> +mat2_a * vec2: <-006.250, -007.500> + +== mat2 transpose == +mat2_a.transpose: +[ + +001.000 +002.000 + +003.000 +004.000 +] + +== mat2 inverse == +mat2_a.inverse: +[ + -002.000 +001.500 + +001.000 -000.500 +] + +== mat3 basics == +identity: +[ + +001.000 +000.000 +000.000 + +000.000 +001.000 +000.000 + +000.000 +000.000 +001.000 +] +mat3_a: +[ + +001.000 +004.000 +007.000 + +002.000 +005.000 +008.000 + +003.000 +006.000 +010.000 +] +mat3_b: +[ + -001.000 +007.000 +006.000 + +003.000 -002.000 +008.000 + +005.000 +004.000 -003.000 +] + +== mat3 multiply == +mat3_a * mat3_b: +[ + +046.000 +027.000 +017.000 + +053.000 +036.000 +028.000 + +065.000 +049.000 +036.000 +] +mat3_b * mat3_a: +[ + +031.000 +067.000 +109.000 + +023.000 +050.000 +085.000 + +004.000 +022.000 +037.000 +] + +== mat3 element access [row, col] == +mat3[0, 0]: +001.000 +mat3[0, 1]: +002.000 +mat3[0, 2]: +003.000 +mat3[1, 0]: +004.000 +mat3[1, 1]: +005.000 +mat3[1, 2]: +006.000 +mat3[2, 0]: +007.000 +mat3[2, 1]: +008.000 +mat3[2, 2]: +010.000 + +== mat3 vector multiply == +vec2_input: <+003.000, -001.500> +vec3_input: <+001.000, -002.000, +003.000> +mat3_a * vec2: <+004.000, +006.500> +mat3_a * vec3: <+014.000, +016.000, +021.000> + +== mat3 transpose == +mat3_a.transpose: +[ + +001.000 +002.000 +003.000 + +004.000 +005.000 +006.000 + +007.000 +008.000 +010.000 +] + +== mat3 inverse == +mat3_a.inverse: +[ + -000.667 -000.667 +001.000 + -001.333 +003.667 -002.000 + +001.000 -002.000 +001.000 +] + +== mat3 constructors == +scale2d: +[ + +002.000 +000.000 +000.000 + +000.000 +003.000 +000.000 + +000.000 +000.000 +001.000 +] +translate2d: +[ + +001.000 +000.000 +005.000 + +000.000 +001.000 +010.000 + +000.000 +000.000 +001.000 +] +rotate_angle_radians: +000.785 +rotate2d: +[ + +000.707 -000.707 +000.000 + +000.707 +000.707 +000.000 + +000.000 +000.000 +001.000 +] +translate * rotate * scale: +[ + +001.414 -002.121 +005.000 + +001.414 +002.121 +010.000 + +000.000 +000.000 +001.000 +] + == matrix constructors and composition == scale: [ @@ -306,7 +453,7 @@ matrix_a.default: mat4( ) transform.default: mat4( 0.5993741, 1.7407088, 0.78146225, 0.0, - -2.4950442, 0.11302096, 1.6619208, 0.0, + -2.4950442, 0.113020934, 1.6619208, 0.0, 1.8697325, -1.9639301, 2.9405916, 0.0, 10.0, 20.0, 30.0, 1.0 ) diff --git a/src/vmath.nim b/src/vmath.nim index 377b769..746e22b 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -1056,6 +1056,13 @@ proc `pos=`*[T](a: var GMat4[T], pos: GVec3[T]) = a[3, 1] = pos.y a[3, 2] = pos.z +proc `*`*[T](a, b: GMat2[T]): GMat2[T] = + result[0, 0] = b[0, 0] * a[0, 0] + b[0, 1] * a[1, 0] + result[0, 1] = b[0, 0] * a[0, 1] + b[0, 1] * a[1, 1] + + result[1, 0] = b[1, 0] * a[0, 0] + b[1, 1] * a[1, 0] + result[1, 1] = b[1, 0] * a[0, 1] + b[1, 1] * a[1, 1] + proc `*`*[T](a, b: GMat3[T]): GMat3[T] = result[0, 0] = b[0, 0] * a[0, 0] + b[0, 1] * a[1, 0] + b[0, 2] * a[2, 0] result[0, 1] = b[0, 0] * a[0, 1] + b[0, 1] * a[1, 1] + b[0, 2] * a[2, 1] @@ -1160,8 +1167,15 @@ proc `*`*[T](a: GMat4[T], b: GVec4[T]): GVec4[T] = a[0, 3] * b.x + a[1, 3] * b.y + a[2, 3] * b.z + a[3, 3] * b.w ) +proc transpose*[T](a: GMat2[T]): GMat2[T] = + ## Return a transpose of the matrix. + gmat2[T]( + a[0, 0], a[1, 0], + a[0, 1], a[1, 1] + ) + proc transpose*[T](a: GMat3[T]): GMat3[T] = - ## Return an transpose of the matrix. + ## Return a transpose of the matrix. gmat3[T]( a[0, 0], a[1, 0], a[2, 0], a[0, 1], a[1, 1], a[2, 1], @@ -1177,6 +1191,10 @@ proc transpose*[T](a: GMat4[T]): GMat4[T] = a[0, 3], a[1, 3], a[2, 3], a[3, 3] ) +proc determinant*[T](a: GMat2[T]): T = + ## Compute a determinant of the matrix. + a[0, 0] * a[1, 1] - a[1, 0] * a[0, 1] + proc determinant*[T](a: GMat3[T]): T = ## Compute a determinant of the matrix. ( @@ -1213,6 +1231,14 @@ proc determinant*[T](a: GMat4[T]): T = a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33 ) +proc inverse*[T](a: GMat2[T]): GMat2[T] = + ## Return an inverse of the matrix. + let invDet = 1 / a.determinant + result[0, 0] = +a[1, 1] * invDet + result[0, 1] = -a[0, 1] * invDet + result[1, 0] = -a[1, 0] * invDet + result[1, 1] = +a[0, 0] * invDet + proc inverse*[T](a: GMat3[T]): GMat3[T] = ## Return an inverse of the matrix. let From dd88dd027af8acb33bb99f4d01112cd4bbce54e4 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 10:27:45 -0700 Subject: [PATCH 24/35] Undo things, y-up default. --- src/vmath.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vmath.nim b/src/vmath.nim index 746e22b..d6c5006 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -1048,7 +1048,7 @@ proc down*[T](a: GMat4[T]): GVec3[T] {.inline.} = proc pos*[T](a: GMat4[T]): GVec3[T] = ## Position of the matrix. - gvec3[T](a[3, 0], a[3, 1], a[3, 2]) + 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. @@ -1622,7 +1622,7 @@ proc lookAt*[T](eye, center, up: GVec3[T]): GMat4[T] = proc lookAt*[T](eye, center: GVec3[T]): GMat4[T] = ## Look at center from eye with default UP vector. - lookAt(eye, center, gvec3(T(0), 0, 1)) + lookAt(eye, center, gvec3(T(0), 1, 0)) proc angle*[T](a: GVec2[T]): T = ## Angle of a Vec2. From 6d7d3e856d4fbb141bea9f832836c6a26364611a Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 13:12:13 -0700 Subject: [PATCH 25/35] Re do the tests based on the dump scripts. --- README.md | 25 +- tests/test.nim | 2105 ++++++++++++++++++++++-------------------------- 2 files changed, 952 insertions(+), 1178 deletions(-) diff --git a/README.md b/README.md index 530205e..3839e79 100644 --- a/README.md +++ b/README.md @@ -112,11 +112,6 @@ vmath follows the standard glTF / OpenGL conventions: right-handed coordinate sy We run identical math operations across vmath, GLSL, [nim-glm](https://github.com/nickelsworth/nim-glm), [gl-matrix](https://github.com/toji/gl-matrix), and [Jolt Physics](https://github.com/jrouwe/JoltPhysics) and compare the generated dumps in [conformance/](conformance/). -The current takeaway is: - -* Math results match across vmath, GLSL, nim-GLM, gl-matrix, and Jolt for the tested constructors, transforms, quaternions, inverses, cross products, slerp, and `fromTwoVectors`. -* API and indexing conventions still differ even when the math matches. -* Jolt is the main convention outlier: it matches the tested math, but its matrix indexing/order conventions line up with the DirectX/HLSL side rather than the math-style `[row, col]` convention used by vmath. | Feature | vmath | GLSL | nim-GLM | gl-matrix | Jolt Physics | |----------------------------|:-----:|:----:|:-------:|:---------:|:------------:| @@ -127,6 +122,9 @@ The current takeaway is: | Rotation matrices | ✅ | ✅ | ✅ | ✅ | ✅ | | Translation matrices | ✅ | ✅ | ✅ | ✅ | ✅ | | Scale matrices | ✅ | ✅ | ✅ | ✅ | ✅ | +| Mat2 | ✅ | ✅ | ✅ | ✅ | N/A | +| Mat3 | ✅ | ✅ | ✅ | ✅ | N/A | +| Mat3 2D constructors. | ✅ | ✅ | ✅ | ✅ | N/A | | Quaternion constructors | ✅ | ✅ | ✅ | ✅ | ✅ | | Quaternion multiply | ✅ | ✅ | ✅ | ✅ | ✅ | | Quaternion vector rotation | ✅ | ✅ | ✅ | ✅ | ✅ | @@ -143,23 +141,22 @@ The current takeaway is: | Perspective matrix | ✅ | ✅ | ✅ | ✅ | ❌ | | Ortho matrix | ✅ | ✅ | ✅ | ✅ | N/A | | Euler angle decomposition | ✅ | ✅ | ✅ | N/A | ✅ | -| Element access | ✅ | ✅ | ✅ | N/A | ✅ | +| Element access | ✅ | ✅ | ✅ | N/A | ❌ | ❌ **Perspective matrix**: Jolt Physics uses Z range `[0, 1]` (Vulkan/DirectX convention) while vmath uses Z range `[-1, 1]` (OpenGL convention). The X and Y scaling match, but Z-related elements differ. -**Element access note**: GLSL matrix indexing is `m[column][row]`, not math-style `[row, col]`. gl-matrix also does not expose built-in 2D matrix indexing; it uses a flat 16-element array and callers map indices manually. Jolt supports element access too, but its indexing/order convention differs from vmath's math-style `[row, col]` interpretation. +❌ **Element access note**: Jolt does convention differs from vmath's math-style `[row, col]` interpretation it follows the DirectX/HLSL convention. # 2.x.x to 3.0.0 vmath breaking changes: -Version `3.0.0` realigned vmath around standard graphics conventions so its behavior matches the math used by GLSL, GLM, gl-matrix, and the rest of the conformance suite much more closely. +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. -* **Matrix indexing changed from `[col, row]` to `[row, col]`.** The syntax is the same, but the meaning of the two indices swapped to match standard math notation. -* **`mat4(1..16)` no longer transposes its arguments.** Values are now stored directly in column-major order. If your old code passed row-major literals, transpose them before constructing the matrix. -* **Matrix multiplication now uses standard `A * B` composition.** That means `B` is applied first, then `A`. -* **`mat4 * vec3` now means `M * v`.** Older code that relied on the transposed behavior will need to be updated. -* **2D `rotate(angle)` now produces the standard counter-clockwise rotation matrix.** +* **Rotation matrices (`rotateX`, `rotateY`, `rotateZ`, 2D `rotate`) now use standard CCW (counter-clockwise) convention.** The sin/cos sign placement in the rotation matrices was swapped. If your code relied on the old CW direction, please negate the angle. +* **Quaternion ↔ matrix conversion (`quat()`, `mat4()`) rewritten.** Uses the standard "biggest component" algorithm matching GLM/GLSL. The off-diagonal signs in `mat4(quat)` were swapped to match the new rotation direction. +* **`toAngles` corrected signs and added gimbal-lock clamping.** No longer negates the Y and Z angles to match the new rotation direction. * **`fromTwoVectors(a, b)` now rotates `a` into `b`.** Previous behavior had that direction reversed. -* **`toAngles` / `fromAngles` now use the standard Y-X-Z convention with corrected signs.** +* **`lookAt()` un-deprecated.** Restored with Y-up which is glTF/OpenGL convention. +* **New functions added:** `quatInverse`, `slerp`, `quatMultiply`, `quatRotate`, `quatRotateX/Y/Z`, `toAngles(quat)`, `mat4(DMat4)`, `dmat4(Mat4)`, and Mat2 operations (`*`, `transpose`, `determinant`, `inverse`). # 1.x.x to 2.0.0 vmath breaking changes: * New right-hand-Z-forward coordinate system and functions that care about diff --git a/tests/test.nim b/tests/test.nim index 5cf449c..bed526a 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -1,1190 +1,967 @@ import - std/random, - vmath + std/[math, random, unittest], + vmath {.all.} randomize(1234) -block: - # Test ~=. - doAssert 1.0 ~= 1.0 - doAssert 0.0 ~= 0.0 - doAssert -1.0 ~= -1.0 - doAssert not(0.1 ~= 0.2) - doAssert not(0.01 ~= 0.02) - doAssert not(0.001 ~= 0.002) - doAssert not(0.0001 ~= 0.0002) - doAssert not(0.00001 ~= 0.00002) - - # Diff < epsilon. - doAssert 0.000001 ~= 0.000002 - doAssert -0.000001 ~= -0.000002 - - doAssert vec2(1.0, 2.0) ~= vec2(1.0, 2.0) - doAssert vec3(1.0, 2.0, 3.0) ~= vec3(1.0, 2.0, 3.0) - doAssert vec4(1.0, 2.0, 3.0, 4.0) ~= vec4(1.0, 2.0, 3.0, 4.0) - doAssert quat(1.0, 2.0, 3.0, 4.0) ~= quat(1.0, 2.0, 3.0, 4.0) - - doAssert dvec2(1) ~= dvec2(1) - doAssert dvec4(1, 2, 3, 4).xy ~= dvec2(1, 2) - - when compiles(1 ~= 1): - doAssert false - -block: - # Test simple functions. - doAssert between(0.5, 0, 1) - doAssert not between(1.5, 0, 1) - - doAssert sign(-1.0) == -1.0 - doAssert sign(0.0) == 1.0 - doAssert sign(1.0) == 1.0 - - block: - proc quantize2(v, n: float32): float32 = - ## Makes v be multiple of n. Rounding to integer quantize by 1.0. - sign(v) * trunc(abs(v) / n) * n - - let n = 1.float32 / 10 - - for _ in 0 ..< 10_000: - let f = rand(-100.float32 .. 100.float32) - doAssert quantize(f, n) == quantize2(f, n) - - doAssert quantize(1.23456789, 1.0) ~= 1 - doAssert quantize(1.23456789, 0.1) ~= 1.2 - doAssert quantize(1.23456789, 0.01) ~= 1.23 - doAssert quantize(-1.23456789, 0.01) ~= -1.23 - - 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 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 - 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 fixAngle(0.1) ~= 0.1 - doAssert fixAngle(1.1) ~= 1.1 - doAssert fixAngle(2.1) ~= 2.1 - doAssert fixAngle(3.1) ~= 3.1 - doAssert fixAngle(4.1) ~= -2.183185577392578 - doAssert fixAngle(-0.1) ~= -0.1 - doAssert fixAngle(-1.1) ~= -1.1 - doAssert fixAngle(-2.1) ~= -2.1 - doAssert fixAngle(-3.1) ~= -3.1 - doAssert fixAngle(-4.1) ~= 2.183185577392578 - - doAssert angleBetween(0.0, 1.0) ~= 1.0 - doAssert angleBetween(0.0, PI) ~= PI - doAssert angleBetween(0.0, PI + 0.2) ~= (-PI + 0.2) - doAssert angleBetween(0.1, 0.2) ~= 0.1 - doAssert angleBetween(0.1, 0.2 + PI*2) ~= 0.1 - doAssert angleBetween(0.1, 0.2 - PI*2) ~= 0.1 - doAssert angleBetween(0.1 + PI*2, 0.2) ~= 0.1 - doAssert angleBetween(0.1 - PI*2, 0.2) ~= 0.1 - doAssert angleBetween(0.2, 0.1) ~= -0.1 - doAssert angleBetween(0.2, 0.1 - PI*2) ~= -0.1 - doAssert angleBetween(0.2, 0.1 + PI*2) ~= -0.1 - doAssert angleBetween(0.2 + PI*2, 0.1) ~= -0.1 - doAssert angleBetween(0.2 - PI*2, 0.1) ~= -0.1 - - doAssert turnAngle(0.0, PI, 0.5) ~= 0.5 - doAssert turnAngle(0.5, PI, 3.5) ~= PI - - proc isNaNSlow(f: SomeFloat): bool = - ## Returns true if number is a NaN. - f.classify notin {fcNormal, fcZero, fcSubnormal} - - doAssert isNaNSlow(0.3) == false - doAssert isNaNSlow(0.0) == false - doAssert isNaNSlow(0.3/0.0) == true - doAssert isNaNSlow(-0.3/0.0) == true - doAssert isNaNSlow(5.0e-324) == false - - doAssert isNan(float32(0.3)) == false - doAssert isNan(float32(0.0)) == false - doAssert isNan(float32(0.3/0.0)) == true - doAssert isNan(float32(-0.3/0.0)) == true - doAssert isNan(float32(5.0e-324)) == false - - doAssert isNan(float64(0.3)) == false - doAssert isNan(float64(0.0)) == false - doAssert isNan(float64(0.3/0.0)) == true - doAssert isNan(float64(-0.3/0.0)) == true - doAssert isNan(float64(5.0e-324)) == false - -block: - when not defined(js): - # Test vec2 cast. - var v = vec2(1.0, 2.0) - var a = cast[array[2, float32]](v) - doAssert a[0] ~= 1.0 - doAssert a[1] ~= 2.0 - -block: - # Test position assignment - var - v2 = vec2(0) - v3 = vec3(0) - v4 = vec4(0) - v2[0] = 1.0 - v2[1] = 2.0 - doAssert v2 ~= vec2(1, 2) - v3[0] = 1.0 - v3[1] = 2.0 - v3[2] = 3.0 - doAssert v3 ~= vec3(1, 2, 3) - v4[0] = 1.0 - v4[1] = 2.0 - v4[2] = 3.0 - v4[3] = 4.0 - doAssert v4 ~= vec4(1, 2, 3, 4) - -block: - # Test vec2 constructor. - doAssert vec2(PI, PI) ~= vec2(PI) - -block: - # Test basic vector vec2. - var a = vec2(1, 2) - var b = vec2(7, 6) - var n = 13.7 - doAssert a + b ~= vec2(8.0, 8.0) - doAssert a - b ~= vec2(-6.0, -4.0) - doAssert a * n ~= vec2(13.7, 27.4) - doAssert a / n ~= vec2(0.0729927, 0.1459854) - a += b - doAssert a ~= vec2(8.0, 8.0) - a -= b - doAssert a ~= vec2(1.0, 2.0) - a *= n - doAssert a ~= vec2(13.7, 27.4) - a /= n - doAssert a ~= vec2(1.0, 2.0) - -block: - # Test basic vector vec3. - var a = vec3(1, 2, 3) - var b = vec3(7, 6, 5) - var n = 13.7 - doAssert a + b ~= vec3(8.0, 8.0, 8.0) - doAssert a - b ~= vec3(-6.0, -4.0, -2.0) - doAssert a * n ~= vec3(13.69999981, 27.39999962, 41.09999847) - doAssert a / n ~= vec3(0.07299270, 0.14598541, 0.21897811) - a += b - doAssert a ~= vec3(8.0, 8.0, 8.0) - a -= b - doAssert a ~= vec3(1.0, 2.0, 3.0) - a *= n - doAssert a ~= vec3(13.69999981, 27.39999962, 41.09999847) - a /= n - doAssert a ~= vec3(1.0, 2.0, 3.0) - -block: - # Test basic vector vec4. - var a = vec4(1, 2, 3, 4) - var b = vec4(7, 6, 5, 4) - var n = 13.7 - doAssert a + b ~= vec4(8.0, 8.0, 8.0, 8.0) - doAssert a - b ~= vec4(-6.0, -4.0, -2.0, 0.0) - doAssert a * n ~= vec4(13.69999981, 27.39999962, 41.09999847, 54.79999924) - doAssert a / n ~= vec4(0.07299270, 0.14598541, 0.21897811, 0.29197082) - a += b - doAssert a ~= vec4(8.0, 8.0, 8.0, 8.0) - a -= b - doAssert a ~= vec4(1.0, 2.0, 3.0, 4.0) - a *= n - doAssert a ~= vec4(13.69999981, 27.39999962, 41.09999847, 54.79999924) - a /= n - doAssert a ~= vec4(1.0, 2.0, 3.0, 4.0) - -block: - # Test all type constructors compile +suite "approximate equality": + test "float ~=": + check 1.0 ~= 1.0 + check 0.0 ~= 0.0 + check -1.0 ~= -1.0 + check not(0.1 ~= 0.2) + check not(0.01 ~= 0.02) + check not(0.001 ~= 0.002) + check not(0.0001 ~= 0.0002) + check not(0.00001 ~= 0.00002) + check 0.000001 ~= 0.000002 + check -0.000001 ~= -0.000002 + + test "vec ~=": + check vec2(1.0, 2.0) ~= vec2(1.0, 2.0) + check vec3(1.0, 2.0, 3.0) ~= vec3(1.0, 2.0, 3.0) + check vec4(1.0, 2.0, 3.0, 4.0) ~= vec4(1.0, 2.0, 3.0, 4.0) + check quat(1.0, 2.0, 3.0, 4.0) ~= quat(1.0, 2.0, 3.0, 4.0) + check dvec2(1) ~= dvec2(1) + check dvec4(1, 2, 3, 4).xy ~= dvec2(1, 2) + + test "int ~= should not compile": + check not compiles(1 ~= 1) + +suite "scalar utilities": + test "between": + check between(0.5, 0, 1) + check not between(1.5, 0, 1) + + test "sign": + check sign(-1.0) == -1.0 + check sign(0.0) == 1.0 + check sign(1.0) == 1.0 + + test "quantize": + check quantize(1.23456789, 1.0) ~= 1 + check quantize(1.23456789, 0.1) ~= 1.2 + check quantize(1.23456789, 0.01) ~= 1.23 + check quantize(-1.23456789, 0.01) ~= -1.23 + + test "fract": + check fract(0.0) ~= 0.0 + check fract(3.14) ~= 0.14 + check fract(-3.14) ~= 0.14 + check fract(1.23456789) ~= 0.23456789 + + test "mix": + check mix(0.0, 1.0, 0.5) ~= 0.5 + check mix(0.0, 10.0, 0.5) ~= 5.0 + check mix(-1.0, 1.0, 0.25) ~= -0.5 + + test "fixAngle": + check fixAngle(0.1) ~= 0.1 + check fixAngle(3.1) ~= 3.1 + check fixAngle(4.1) ~= -2.183185577392578 + check fixAngle(-4.1) ~= 2.183185577392578 + + test "angleBetween": + check angleBetween(0.0, 1.0) ~= 1.0 + check angleBetween(0.0, PI) ~= PI + check angleBetween(0.1, 0.2) ~= 0.1 + check angleBetween(0.1, 0.2 + PI*2) ~= 0.1 + check angleBetween(0.2, 0.1) ~= -0.1 + + test "isNan": + check vmath.isNan(float32(0.3)) == false + check vmath.isNan(float32(0.0)) == false + check vmath.isNan(float32(0.3/0.0)) == true + check vmath.isNan(float64(0.3/0.0)) == true + +suite "vector memory layout": + test "vec2 cast to array": + when not defined(js): + let v = vec2(1.0, 2.0) + let a = cast[array[2, float32]](v) + check a[0] == 1.0f + check a[1] == 2.0f + + test "vec3 cast to array": + when not defined(js): + let v = vec3(1.0, 2.0, 3.0) + let a = cast[array[3, float32]](v) + check a[0] == 1.0f + check a[1] == 2.0f + check a[2] == 3.0f + + test "vec4 cast to array": + when not defined(js): + let v = vec4(1.0, 2.0, 3.0, 4.0) + let a = cast[array[4, float32]](v) + check a[0] == 1.0f + check a[1] == 2.0f + check a[2] == 3.0f + check a[3] == 4.0f + + test "vec component access matches array index": + let v2 = vec2(10, 20) + check v2.x == v2[0] + check v2.y == v2[1] + + let v3 = vec3(10, 20, 30) + check v3.x == v3[0] + check v3.y == v3[1] + check v3.z == v3[2] + + let v4 = vec4(10, 20, 30, 40) + check v4.x == v4[0] + check v4.y == v4[1] + check v4.z == v4[2] + check v4.w == v4[3] + + test "vec component assignment": + var v2 = vec2(0) + v2[0] = 1.0; v2[1] = 2.0 + check v2 ~= vec2(1, 2) + + var v3 = vec3(0) + v3[0] = 1.0; v3[1] = 2.0; v3[2] = 3.0 + check v3 ~= vec3(1, 2, 3) + + var v4 = vec4(0) + v4[0] = 1.0; v4[1] = 2.0; v4[2] = 3.0; v4[3] = 4.0 + check v4 ~= vec4(1, 2, 3, 4) + +suite "mat2 memory layout and element access": + test "mat2 cast to flat array is column-major": + when not defined(js): + # Memory: [col0_row0, col0_row1, col1_row0, col1_row1] + let m = cast[Mat2]([1.0f, 2.0f, 3.0f, 4.0f]) + let a = cast[array[4, float32]](m) + check a[0] == 1.0f # col 0 row 0 + check a[1] == 2.0f # col 0 row 1 + check a[2] == 3.0f # col 1 row 0 + check a[3] == 4.0f # col 1 row 1 + + test "mat2 [row, col] element access": + when not defined(js): + let m = cast[Mat2]([1.0f, 2.0f, 3.0f, 4.0f]) + # [row, col] indexing into column-major memory + check m[0, 0] == 1.0f # arr[0*2+0] = arr[0] + check m[0, 1] == 2.0f # arr[0*2+1] = arr[1] + check m[1, 0] == 3.0f # arr[1*2+0] = arr[2] + check m[1, 1] == 4.0f # arr[1*2+1] = arr[3] + + test "mat2 element assignment": + var m = mat2() + m[0, 0] = 5.0; m[0, 1] = 6.0 + m[1, 0] = 7.0; m[1, 1] = 8.0 + check m[0, 0] == 5.0f + check m[0, 1] == 6.0f + check m[1, 0] == 7.0f + check m[1, 1] == 8.0f + + test "mat2 identity": + let m = mat2() + check m[0, 0] == 1.0f + check m[0, 1] == 0.0f + check m[1, 0] == 0.0f + check m[1, 1] == 1.0f + + test "mat2 scalar constructor": + let m = mat2(1, 2, 3, 4) + check m[0, 0] == 1.0f + check m[0, 1] == 2.0f + check m[1, 0] == 3.0f + check m[1, 1] == 4.0f + + test "mat2 vector column constructor": + let m = mat2(vec2(1, 2), vec2(3, 4)) + check m[0, 0] == 1.0f + check m[0, 1] == 2.0f + check m[1, 0] == 3.0f + check m[1, 1] == 4.0f + +suite "mat2 operations": let - _ = bvec2(true, false) - _ = bvec3(true, false, true) - _ = bvec4(true, false, true, false) - - _ = ivec2(-1, 2) - _ = ivec3(-1, 2, 3) - _ = ivec4(-1, 2, 3, 4) - - _ = uvec2(1, 2) - _ = uvec3(1, 2, 3) - _ = uvec4(1, 2, 3, 4) - - _ = vec2(1.0, 2.0) - _ = vec3(1.0, 2.0, 3.0) - _ = vec4(1.0, 2.0, 3.0, 4.0) - - _ = dvec2(1.0, 2.0) - _ = dvec3(1.0, 2.0, 3.0) - _ = dvec4(1.0, 2.0, 3.0, 4.0) - - _ = bvec2(true) - _ = bvec3(true) - _ = bvec4(true) - - _ = ivec2(-1) - _ = ivec3(-1) - _ = ivec4(-1) - - _ = uvec2(1) - _ = uvec3(1) - _ = uvec4(1) - - _ = vec2(1.0) - _ = vec3(1.0) - _ = vec4(1.0) - - _ = dvec2(1.0) - _ = dvec3(1.0) - _ = dvec4(1.0) - - _ = bvec2() - _ = bvec3() - _ = bvec4() - - _ = ivec2() - _ = ivec3() - _ = ivec4() - - _ = uvec2() - _ = uvec3() - _ = uvec4() - - _ = vec2() - _ = vec3() - _ = vec4() - - _ = dvec2() - _ = dvec3() - _ = dvec4() - - var a = vec3(vec2(1, 2), 3) - doAssert a == vec3(1, 2, 3) - - var b = vec4(vec3(1, 2, 3), 4) - doAssert b == vec4(1, 2, 3, 4) - - var c = vec4(vec2(1, 2), vec2(3, 4)) - doAssert c == vec4(1, 2, 3, 4) - -block: - # test $ string functions - doAssert $bvec2(true, false) == "bvec2(true, false)" - doAssert $bvec3(true, false, true) == "bvec3(true, false, true)" - doAssert $bvec4(true, false, true, false) == "bvec4(true, false, true, false)" - - doAssert $ivec2(1, 2) == "ivec2(1, 2)" - doAssert $ivec3(1, 2, 3) == "ivec3(1, 2, 3)" - doAssert $ivec4(1, 2, 3, 4) == "ivec4(1, 2, 3, 4)" - - doAssert $uvec2(1, 2) == "uvec2(1, 2)" - doAssert $uvec3(1, 2, 3) == "uvec3(1, 2, 3)" - doAssert $uvec4(1, 2, 3, 4) == "uvec4(1, 2, 3, 4)" - - doAssert $vec2(1.0, 2.0) == "vec2(1.0, 2.0)" - doAssert $vec3(1.0, 2.0, 3.0) == "vec3(1.0, 2.0, 3.0)" - doAssert $vec4(1.0, 2.0, 3.0, 4.0) == "vec4(1.0, 2.0, 3.0, 4.0)" - - doAssert $dvec2(1.0, 2.0) == "dvec2(1.0, 2.0)" - doAssert $dvec3(1.0, 2.0, 3.0) == "dvec3(1.0, 2.0, 3.0)" - doAssert $dvec4(1.0, 2.0, 3.0, 4.0) == "dvec4(1.0, 2.0, 3.0, 4.0)" - -block: - # test swizzle vec - var a = vec2(1, 2) - doAssert a.x == 1.0 - doAssert a.y == 2.0 - doAssert a.yx == vec2(2, 1) - doAssert a.gr == vec2(2, 1) - doAssert a.ts == vec2(2, 1) - doAssert a.xxx == vec3(1, 1, 1) - - a.yx = vec2(-1, -2) - doAssert a == vec2(-2, -1) - - a.xx = vec2(-7, -3) - doAssert a == vec2(-3, -1) - - when compiles(a.xyzxyz): - doAssert false - - when compiles(a.z = 123): - doAssert false - - var b = vec4(1, 2, 3, 4) - doAssert b == vec4(1, 2, 3, 4) - b.wzyx = b - doAssert b == vec4(4, 3, 2, 1) - - b.g = 123 - doAssert b == vec4(4.0, 123.0, 2.0, 1.0) - -block: - # test swizzle dvec float64 - var a = dvec2(1, 2) - doAssert a.x == 1.0 - doAssert a.y == 2.0 - doAssert a.yx == dvec2(2, 1) - doAssert a.gr == dvec2(2, 1) - doAssert a.ts == dvec2(2, 1) - doAssert a.xxx == dvec3(1, 1, 1) - - a.yx = dvec2(-1, -2) - doAssert a == dvec2(-2, -1) - - a.xx = dvec2(-7, -3) - doAssert a == dvec2(-3, -1) - - when compiles(a.xyzxyz): - doAssert false - - when compiles(a.z = 123): - doAssert false - - var b = dvec4(1, 2, 3, 4) - doAssert b == dvec4(1, 2, 3, 4) - - b.g = 123 - doAssert b == dvec4(1.0, 123.0, 3.0, 4.0) - -block: - # test swizzle self-assignment - var a = dvec2(1, 2) - a.yx = a - doAssert a == dvec2(2, 1) - - var b = dvec3(1, 2, 3) - b.zyx = b - doAssert b == dvec3(3, 2, 1) - - var c = dvec4(1, 2, 3, 4) - c.wzyx = c - doAssert c == dvec4(4, 3, 2, 1) - -block: - # Test swizzle calls only once - var callCount = 0 - proc countsCalls(): Vec2 = - inc callCount - - doAssert countsCalls().yx == vec2(0, 0) - doAssert callCount == 1 - - callCount = 0 - doAssert vec2(0, 0) == countsCalls().yx - doAssert callCount == 1 - - var tmp: Vec2 - proc countsCalls2(): var Vec2 = - inc callCount - return tmp - - callCount = 0 - countsCalls2().yx = vec2(0, 0) - doAssert callCount == 1 - -block: - # Test swizzle with complex expressions - var a = [ - vec2(1, 2), - vec2(3, 4), - vec2(5, 6), - vec2(7, 8), - ] - var i = 0 - proc f(): var Vec2 = - # function with side effects - result = a[i] - inc i - - doAssert f().yx == vec2(2, 1) - doAssert f().gr == vec2(4, 3) - doAssert f().ts == vec2(6, 5) - doAssert f().yx == vec2(8, 7) - doAssert i == 4 - - i = 0 - f().yx = f() - doAssert a[0] == vec2(4, 3) - doAssert a[1] == vec2(3, 4) - doAssert i == 2 - - var b = [ - vec3(1, 2, 3), - vec3(4, 5, 6), - vec3(7, 8, 9), - ] - i = 0 - proc g(): var Vec3 = - # function with side effects - result = b[i] - inc i - - doAssert g().yxz == vec3(2, 1, 3) - doAssert g().bgr == vec3(6, 5, 4) - doAssert g().tps == vec3(8, 9, 7) - doAssert i == 3 - - i = 0 - g().yxz = g() - doAssert b[0] == vec3(5, 4, 6) - doAssert b[1] == vec3(4, 5, 6) - doAssert i == 2 - - var c = [ - vec4(1, 2, 3, 4), - vec4(5, 6, 7, 8), - ] - i = 0 - proc h(): var Vec4 = - # function with side effects - result = c[i] - inc i - - doAssert h().yxzw == vec4(2, 1, 3, 4) - doAssert h().tqsp == vec4(6, 8, 5, 7) - doAssert i == 2 - - i = 0 - h().wzyx = h() - doAssert c[0] == vec4(8, 7, 6, 5) - doAssert c[1] == vec4(5, 6, 7, 8) - doAssert i == 2 - - -block: - # Test basic mat constructors. - block: - let - _ = mat2() - _ = mat3() - _ = mat4() - - block: - let - _ = mat2( - 1, 0, - 0, 1 - ) - _ = mat3( - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - ) - _ = mat4( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ) + a = cast[Mat2]([1.0f, 2.0f, 3.0f, 4.0f]) + b = cast[Mat2]([5.0f, -6.0f, 7.0f, -8.0f]) + + test "mat2 multiply": + let ab = a * b + let ba = b * a + check not(ab ~= ba) # non-commutative + # identity * a = a + check mat2() * a ~= a + check a * mat2() ~= a + + test "mat2 transpose": + let t = transpose(a) + check t[0, 0] == a[0, 0] + check t[0, 1] == a[1, 0] + check t[1, 0] == a[0, 1] + check t[1, 1] == a[1, 1] + # double transpose = original + check transpose(transpose(a)) ~= a + + test "mat2 determinant": + check determinant(a) == 1.0f * 4.0f - 3.0f * 2.0f # ad - bc = -2 + + test "mat2 inverse": + let inv = inverse(a) + check a * inv ~= mat2() + check inv * a ~= mat2() + + test "mat2 * vec2": + let v = vec2(1.25, -2.5) + let result = a * v + # Verify by manual computation + check result.x ~= (a[0, 0] * v.x + a[1, 0] * v.y) + check result.y ~= (a[0, 1] * v.x + a[1, 1] * v.y) + +suite "mat3 memory layout and element access": + test "mat3 cast to flat array is column-major": + when not defined(js): + # 9 floats: col0(3), col1(3), col2(3) + let m = cast[Mat3]([1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f]) + let a = cast[array[9, float32]](m) + check a[0] == 1.0f # col 0 row 0 + check a[1] == 2.0f # col 0 row 1 + check a[2] == 3.0f # col 0 row 2 + check a[3] == 4.0f # col 1 row 0 + check a[4] == 5.0f # col 1 row 1 + check a[5] == 6.0f # col 1 row 2 + check a[6] == 7.0f # col 2 row 0 + check a[7] == 8.0f # col 2 row 1 + check a[8] == 10.0f # col 2 row 2 + + test "mat3 [row, col] element access": + when not defined(js): + let m = cast[Mat3]([1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f]) + check m[0, 0] == 1.0f + check m[0, 1] == 2.0f + check m[0, 2] == 3.0f + check m[1, 0] == 4.0f + check m[1, 1] == 5.0f + check m[1, 2] == 6.0f + check m[2, 0] == 7.0f + check m[2, 1] == 8.0f + check m[2, 2] == 10.0f + + test "mat3 identity": + let m = mat3() + for r in 0 .. 2: + for c in 0 .. 2: + if r == c: + check m[r, c] == 1.0f + else: + check m[r, c] == 0.0f + + test "mat3 element assignment": + var m = mat3() + m[1, 2] = 42.0 + check m[1, 2] == 42.0f + +suite "mat3 operations": + let + a = cast[Mat3]([1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f]) + b = cast[Mat3]([-1.0f, 3.0f, 5.0f, 7.0f, -2.0f, 4.0f, 6.0f, 8.0f, -3.0f]) + + test "mat3 multiply": + check mat3() * a ~= a + check a * mat3() ~= a + check not(a * b ~= b * a) + + test "mat3 transpose": + let t = transpose(a) + for r in 0 .. 2: + for c in 0 .. 2: + check t[r, c] == a[c, r] + check transpose(transpose(a)) ~= a + + test "mat3 determinant non-zero": + # [1..8, 10] has det = -3 + check abs(determinant(a) - (-3.0f)) < 0.001f + + test "mat3 inverse": + let inv = inverse(a) + check a * inv ~= mat3() + check inv * a ~= mat3() + + test "mat3 * vec3": + let v = vec3(1.0, -2.0, 3.0) + let result = a * v + check result.x ~= (a[0, 0] * v.x + a[1, 0] * v.y + a[2, 0] * v.z) + check result.y ~= (a[0, 1] * v.x + a[1, 1] * v.y + a[2, 1] * v.z) + check result.z ~= (a[0, 2] * v.x + a[1, 2] * v.y + a[2, 2] * v.z) + + test "mat3 * vec2 (2D homogeneous)": + let v = vec2(3.0, -1.5) + let result = a * v + # Should treat vec2 as vec3(x, y, 1) and return xy + let full = a * vec3(v.x, v.y, 1.0) + check result.x ~= full.x + check result.y ~= full.y + +suite "mat3 2D constructors": + test "scale2D": + let s = scale(vec2(2.0, 3.0)) + check s[0, 0] == 2.0f + check s[1, 1] == 3.0f + check s[2, 2] == 1.0f + check s[1, 0] == 0.0f + # Scaling a point + let v = s * vec2(5.0, 10.0) + check v ~= vec2(10.0, 30.0) + + test "translate2D": + let t = translate(vec2(5.0, 10.0)) + check t[2, 0] == 5.0f + check t[2, 1] == 10.0f + let v = t * vec2(1.0, 2.0) + check v ~= vec2(6.0, 12.0) + + test "rotate2D": + let r = rotate(45.0f.toRadians) + # cos(45) ~= 0.707, sin(45) ~= 0.707 + check r[0, 0] ~= cos(45.0f.toRadians) + check r[0, 1] ~= sin(45.0f.toRadians) + check r[1, 0] ~= -sin(45.0f.toRadians) + check r[1, 1] ~= cos(45.0f.toRadians) + # Rotating (1, 0) by 90 degrees should give (0, 1) + let r90 = rotate(90.0f.toRadians) + check r90 * vec2(1.0, 0.0) ~= vec2(0.0, 1.0) + + test "2D transform composition": + let mat2d = translate(vec2(10, 20)) * rotate(45.toRadians) * scale(vec2(2)) + check mat2d ~= mat3( + 1.414213538169861, 1.414213538169861, 0.0, + -1.414213538169861, 1.414213538169861, 0.0, + 10.0, 20.0, 1.0 + ) - block: - # test $ string functions - doAssert $mat2( - 1, 3, - 0, 1 - ) == """mat2( - 1.0, 3.0, - 0.0, 1.0 -)""" - doAssert $mat3( - 1, 3, 0, - 0, 1, 0, - 0, 3, 1 - ) == """mat3( - 1.0, 3.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 3.0, 1.0 -)""" - doAssert $mat4( - 1, 3, 0, 0, - 0, 1, 0, 0, - 0, 3, 1, 0, - 0, 3, 0, 1 - ) == """mat4( - 1.0, 3.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 3.0, 1.0, 0.0, - 0.0, 3.0, 0.0, 1.0 -)""" - doAssert $dmat2( - 1, 0, - 4, 1 - ) == """dmat2( - 1.0, 0.0, - 4.0, 1.0 -)""" - doAssert $dmat3( - 1, 0, 0, - 4, 1, 0, - 4, 0, 1 - ) == """dmat3( - 1.0, 0.0, 0.0, - 4.0, 1.0, 0.0, - 4.0, 0.0, 1.0 -)""" - doAssert $dmat4( +suite "mat4 memory layout and element access": + test "mat4 cast to flat array is column-major": + when not defined(js): + let m = cast[Mat4]([ + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f + ]) + let a = cast[array[16, float32]](m) + # Column 0 + check a[0] == 1.0f; check a[1] == 2.0f; check a[2] == 3.0f; check a[3] == 4.0f + # Column 1 + check a[4] == 5.0f; check a[5] == 6.0f; check a[6] == 7.0f; check a[7] == 8.0f + # Column 2 + check a[8] == 9.0f; check a[9] == 10.0f; check a[10] == 11.0f; check a[11] == 12.0f + # Column 3 + check a[12] == 13.0f; check a[13] == 14.0f; check a[14] == 15.0f; check a[15] == 16.0f + + test "mat4 [row, col] element access matches flat column-major": + when not defined(js): + let m = cast[Mat4]([ + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f + ]) + let a = cast[array[16, float32]](m) + for row in 0 .. 3: + for col in 0 .. 3: + # m[row, col] should equal a[row * 4 + col] + check m[row, col] == a[row * 4 + col] + + test "mat4 identity": + let m = mat4() + for r in 0 .. 3: + for c in 0 .. 3: + if r == c: + check m[r, c] == 1.0f + else: + check m[r, c] == 0.0f + + test "mat4 constructors match": + let fromScalars = mat4( 1, 0, 0, 0, - 4, 1, 0, 0, - 4, 0, 1, 0, - 4, 0, 0, 1 - ) == """dmat4( - 1.0, 0.0, 0.0, 0.0, - 4.0, 1.0, 0.0, 0.0, - 4.0, 0.0, 1.0, 0.0, - 4.0, 0.0, 0.0, 1.0 -)""" + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ) + let fromVecs = mat4( + vec4(1, 0, 0, 0), + vec4(0, 1, 0, 0), + vec4(0, 0, 1, 0), + vec4(0, 0, 0, 1) + ) + check fromScalars ~= fromVecs + check fromScalars ~= mat4() - block: +suite "mat4 operations": + let + a = cast[Mat4]([ + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f + ]) + b = cast[Mat4]([ + -10.0f, -20.0f, -30.0f, -40.0f, + 50.0f, 60.0f, 70.0f, 80.0f, + 90.0f, 100.0f, 110.0f, 120.0f, + 130.0f, 140.0f, 150.0f, 160.0f + ]) + + test "mat4 multiply identity": + check mat4() * a ~= a + check a * mat4() ~= a + + test "mat4 multiply non-commutative": + check not(a * b ~= b * a) + + test "mat4 transpose": + let t = transpose(a) + for r in 0 .. 3: + for c in 0 .. 3: + check t[r, c] == a[c, r] + check transpose(transpose(a)) ~= a + + test "mat4 inverse of invertible matrix": + # Pure rotation + translation (well-conditioned) + let m = translate(vec3(10, 20, 30)) * rotateZ(71.toRadians) * + rotateY(-23.toRadians) * rotateX(37.toRadians) + let inv = inverse(m) + check m * inv ~= mat4() + check inv * m ~= mat4() + + test "mat4 inverse with non-uniform scale (double)": + # Non-uniform scale needs float64 for ~= tolerance + let m = translate(dvec3(10, 20, 30)) * rotateZ(71.0.toRadians) * + rotateY(-23.0.toRadians) * rotateX(37.0.toRadians) * scale(dvec3(2, 3, 4)) + let inv = inverse(m) + check m * inv ~= dmat4() + check inv * m ~= dmat4() + + test "mat4 * vec3": + let m = translate(vec3(10, 20, 30)) + let v = vec3(1.25, -2.5, 3.75) + let result = m * v + check result ~= vec3(11.25, 17.5, 33.75) + + test "mat4 * vec4": + let m = translate(vec3(10, 20, 30)) + let v = vec4(1.25, -2.5, 3.75, 1.0) + let result = m * v + check result ~= vec4(11.25, 17.5, 33.75, 1.0) + +suite "mat4 constructors": + test "scale matrix": + let s = scale(vec3(2, 3, 4)) + check s[0, 0] == 2.0f + check s[1, 1] == 3.0f + check s[2, 2] == 4.0f + check s[3, 3] == 1.0f + check s * vec3(1, 1, 1) ~= vec3(2, 3, 4) + + test "translate matrix": + let t = translate(vec3(10, 20, 30)) + check t[3, 0] == 10.0f + check t[3, 1] == 20.0f + check t[3, 2] == 30.0f + check t * vec3(0, 0, 0) ~= vec3(10, 20, 30) + + test "rotateX matrix": + let r = rotateX(90.0f.toRadians) + # Rotating Y-axis around X by 90 should give Z-axis + check r * vec3(0, 1, 0) ~= vec3(0, 0, 1) + + test "rotateY matrix": + let r = rotateY(90.0f.toRadians) + # Rotating X-axis around Y by 90 should give -Z-axis + check r * vec3(1, 0, 0) ~= vec3(0, 0, -1) + + test "rotateZ matrix": + let r = rotateZ(90.0f.toRadians) + # Rotating X-axis around Z by 90 should give Y-axis + check r * vec3(1, 0, 0) ~= vec3(0, 1, 0) + + test "transform composition order": + # T * R * S: scale first, then rotate, then translate + let t = translate(vec3(10, 20, 30)) + let r = rotateZ(71.0f.toRadians) * rotateY(-23.0f.toRadians) * rotateX(37.0f.toRadians) + let s = scale(vec3(2, 3, 4)) + let transform = t * r * s + check transform[3, 0] ~= 10.0f + check transform[3, 1] ~= 20.0f + check transform[3, 2] ~= 30.0f + + test "3D and 2D composition consistency": + let mat2d = translate(vec2(10, 20)) * rotate(45.toRadians) * scale(vec2(2)) + let mat3d = translate(vec3(10, 20, 0)) * rotateZ(45.toRadians) * scale(vec3(2)) + # The 2D and 3D transforms should produce matching XY results + let v2 = mat2d * vec2(1, 0) + let v3 = mat3d * vec3(1, 0, 0) + check v2.x ~= v3.x + check v2.y ~= v3.y + +suite "quaternion constructors": + test "identity quaternion": + let q = quat(0, 0, 0, 1) + check q.x == 0.0f + check q.y == 0.0f + check q.z == 0.0f + check q.w == 1.0f + + test "axis-angle constructors": + let qx = quatRotateX(37.0f.toRadians) + let qy = quatRotateY(-23.0f.toRadians) + let qz = quatRotateZ(71.0f.toRadians) + # Should be unit quaternions + check abs(qx.length - 1.0f) < 1e-5f + check abs(qy.length - 1.0f) < 1e-5f + check abs(qz.length - 1.0f) < 1e-5f + + test "fromAxisAngle": + let axis = normalize(vec3(1.0, 2.0, -3.0)) + let angle = 48.0f.toRadians + let q = fromAxisAngle(axis, angle) + check abs(q.length - 1.0f) < 1e-5f + check q.w ~= cos(angle * 0.5f) + + test "quaternion matrix constructors match rotation matrices": + check quatRotateX(PI / 2).mat4() ~= rotateX(PI / 2) + check quatRotateY(PI / 2).mat4() ~= rotateY(PI / 2) + check quatRotateZ(PI / 2).mat4() ~= rotateZ(PI / 2) + +suite "quaternion arithmetic": + test "quat + - * / operators": let - _ = mat2( - vec2(1, 0), - vec2(0, 1) - ) - _ = mat3( - vec3(1, 0, 0), - vec3(0, 1, 0), - vec3(0, 0, 1) - ) - _ = mat4( - vec4(1, 0, 0, 0), - vec4(0, 1, 0, 0), - vec4(0, 0, 1, 0), - vec4(0, 0, 0, 1) + a = dquat(1, 2, 3, 4) + b = dquat(-0.5, 0.25, 2.0, -3.0) + check a + b ~= dquat(0.5, 2.25, 5.0, 1.0) + check a - b ~= dquat(1.5, 1.75, 1.0, 7.0) + check a * 2.0 ~= dquat(2, 4, 6, 8) + check b / 2.0 ~= dquat(-0.25, 0.125, 1.0, -1.5) + +suite "quaternion multiply": + test "quaternion composition matches matrix composition": + let + qx = quatRotateX(0.37) + qy = quatRotateY(-0.91) + qz = quatRotateZ(1.24) + mxyz = rotateX(0.37) * rotateY(-0.91) * rotateZ(1.24) + check quatMultiply(quatMultiply(qx, qy), qz).mat4() ~= mxyz + + test "quaternion multiply identity": + let q = fromAxisAngle(normalize(vec3(1, 2, -3)), 48.0f.toRadians) + let identity = quat(0, 0, 0, 1) + check quatMultiply(q, identity) ~= q + check quatMultiply(identity, q) ~= q + +suite "quaternion vector rotation": + test "rotate basis vectors": + let x = dvec3(1, 0, 0) + let y = dvec3(0, 1, 0) + check quatRotateY(PI / 2) * x ~= dvec3(0, 0, -1) + check quatRotateX(PI / 2) * y ~= dvec3(0, 0, 1) + check quatRotateZ(PI / 2) * x ~= dvec3(0, 1, 0) + + test "quatRotate matches matrix multiply": + for _ in 0 ..< 1000: + let + axis = dvec3(rand(2.0)-1.0, rand(2.0)-1.0, rand(2.0)-1.0) + angle = rand(-PI .. PI) + q = fromAxisAngle(axis.normalize, angle) + v = dvec3(rand(2.0)-1.0, rand(2.0)-1.0, rand(2.0)-1.0) + check quatRotate(q, v) ~= q.mat4() * v + check q * v ~= q.mat4() * v + +suite "quaternion matrix roundtrip": + test "identity roundtrip": + let m = mat4() + check m.quat().mat4() ~= m + + test "single axis rotations": + for angle in [PI/6, PI/4, PI/3, PI/2, PI, -PI/4]: + let mx = rotateX(angle) + check mx.quat().mat4() ~= mx + let my = rotateY(angle) + check my.quat().mat4() ~= my + let mz = rotateZ(angle) + check mz.quat().mat4() ~= mz + + test "arbitrary rotations fuzz": + for _ in 0 ..< 2000: + let m = rotate( + PI*rand(2.0), + dvec3(rand(2.0)-0.5, rand(2.0)-0.5, rand(2.0)-0.5).normalize() ) + check m.quat().mat4() ~= m - block: + test "hard decomposition near 180 degrees": let - _ = dmat2() - _ = dmat3() - _ = dmat4() - - block: + axis = normalize(vec3(1.0, -2.0, 3.0)) + angle = 170.0f.toRadians + q = fromAxisAngle(axis, angle) + m = q.mat4() + check m.quat().mat4() ~= m + +suite "quaternion inverse": + test "q * q^-1 = identity": + let q = fromAxisAngle(normalize(dvec3(1, 2, -3)), 48.0.toRadians) + let product = quatMultiply(q, quatInverse(q)) + check abs(product.x) < 1e-5 + check abs(product.y) < 1e-5 + check abs(product.z) < 1e-5 + check abs(product.w - 1.0) < 1e-5 + + test "inverse undoes rotation": let - _ = dmat2( - 1, 0, - 0, 1 - ) - _ = dmat3( - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - ) - _ = dmat4( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ) - - block: + q = fromAxisAngle(normalize(dvec3(1, 2, -3)), 48.0.toRadians) + v = dvec3(1.25, -2.5, 3.75) + rotated = quatRotate(q, v) + unrotated = quatRotate(quatInverse(q), rotated) + check unrotated ~= v + + test "unit quaternion inverse = conjugate": + let q = fromAxisAngle(normalize(dvec3(1, 2, -3)), 48.0.toRadians) + let inv = quatInverse(q) + let conj = dquat(-q.x, -q.y, -q.z, q.w) + check inv ~= conj + + test "inverse fuzz": + for _ in 0 ..< 1000: + let + axis = dvec3(rand(2.0)-1.0, rand(2.0)-1.0, rand(2.0)-1.0).normalize() + q = fromAxisAngle(axis, rand(-PI .. PI)) + product = quatMultiply(q, quatInverse(q)) + check abs(product.w) ~= 1.0 + +suite "quaternion to axis-angle": + test "known axis-angle roundtrip": let - _ = dmat2( - dvec2(1, 0), - dvec2(0, 1) - ) - _ = dmat3( - dvec3(1, 0, 0), - dvec3(0, 1, 0), - dvec3(0, 0, 1) - ) - _ = dmat4( - dvec4(1, 0, 0, 0), - dvec4(0, 1, 0, 0), - dvec4(0, 0, 1, 0), - dvec4(0, 0, 0, 1) - ) - - block: - var - d2 = dmat2() - d3 = dmat3() - d4 = dmat4() - - d2[0, 0] = 123.123 - d2[1, 1] = 123.123 - - d3[0, 0] = 123.123 - d3[1, 1] = 123.123 - d3[2, 2] = 123.123 - - d4[0, 0] = 123.123 - d4[1, 1] = 123.123 - d4[2, 2] = 123.123 - d4[3, 3] = 123.123 - -block: - # Test basic mat functions. - doAssert dmat3().transpose() ~= dmat3( - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0 - ) - doAssert dmat4().transpose() ~= dmat4( - 1.0, 0.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, 0.0, 1.0 - ) - - doAssert scale(dvec2(1, 2)) ~= dmat3( - 1.0, 0.0, 0.0, - 0.0, 2.0, 0.0, - 0.0, 0.0, 1.0 - ) - doAssert scale(dvec3(2, 2, 3)) ~= dmat4( - 2.0, 0.0, 0.0, 0.0, - 0.0, 2.0, 0.0, 0.0, - 0.0, 0.0, 3.0, 0.0, - 0.0, 0.0, 0.0, 1.0 - ) - - doAssert translate(dvec2(1, 2)) ~= dmat3( - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 1.0, 2.0, 1.0 - ) - doAssert translate(dvec3(1, 2, 3)) ~= dmat4( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 1.0, 2.0, 3.0, 1.0 - ) - - doAssert rotate(1.0) ~= dmat3( - 0.5403023058681398, 0.8414709848078965, 0.0, - -0.8414709848078965, 0.5403023058681398, 0.0, - 0.0, 0.0, 1.0 - ) - - doAssert scale(dvec2(2)) ~= dmat3( - 2.0, 0.0, 0.0, - 0.0, 2.0, 0.0, - 0.0, 0.0, 1.0 - ) - doAssert scale(dvec3(2)) ~= dmat4( - 2.0, 0.0, 0.0, 0.0, - 0.0, 2.0, 0.0, 0.0, - 0.0, 0.0, 2.0, 0.0, - 0.0, 0.0, 0.0, 1.0 - ) - - doAssert translate(dvec2(2)) ~= dmat3( - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 2.0, 2.0, 1.0 - ) - doAssert translate(dvec3(2)) ~= dmat4( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 2.0, 2.0, 2.0, 1.0 - ) - - doAssert rotate(1.0).inverse() ~= dmat3( - 0.5403023058681398, -0.8414709848078965, -0.0, - 0.8414709848078965, 0.5403023058681398, -0.0, - 0.0, -0.0, 1.0 - ) - doAssert rotate(1.0, dvec3(1, 0, 0)).inverse() ~= dmat4( - 1.0, 0.0, 0.0, 0.0, - 0.0, 0.5403023058681398, -0.8414709848078965, 0.0, - 0.0, 0.8414709848078965, 0.5403023058681398, 0.0, - 0.0, 0.0, -0.0, 1.0 - ) - - - block: - doAssert translate(vec2(1, 2)).pos == vec2(1, 2) - - var translation = translate(vec2(1, 2)) - translation.pos = vec2(3, 4) - doAssert translation.pos == vec2(3, 4) - - block: - doAssert translate(vec3(1, 2, 3)).pos == vec3(1, 2, 3) - - var translation = translate(vec3(1, 2, 3)) - translation.pos = vec3(3, 4, 5) - doAssert translation.pos == vec3(3, 4, 5) - -block: - # Test basic vector mat4 and quat. - var m1 = mat4( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m2 ~= mat4( - 1.00000, 0.00000, 0.00000, 0.00000, - 0.00000, 1.00000, 0.00000, 0.00000, - 0.00000, 0.00000, 1.00000, 0.00000, - 0.00000, 0.00000, 0.00000, 1.00000 - ) - doAssert m1 ~= (m2) - -block: - # Test basic vector mat4 -1. - var m1 = mat4( - 1, 0, 0, 0, - 0, 0, -1, 0, - 0, 1, 0, 0, - 0, 0, 0, 1) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m1 ~= m2 - -block: - # Test Y 90. - var m1 = rotate(PI/2, dvec3(0, 1, 0)) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m1 ~= m2 - -block: - # Test -Y 90. - var m1 = rotate(PI/2, dvec3(0, -1, 0)) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m1 ~= m2 - -block: - # Test X 90. - var m1 = rotate(PI/2, dvec3(1, 0, 0)) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m1 ~= m2 - -block: - # Test Y 90. - var m1 = rotate(PI/2, dvec3(1, 0, 0)) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m1 ~= m2 - -block: - # Test 1,1,1 1.11rad. - var m1 = rotate(PI*1.11, dvec3(1, 1, 1).normalize()) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m1 ~= m2 - -block: - # Test 1,1,1 1.11rad. - var m1 = rotate(PI*1.11, dvec3(-1, 1, 1).normalize()) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m1 ~= m2 - -block: - # Test 1,1,1 1.11rad. - var m1 = rotate(PI*1.11, dvec3(-1, 0.34, 1.123).normalize()) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m1 ~= m2 - -block: - # Test super random quat test. - for i in 0 ..< 1000: - var m1 = rotate( - PI*rand(2.0), - dvec3(rand(2.0)-0.5, rand(2.0)-0.5, rand(2.0)-0.5).normalize() - ) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m1 ~= m2 - -block: - # Test *=1 /=1 don't change anything. - var v2 = vec2(0, 0) - v2 *= 1 - v2 /= 1 - doAssert v2 == vec2(0, 0) - - var v3 = vec3(0, 0, 0) - v3 *= 1 - v3 /= 1 - doAssert v3 == vec3(0, 0, 0) - - var v4 = vec4(0, 0, 0, 0) - v4 *= 1 - v4 /= 1 - doAssert v4 == vec4(0, 0, 0, 0) - - var q = quat(0, 0, 0, 0) - q *= 1 - q /= 1 - doAssert q == quat(0, 0, 0, 0) - -block: - # Test matrix and vector multiplication. - var a3 = mat3( - 0.9659258723258972, -0.258819043636322, 0.0, - 0.258819043636322, 0.9659258723258972, 0.0, - -25.00000953674316, 70.09619140625, 1.0 - ) - var b3 = mat3( - 0.9659258127212524, 0.258819043636322, 0.0, - -0.258819043636322, 0.9659258127212524, 0.0, - 77.64571380615234, 0.0, 1.0 - ) - - when not defined(js): - # TODO: Figure out why we loose soo much precision in js. - - doAssert a3 * b3 ~= mat3( - 1.0000, 0.0000, 0.0000, - 0.0000, 1.0000, 0.0000, - 50.0000, 50.0000, 1.0000 - ) - - doAssert a3 * vec2(77.64571380615234, 0) ~= vec2(50.0, 50.0) - - doAssert mat3(1, 2, 3, 4, 5, 6, 7, 8, 9) * - mat3(10, 20, 30, 40, 50, 60, 70, 80, 90) ~= mat3( - 300.0000, 360.0000, 420.0000, - 660.0000, 810.0000, 960.0000, - 1020.0000, 1260.0000, 1500.0000 - ) + axis = normalize(dvec3(1, 2, -3)) + angle = 48.0.toRadians + q = fromAxisAngle(axis, angle) + (extractedAxis, extractedAngle) = toAxisAngle(q) + check extractedAxis ~= axis + check abs(extractedAngle - angle) < 1e-5 + + test "identity gives zero angle": + let (_, angle) = toAxisAngle(dquat(0, 0, 0, 1)) + check abs(angle) < 1e-5 + + test "90 degree axes": + for axisVec in [dvec3(1, 0, 0), dvec3(0, 1, 0), dvec3(0, 0, 1)]: + let + q = fromAxisAngle(axisVec, PI / 2) + (a, ang) = toAxisAngle(q) + check a ~= axisVec + check abs(ang - PI / 2) < 1e-5 + + test "axis-angle fuzz": + for _ in 0 ..< 1000: + let + axis = dvec3(rand(2.0)-1.0, rand(2.0)-1.0, rand(2.0)-1.0).normalize() + angle = rand(0.001 .. PI) + q = fromAxisAngle(axis, angle) + (a, ang) = toAxisAngle(q) + check a ~= axis + check abs(ang - angle) < 1e-4 + +suite "slerp": + test "endpoints": + let + qx = quatRotateX(0.37) + qz = quatRotateZ(1.24) + check slerp(qx, qz, 0.0) ~= qx -block: - # 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, - 0.0, 0.0, -0.001000000047497451, - 0.0, -0.0, 0.0, -0.0, 1.0 - ) - - doAssert perspective[float32](75, 1.666, 1, 1000) ~= mat4( - 0.7822480201721191, 0.0, 0.0, 0.0, - 0.0, 1.30322527885437, 0.0, 0.0, - 0.0, 0.0, -1.002002000808716, -1.0, - 0.0, 0.0, -2.002002000808716, 0.0 - ) - - # Test super random quat test. - for i in 0 ..< 1000: - var m1 = rotate( - PI*rand(2.0), - dvec3(rand(2.0)-0.5, rand(2.0)-0.5, rand(2.0)-0.5).normalize() - ) - var q1 = m1.quat() - var m2 = q1.mat4() - doAssert m1 ~= m2 + test "midpoint equidistant": + let + qx = quatRotateX(0.37) + qz = quatRotateZ(1.24) + mid = slerp(qx, qz, 0.5) + a0 = arccos(clamp(dot(qx, mid), -1.0, 1.0)) + a1 = arccos(clamp(dot(mid, qz), -1.0, 1.0)) + check abs(a0 - a1) < 1e-5 + + test "slerp unit length": + let + qx = quatRotateX(0.37) + qz = quatRotateZ(1.24) + for i in 0 .. 10: + let q = slerp(qx, qz, i.float64 / 10.0) + check abs(q.length - 1.0) < 1e-5 -block: - # test fromTwoVectors - let - a = vec3(1, 0, 0) - b = vec3(0, 1, 0) - q1 = fromTwoVectors(a, b) - doAssert q1.mat4 * a ~= b + test "slerp identical inputs": + let qx = quatRotateX(0.37) + check slerp(qx, qx, 0.5) ~= qx - for i in 0 ..< 1000: + test "slerp with opposite quaternion": + let + qx = quatRotateX(0.37) + qz = quatRotateZ(1.24) + qNeg = -qz + result = slerp(qx, qNeg, 0.5) + check abs(result.length - 1.0) < 1e-5 + + test "slerp fuzz unit length": + for _ in 0 ..< 1000: + let + a = fromAxisAngle(dvec3(rand(2.0)-1, rand(2.0)-1, rand(2.0)-1).normalize(), rand(-PI .. PI)) + b = fromAxisAngle(dvec3(rand(2.0)-1, rand(2.0)-1, rand(2.0)-1).normalize(), rand(-PI .. PI)) + t = rand(0.0 .. 1.0) + q = slerp(a, b, t) + check abs(q.length - 1.0) < 1e-5 + +suite "fromTwoVectors": + test "basic rotation": let - a = vec3(rand(2.0)-0.5, rand(2.0)-0.5, rand(2.0)-0.5).normalize() - b = vec3(rand(2.0)-0.5, rand(2.0)-0.5, rand(2.0)-0.5).normalize() + a = vec3(1, 0, 0) + b = vec3(0, 1, 0) q = fromTwoVectors(a, b) - doAssert dist(q.mat4 * a, b) < 1E5 - -block: - let mat2d = translate(vec2(10, 20)) * rotate(45.toRadians) * scale(vec2(2)) - - let mat3d = translate(vec3(10, 20, 0)) * rotateZ(45.toRadians) * scale(vec3(2)) + check q.mat4() * a ~= b + + test "fromTwoVectors fuzz": + for _ in 0 ..< 1000: + let + a = vec3(rand(2.0)-0.5, rand(2.0)-0.5, rand(2.0)-0.5).normalize() + b = vec3(rand(2.0)-0.5, rand(2.0)-0.5, rand(2.0)-0.5).normalize() + q = fromTwoVectors(a, b) + check dist(q.mat4() * a, b) < 1e-5 + +suite "cross product": + test "basis vectors": + check cross(vec3(1, 0, 0), vec3(0, 1, 0)) ~= vec3(0, 0, 1) + check cross(vec3(0, 1, 0), vec3(1, 0, 0)) ~= vec3(0, 0, -1) + + test "cross product anticommutative": + let + a = normalize(vec3(1.0, 2.0, 3.0)) + b = normalize(vec3(-1.0, 0.5, 2.0)) + check cross(a, b) ~= -cross(b, a) + +suite "euler angles": + const PI = PI.float32 + + proc eq(a, b: Vec3): bool = + const epsilon = 0.001 + abs(angleBetween(a.x, b.x)) < epsilon and + abs(angleBetween(a.y, b.y)) < epsilon and + abs(angleBetween(a.z, b.z)) < epsilon + + test "from vector": + check vec3(0, 0, 1).toAngles.eq vec3(0f, 0f, 0f) # forward + check vec3(0, 0, -1).toAngles.eq vec3(0f, PI, 0f) # back + check vec3(-1, 0, 0).toAngles.eq vec3(0f, PI/2, 0f) # right + check vec3(1, 0, 0).toAngles.eq vec3(0f, -PI/2, 0f) # left + check vec3(0, 1, 0).toAngles.eq vec3(PI/2, 0f, 0f) # up + check vec3(0, -1, 0).toAngles.eq vec3(-PI/2, 0f, 0f) # down + + test "from matrix": + check mat4().toAngles.eq vec3(0, 0, 0) + check rotateX(10.toRadians()).toAngles.eq vec3(10.toRadians(), 0, 0) + check rotateY(10.toRadians()).toAngles.eq vec3(0, 10.toRadians(), 0) + check rotateZ(10.toRadians()).toAngles.eq vec3(0, 0, 10.toRadians()) + + test "euler roundtrip fuzz (non-polar)": + for _ 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) + a = fromAngles(b).toAngles() + check a.eq(b) + +suite "lookAt": + test "basic lookAt": + let m = lookAt(vec3(5, 5, 5), vec3(0, 0, 0), vec3(0, 1, 0)) + check m[3, 3] ~= 1.0f + # Should be orthogonal (inverse == transpose for the rotation part) + let det = determinant(m) + check abs(det - 1.0f) < 0.01f + + test "lookAt with default up": + let m = lookAt(vec3(0, 0, 5), vec3(0, 0, 0)) + check m * vec3(0, 0, 0) ~= vec3(0, 0, -5) + +suite "projection matrices": + test "ortho": + let o = ortho[float32](-1, 1, 1, -1, -1000, 1000) + check o ~= mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, -1.0, 0.0, 0.0, + 0.0, 0.0, -0.001000000047497451, 0.0, + -0.0, 0.0, -0.0, 1.0 + ) - doAssert mat2d ~= mat3( - 1.414213538169861, 1.414213538169861, 0.0, - -1.414213538169861, 1.414213538169861, 0.0, - 10.0, 20.0, 1.0 - ) + test "perspective": + let p = perspective[float32](75, 1.666, 1, 1000) + check p ~= mat4( + 0.7822480201721191, 0.0, 0.0, 0.0, + 0.0, 1.30322527885437, 0.0, 0.0, + 0.0, 0.0, -1.002002000808716, -1.0, + 0.0, 0.0, -2.002002000808716, 0.0 + ) - doAssert mat3d ~= mat4( - 1.414213418960571, 1.41421365737915, 0.0, 0.0, - -1.41421365737915, 1.414213418960571, 0.0, 0.0, - 0.0, 0.0, 2.0, 0.0, - 10.0, 20.0, 0.0, 1.0 - ) +suite "vector constructors": + test "all types compile": + check bvec2(true, false) == bvec2(true, false) + check ivec3(-1, 2, 3) == ivec3(-1, 2, 3) + check uvec4(1, 2, 3, 4) == uvec4(1, 2, 3, 4) + check vec2(1.0) == vec2(1.0, 1.0) + check dvec3(1.0) == dvec3(1.0, 1.0, 1.0) + + test "composite constructors": + check vec3(vec2(1, 2), 3) == vec3(1, 2, 3) + check vec4(vec3(1, 2, 3), 4) == vec4(1, 2, 3, 4) + check vec4(vec2(1, 2), vec2(3, 4)) == vec4(1, 2, 3, 4) + + test "type conversions": + check vec2(ivec2(1, 1)) == vec2(1, 1) + check vec2(uvec2(5, 5)) == vec2(5, 5) + check vec3(ivec3(1, 2, 3)) == vec3(1, 2, 3) + check vec4(uvec4(17, 18, 19, 20)) == vec4(17, 18, 19, 20) + +suite "vector arithmetic": + test "vec2 operations": + let a = vec2(1, 2) + let b = vec2(7, 6) + check a + b ~= vec2(8, 8) + check a - b ~= vec2(-6, -4) + check a * 2.0 ~= vec2(2, 4) + check a / 2.0 ~= vec2(0.5, 1.0) + + test "vec3 operations": + let a = vec3(1, 2, 3) + let b = vec3(7, 6, 5) + check a + b ~= vec3(8, 8, 8) + check a - b ~= vec3(-6, -4, -2) + + test "vec4 operations": + let a = vec4(1, 2, 3, 4) + let b = vec4(7, 6, 5, 4) + check a + b ~= vec4(8, 8, 8, 8) + check a - b ~= vec4(-6, -4, -2, 0) + + test "compound assignment": + var a = vec3(1, 2, 3) + a += vec3(7, 6, 5) + check a ~= vec3(8, 8, 8) + a -= vec3(7, 6, 5) + check a ~= vec3(1, 2, 3) + + test "min max": + check min(vec3(10, -10, 7), vec3(-10, 10, 0)) == vec3(-10, -10, 0) + check max(vec3(10, -10, 7), vec3(-10, 10, 0)) == vec3(10, 10, 7) + + test "equality and inequality": + check vec2(1, 1) == vec2(1, 1) + check vec2(1, 1) != vec2(1, 2) + check vec3(1, 1, 1) == vec3(1, 1, 1) + check vec3(1, 1, 1) != vec3(1, 1, 2) + check vec4(1, 1, 1, 1) == vec4(1, 1, 1, 1) + check vec4(1, 1, 1, 1) != vec4(1, 1, 1, 2) + +suite "vector swizzling": + test "vec2 swizzle read": + let a = vec2(1, 2) + check a.x == 1.0 + check a.y == 2.0 + check a.yx == vec2(2, 1) + check a.xxx == vec3(1, 1, 1) + + test "vec2 swizzle write": + var a = vec2(1, 2) + a.yx = vec2(-1, -2) + check a == vec2(-2, -1) + + test "vec4 swizzle self-assignment": + var b = vec4(1, 2, 3, 4) + b.wzyx = b + check b == vec4(4, 3, 2, 1) + +suite "string representation": + test "vector $": + check $vec2(1.0, 2.0) == "vec2(1.0, 2.0)" + check $vec3(1.0, 2.0, 3.0) == "vec3(1.0, 2.0, 3.0)" + check $vec4(1.0, 2.0, 3.0, 4.0) == "vec4(1.0, 2.0, 3.0, 4.0)" + check $ivec2(1, 2) == "ivec2(1, 2)" + check $bvec2(true, false) == "bvec2(true, false)" + + test "matrix $": + check $mat2(1, 3, 0, 1) == """mat2( + 1.0, 3.0, + 0.0, 1.0 +)""" -block: - let - a2 = vec2(10, -10) - b2 = vec2(-10, 10) - a3 = vec3(10, -10, 7) - b3 = vec3(-10, 10, 0) - a4 = vec4(10, -10, 7, -2) - b4 = vec4(-10, 10, 0, -1) - - doAssert min(a2, b2) == vec2(-10, -10) - doAssert min(a3, b3) == vec3(-10, -10, 0) - doAssert min(a4, b4) == vec4(-10, -10, 0, -2) - - doAssert max(a2, b2) == vec2(10, 10) - doAssert max(a3, b3) == vec3(10, 10, 7) - doAssert max(a4, b4) == vec4(10, 10, 7, -1) - - doAssert mix(10f, 7, 0.75) == 7.75 - doAssert mix(a2, b2, 0.75) == vec2(-5.0, 5.0) - doAssert mix(a3, b3, 0.75) == vec3(-5.0, 5.0, 1.75) - doAssert mix(a4, b4, 0.75) == vec4(-5.0, 5.0, 1.75, -1.25) - - doAssert `mod`(1, 2) == 1 - doAssert `mod`(vec2(12, 6), vec2(6, 12)) == vec2(0, 6) - doAssert `mod`(vec3(12, 6, 18), vec3(6, 12, 7)) == vec3(0, 6, 4) - doAssert `mod`(vec4(12, 6, 18, 16), vec4(6, 12, 7, 15)) == vec4(0, 6, 4, 1) - - doAssert `zmod`(1, 2) == 1 - doAssert `zmod`(vec2(12, 6), vec2(6, 12)) == vec2(0, 6) - doAssert `zmod`(vec3(12, 6, 18), vec3(6, 12, 7)) == vec3(0, 6, 4) - doAssert `zmod`(vec4(12, 6, 18, 16), vec4(6, 12, 7, 15)) == vec4(0, 6, 4, 1) - -block: - doAssert vec2(1, 1) == vec2(1, 1) - doAssert dvec2(2, 2) == dvec2(2, 2) - doAssert bvec2(true, true) == bvec2(true, true) - doAssert ivec2(3, 3) == ivec2(3, 3) - doAssert uvec2(3, 3) == uvec2(3, 3) - - doAssert vec3(1, 1, 1) == vec3(1, 1, 1) - doAssert dvec3(2, 2, 2) == dvec3(2, 2, 2) - doAssert bvec3(true, true, true) == bvec3(true, true, true) - doAssert ivec3(3, 3, 3) == ivec3(3, 3, 3) - doAssert uvec3(3, 3, 3) == uvec3(3, 3, 3) - - doAssert vec4(1, 1, 1, 1) == vec4(1, 1, 1, 1) - doAssert dvec4(2, 2, 2, 2) == dvec4(2, 2, 2, 2) - doAssert bvec4(true, true, true, false) == bvec4(true, true, true, false) - doAssert ivec4(3, 3, 3, 3) == ivec4(3, 3, 3, 3) - doAssert uvec4(3, 3, 3, 3) == uvec4(3, 3, 3, 3) - - doAssert vec2(1, 1) != vec2(1, 2) - doAssert dvec2(2, 2) != dvec2(2, 3) - doAssert bvec2(true, true) != bvec2(true, false) - doAssert ivec2(3, 3) != ivec2(3, 4) - doAssert uvec2(3, 3) != uvec2(3, 4) - - doAssert vec3(1, 1, 1) != vec3(1, 1, 2) - doAssert dvec3(2, 2, 2) != dvec3(2, 2, 3) - doAssert bvec3(true, true, true) != bvec3(true, true, false) - doAssert ivec3(3, 3, 3) != ivec3(3, 3, 4) - doAssert uvec3(3, 3, 3) != uvec3(3, 3, 4) - - doAssert vec4(1, 1, 1, 1) != vec4(1, 1, 1, 2) - doAssert dvec4(2, 2, 2, 2) != dvec4(2, 2, 2, 3) - doAssert bvec4(true, true, true, false) != bvec4(true, true, true, true) - doAssert ivec4(3, 3, 3, 3) != ivec4(3, 3, 3, 4) - doAssert uvec4(3, 3, 3, 3) != uvec4(3, 3, 3, 4) - -block: - doAssert vec2(ivec2(1, 1)) == vec2(1, 1) - doAssert vec2(uvec2(5, 5)) == vec2(5, 5) - doAssert ivec2(uvec2(23, 23)) == ivec2(23, 23) - doAssert uvec2(ivec2(12, 12)) == uvec2(12, 12) - doAssert vec3(ivec3(1, 2, 3)) == vec3(1, 2, 3) - doAssert vec3(uvec3(4, 5, 6)) == vec3(4, 5, 6) - doAssert ivec3(uvec3(7, 8, 9)) == ivec3(7, 8, 9) - doAssert uvec3(ivec3(10, 11, 12)) == uvec3(10, 11, 12) - doAssert vec4(ivec4(13, 14, 15, 16)) == vec4(13, 14, 15, 16) - doAssert vec4(uvec4(17, 18, 19, 20)) == vec4(17, 18, 19, 20) - doAssert ivec4(uvec4(21, 22, 23, 24)) == ivec4(21, 22, 23, 24) - doAssert uvec4(ivec4(25, 26, 27, 28)) == uvec4(25, 26, 27, 28) - -block: - # Test for https://github.com/treeform/vmath/issues/44 - doAssert PI.toDegrees() == 180 - doAssert (PI*2).toDegrees() == 360 - -block: - # Test for https://github.com/treeform/vmath/issues/45 - block: - let a = uvec2(10, 10) - var b: UVec2 - when compiles(b = a / 2): doAssert false # type mismatch - b = a div 2 - - block: - let a = vec2(10, 10) - var b: Vec2 - 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) +suite "double precision": + test "dmat constructors": + let m2 = dmat2(); let m3 = dmat3(); let m4 = dmat4() + check m2[0, 0] == 1.0 + check m3[1, 1] == 1.0 + check m4[2, 2] == 1.0 - doAssert m.forward() ~= m * vec3(0, 0, 1) - doAssert m.back() ~= m * vec3(0, 0, -1) + test "dmat element access": + var d4 = dmat4() + d4[0, 0] = 123.123 + check d4[0, 0] == 123.123 - doAssert m.right() ~= m * vec3(1, 0, 0) - doAssert m.left() ~= m * vec3(-1, 0, 0) + test "dmat transpose": + check dmat3().transpose() ~= dmat3() + check dmat4().transpose() ~= dmat4() - doAssert m.up() ~= m * vec3(0, 1, 0) - doAssert m.down() ~= m * vec3(0, -1, 0) + test "dmat scale translate rotate": + check scale(dvec2(1, 2)) ~= dmat3( + 1.0, 0.0, 0.0, + 0.0, 2.0, 0.0, + 0.0, 0.0, 1.0 + ) + check translate(dvec3(1, 2, 3)) ~= dmat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 1.0, 2.0, 3.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 - -block: - # Test for https://github.com/treeform/vmath/issues/73 - template gen2DTestsFor(constructor: untyped): void = - doAssert angle(constructor(1, 0), constructor(1, 0)) ~= 0 - doAssert angle(constructor(1, 1), constructor(-1, -1)) ~= Pi - doAssert angle(constructor(1, 0), constructor(0, 1)) ~= Pi/2 - doAssert angle(constructor(1, 0), constructor(-1, 0)) ~= Pi - doAssert angle(constructor(1, 1), constructor(1, -1)) ~= Pi/2 - - # Edge cases: - doAssert angle(constructor(0, 0), constructor(1, 0)).isNaN() - - gen2DTestsFor vec2 - gen2DTestsFor dvec2 - - template gen3DTestsFor(constructor: untyped): void = - doAssert angle(constructor(1, 0, 0), constructor(1, 0, 0)) ~= 0 - doAssert angle(constructor(1, 1, 1), constructor(-1, -1, -1)) ~= Pi - doAssert angle(constructor(1, 0, 0), constructor(0, 1, 0)) ~= Pi/2 - doAssert angle(constructor(1, 0, 0), constructor(-1, 0, 0)) ~= Pi - doAssert angle(constructor(1, 1, 1), constructor(1, -1, 1)) ~= arccos(1/3) - doAssert angle(constructor(1, 0, 0), constructor(0, 0, 1)) ~= Pi/2 - doAssert angle(constructor(1, 1, 1), constructor(-1, -1, 1)) ~= arccos(-1/3) - - # Edge cases: - doAssert angle(vec3(0, 0, 0), vec3(1, 0, 0)).isNaN() - - gen3DTestsFor vec3 - gen3DTestsFor dvec3 - -echo "test finished successfully" + test "mat4 and dmat4 conversion": + let m = mat4( + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + ) + let d = dmat4(m) + let m2 = mat4(d) + check m ~= m2 + +suite "angle conversions": + test "toDegrees and toRadians": + check PI.toDegrees() == 180 + check (PI*2).toDegrees() == 360 + check 180.0.toRadians() ~= PI + check 360.0.toRadians() ~= PI*2 + +suite "position accessors": + test "mat3 pos": + check translate(vec2(1, 2)).pos == vec2(1, 2) + var m = translate(vec2(1, 2)) + m.pos = vec2(3, 4) + check m.pos == vec2(3, 4) + + test "mat4 pos": + check translate(vec3(1, 2, 3)).pos == vec3(1, 2, 3) + var m = translate(vec3(1, 2, 3)) + m.pos = vec3(3, 4, 5) + check m.pos == vec3(3, 4, 5) + +suite "angle between vectors": + test "vec2 angle": + check angle(vec2(1, 0), vec2(1, 0)) ~= 0 + check angle(vec2(1, 0), vec2(0, 1)) ~= PI/2 + check angle(vec2(1, 0), vec2(-1, 0)) ~= PI + check vmath.isNan(angle(vec2(0, 0), vec2(1, 0))) + + test "vec3 angle": + check angle(vec3(1, 0, 0), vec3(1, 0, 0)) ~= 0 + check angle(vec3(1, 0, 0), vec3(0, 1, 0)) ~= PI/2 + check angle(vec3(1, 0, 0), vec3(-1, 0, 0)) ~= PI + check vmath.isNan(angle(vec3(0, 0, 0), vec3(1, 0, 0))) From 9e50c7709db435db7c2ec8e33baa7a418953b989 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 13:17:30 -0700 Subject: [PATCH 26/35] more tests --- tests/test.nim | 261 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 5 deletions(-) diff --git a/tests/test.nim b/tests/test.nim index bed526a..9fc7cef 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -501,6 +501,171 @@ suite "mat4 constructors": check v2.x ~= v3.x check v2.y ~= v3.y +suite "degenerate matrices": + test "all-zero mat2": + let z = mat2(0, 0, 0, 0) + check determinant(z) == 0.0f + # zero * anything = zero + check z * mat2() ~= z + check mat2() * z ~= z + check z * z ~= z + # zero * vec = zero + check z * vec2(1, 2) ~= vec2(0, 0) + # transpose of zero is zero + check transpose(z) ~= z + + test "all-zero mat3": + let z = mat3(0, 0, 0, 0, 0, 0, 0, 0, 0) + check determinant(z) == 0.0f + check z * mat3() ~= z + check mat3() * z ~= z + check z * z ~= z + check z * vec3(1, 2, 3) ~= vec3(0, 0, 0) + check transpose(z) ~= z + + test "all-zero mat4": + let z = mat4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + check determinant(z) == 0.0f + check z * mat4() ~= z + check mat4() * z ~= z + check z * z ~= z + check z * vec3(1, 2, 3) ~= vec3(0, 0, 0) + check transpose(z) ~= z + + test "singular mat2 (det=0)": + # Rows are linearly dependent + let s = mat2(1, 2, 2, 4) + check determinant(s) == 0.0f + # Inverse produces Inf/NaN (division by zero det) + let inv = inverse(s) + check vmath.isNan(inv[0, 0]) or abs(inv[0, 0]) == Inf + + test "singular mat3 (det=0)": + # Row 2 = Row 0 + Row 1 + let s = mat3(1, 0, 0, 0, 1, 0, 1, 1, 0) + check abs(determinant(s)) < 1e-6f + let inv = inverse(s) + check vmath.isNan(inv[0, 0]) or abs(inv[0, 0]) == Inf + + test "singular mat4 (det=0)": + # Duplicate rows → det = 0 + let s = mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 1, 0, 0, 0, + 0, 0, 0, 1 + ) + check abs(determinant(s)) < 1e-6f + let inv = inverse(s) + check vmath.isNan(inv[0, 0]) or abs(inv[0, 0]) == Inf + + test "zero diagonal mat2": + let m = mat2(0, 3, 7, 0) + check determinant(m) == -(7.0f * 3.0f) # -21 + # Still invertible (det != 0) + let inv = inverse(m) + check m * inv ~= mat2() + + test "zero diagonal mat3": + let m = mat3(0, 1, 0, 0, 0, 1, 1, 0, 0) + # Permutation matrix, det = 1 + check abs(determinant(m) - 1.0f) < 1e-5f + let inv = inverse(m) + check m * inv ~= mat3() + + test "zero diagonal mat4": + # Anti-diagonal matrix + let m = mat4( + 0, 0, 0, 1, + 0, 0, 1, 0, + 0, 1, 0, 0, + 1, 0, 0, 0 + ) + check abs(determinant(m)) > 0.0f + let inv = inverse(m) + check m * inv ~= mat4() + + test "near-singular mat3 (tiny det)": + let m = dmat3( + 1.0, 0.0, 0.0, + 0.0, 1e-15, 0.0, + 0.0, 0.0, 1.0 + ) + check abs(determinant(m)) < 1e-10 + check determinant(m) != 0.0 + # Inverse exists but is huge + let inv = inverse(m) + check inv[1, 1] > 1e14 + + test "scale by zero (rank-deficient mat4)": + let m = scale(vec3(1, 0, 1)) + check determinant(m) == 0.0f + # Multiply still works, just collapses one axis + check m * vec3(5, 5, 5) ~= vec3(5, 0, 5) + + test "all-ones matrices": + let m2 = mat2(1, 1, 1, 1) + check determinant(m2) == 0.0f + # m * m = 2 * m (idempotent up to scaling) + check m2 * m2 ~= mat2(2, 2, 2, 2) + + let m3 = mat3(1, 1, 1, 1, 1, 1, 1, 1, 1) + check determinant(m3) == 0.0f + check m3 * m3 ~= mat3(3, 3, 3, 3, 3, 3, 3, 3, 3) + + test "negative identity": + let m2 = mat2(-1, 0, 0, -1) + check determinant(m2) == 1.0f + check m2 * m2 ~= mat2() # (-I)^2 = I + check inverse(m2) ~= m2 # (-I)^-1 = -I + + let m3 = mat3(-1, 0, 0, 0, -1, 0, 0, 0, -1) + check determinant(m3) == -1.0f + check m3 * m3 ~= mat3() + check inverse(m3) ~= m3 + + let m4 = mat4( + -1, 0, 0, 0, + 0,-1, 0, 0, + 0, 0,-1, 0, + 0, 0, 0, 1 + ) + check abs(determinant(m4) - (-1.0f)) < 1e-5f + check inverse(m4) ~= m4 + + test "projection matrix (non-invertible rank 1)": + # Projects onto x-axis: P^2 = P + let p = mat3(1, 0, 0, 0, 0, 0, 0, 0, 0) + check determinant(p) == 0.0f + check p * p ~= p # idempotent + check p * vec3(5, 7, 9) ~= vec3(5, 0, 0) + + test "very large values": + let big = 1e18f + let m = mat2(big, 0, 0, big) + check determinant(m) == big * big + let inv = inverse(m) + check inv[0, 0] ~= (1.0f / big) + check m * inv ~= mat2() + + test "mixed large and small values": + let m = dmat3( + 1e10, 0.0, 0.0, + 0.0, 1e-10, 0.0, + 0.0, 0.0, 1.0 + ) + let inv = inverse(m) + check m * inv ~= dmat3() + + test "skew/shear matrix": + # Shear in x by y + let m = mat3(1, 0, 0, 3, 1, 0, 0, 0, 1) + check determinant(m) == 1.0f + let inv = inverse(m) + check m * inv ~= mat3() + # Shearing (1, 0) should give (1, 0), shearing (0, 1) should give (3, 1) + check m * vec3(0, 1, 0) ~= vec3(3, 1, 0) + suite "quaternion constructors": test "identity quaternion": let q = quat(0, 0, 0, 1) @@ -880,17 +1045,103 @@ suite "vector swizzling": check b == vec4(4, 3, 2, 1) suite "string representation": - test "vector $": + test "vec2 $": check $vec2(1.0, 2.0) == "vec2(1.0, 2.0)" + + test "vec3 $": check $vec3(1.0, 2.0, 3.0) == "vec3(1.0, 2.0, 3.0)" + + test "vec4 $": check $vec4(1.0, 2.0, 3.0, 4.0) == "vec4(1.0, 2.0, 3.0, 4.0)" + + test "dvec2 $": + check $dvec2(1.0, 2.0) == "dvec2(1.0, 2.0)" + + test "dvec3 $": + check $dvec3(1.0, 2.0, 3.0) == "dvec3(1.0, 2.0, 3.0)" + + test "dvec4 $": + check $dvec4(1.0, 2.0, 3.0, 4.0) == "dvec4(1.0, 2.0, 3.0, 4.0)" + + test "ivec2 $": check $ivec2(1, 2) == "ivec2(1, 2)" + + test "ivec3 $": + check $ivec3(1, 2, 3) == "ivec3(1, 2, 3)" + + test "ivec4 $": + check $ivec4(1, 2, 3, 4) == "ivec4(1, 2, 3, 4)" + + test "uvec2 $": + check $uvec2(1, 2) == "uvec2(1, 2)" + + test "uvec3 $": + check $uvec3(1, 2, 3) == "uvec3(1, 2, 3)" + + test "uvec4 $": + check $uvec4(1, 2, 3, 4) == "uvec4(1, 2, 3, 4)" + + test "bvec2 $": check $bvec2(true, false) == "bvec2(true, false)" - test "matrix $": - check $mat2(1, 3, 0, 1) == """mat2( - 1.0, 3.0, - 0.0, 1.0 + test "bvec3 $": + check $bvec3(true, false, true) == "bvec3(true, false, true)" + + test "bvec4 $": + check $bvec4(true, false, true, false) == "bvec4(true, false, true, false)" + + test "quat $ (prints as vec4)": + check $quat(1.0, 2.0, 3.0, 4.0) == "vec4(1.0, 2.0, 3.0, 4.0)" + check $dquat(1.0, 2.0, 3.0, 4.0) == "dvec4(1.0, 2.0, 3.0, 4.0)" + + test "mat2 $": + check $mat2(1, 2, 3, 4) == """mat2( + 1.0, 2.0, + 3.0, 4.0 +)""" + + test "mat3 $": + check $mat3(1, 2, 3, 4, 5, 6, 7, 8, 9) == """mat3( + 1.0, 2.0, 3.0, + 4.0, 5.0, 6.0, + 7.0, 8.0, 9.0 +)""" + + test "mat4 $": + check $mat4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) == """mat4( + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 +)""" + + test "dmat2 $": + check $dmat2(1, 2, 3, 4) == """dmat2( + 1.0, 2.0, + 3.0, 4.0 +)""" + + test "dmat3 $": + check $dmat3(1, 2, 3, 4, 5, 6, 7, 8, 9) == """dmat3( + 1.0, 2.0, 3.0, + 4.0, 5.0, 6.0, + 7.0, 8.0, 9.0 +)""" + + test "dmat4 $": + check $dmat4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) == """dmat4( + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 +)""" + + test "mat4 identity $": + check $mat4() == """mat4( + 1.0, 0.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, 0.0, 1.0 )""" suite "double precision": From acb2a5234b6fbaabf740c0cb47f3c3b42b736b99 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 13:35:15 -0700 Subject: [PATCH 27/35] add more tests --- tests/test.nim | 178 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/test.nims | 1 + 2 files changed, 179 insertions(+) create mode 100644 tests/test.nims diff --git a/tests/test.nim b/tests/test.nim index 9fc7cef..f635131 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -501,6 +501,184 @@ suite "mat4 constructors": check v2.x ~= v3.x check v2.y ~= v3.y +suite "vector operations": + test "length": + check vec2(3, 4).length ~= 5.0f + check vec3(1, 2, 2).length ~= 3.0f + check vec4(1, 0, 0, 0).length ~= 1.0f + check dvec3(0, 0, 0).length ~= 0.0 + + test "lengthSq": + check vec2(3, 4).lengthSq ~= 25.0f + check vec3(1, 2, 2).lengthSq ~= 9.0f + check vec4(2, 0, 0, 0).lengthSq ~= 4.0f + + test "normalize": + check normalize(vec2(10, 0)) ~= vec2(1, 0) + check normalize(vec3(0, 0, 5)) ~= vec3(0, 0, 1) + check abs(normalize(vec4(1, 1, 1, 1)).length - 1.0f) < 1e-5f + + test "dist and distSq": + check dist(vec2(0, 0), vec2(3, 4)) ~= 5.0f + check distSq(vec2(0, 0), vec2(3, 4)) ~= 25.0f + check dist(vec3(0, 0, 0), vec3(1, 2, 2)) ~= 3.0f + check distSq(vec3(0, 0, 0), vec3(1, 2, 2)) ~= 9.0f + + test "dot product": + check dot(vec2(1, 0), vec2(0, 1)) ~= 0.0f + check dot(vec2(1, 0), vec2(1, 0)) ~= 1.0f + check dot(vec3(1, 2, 3), vec3(4, 5, 6)) ~= 32.0f + check dot(vec4(1, 2, 3, 4), vec4(5, 6, 7, 8)) ~= 70.0f + + test "dir (point to point)": + let d = dir(vec3(0, 0, 0), vec3(10, 0, 0)) + check abs(d.length - 1.0f) < 1e-5f + let d2 = dir(vec2(0, 0), vec2(0, 5)) + check abs(d2.length - 1.0f) < 1e-5f + + test "dir (angle to vec2)": + check dir(0.0f) ~= vec2(1, 0) + check dir(float32(PI / 2)) ~= vec2(0, 1) + check dir(float32(PI)) ~= vec2(-1, 0) + + test "angle of vec2": + check angle(vec2(1, 0)) ~= 0.0f + check angle(vec2(0, 1)) ~= float32(PI / 2) + check angle(vec2(-1, 0)) ~= float32(PI) + + test "mix (vector lerp)": + check mix(vec2(0, 0), vec2(10, 20), 0.5f) ~= vec2(5, 10) + check mix(vec3(0, 0, 0), vec3(10, 20, 30), 0.25f) ~= vec3(2.5, 5.0, 7.5) + check mix(vec4(0, 0, 0, 0), vec4(4, 8, 12, 16), 0.5f) ~= vec4(2, 4, 6, 8) + + test "mix (per-component vector)": + check mix(vec2(0, 0), vec2(10, 20), vec2(0.5, 1.0)) ~= vec2(5, 20) + check mix(vec3(0, 0, 0), vec3(10, 20, 30), vec3(0.0, 0.5, 1.0)) ~= vec3(0, 10, 30) + + test "clamp (vector bounds)": + check clamp(vec2(5, -5), vec2(0, 0), vec2(3, 3)) == vec2(3, 0) + check clamp(vec3(5, -5, 1), vec3(0, 0, 0), vec3(3, 3, 3)) == vec3(3, 0, 1) + + test "clamp (scalar bounds)": + check clamp(vec2(5, -5), 0.0f, 3.0f) == vec2(3, 0) + check clamp(vec3(5, -5, 1), 0.0f, 3.0f) == vec3(3, 0, 1) + + test "inversesqrt": + check inversesqrt(4.0f) ~= 0.5f + check inversesqrt(1.0f) ~= 1.0f + check inversesqrt(16.0) ~= 0.25 + + test "zmod": + # GLSL-style mod: a - b * floor(a/b) + check zmod(5.5f, 3.0f) ~= 2.5f + check zmod(-1.0f, 3.0f) ~= 2.0f # differs from Nim mod for negatives + check zmod(7.0f, 3.0f) ~= 1.0f + + test "turnAngle": + # Should step toward target angle, clamped by speed + let a = 0.0f + let b = 1.0f + check turnAngle(a, b, 0.5f) ~= 0.5f # step partway + check turnAngle(a, b, 2.0f) ~= b # speed exceeds gap, snap to target + check turnAngle(a, b, 0.01f) ~= 0.01f # small step + +suite "mat4 direction accessors": + test "identity directions": + let m = mat4() + check m.forward ~= vec3(0, 0, 1) + check m.back ~= vec3(0, 0, -1) + check m.left ~= vec3(-1, 0, 0) + check m.right ~= vec3(1, 0, 0) + check m.up ~= vec3(0, 1, 0) + check m.down ~= vec3(0, -1, 0) + + test "rotated directions": + let m = rotateY(float32(PI / 2)) + # After 90° CCW around Y: forward (+Z) rotates toward +X + check m.forward ~= vec3(1, 0, 0) + check m.right ~= vec3(0, 0, -1) + check m.up ~= vec3(0, 1, 0) + + test "rotationOnly strips translation": + let m = translate(vec3(10, 20, 30)) * rotateX(float32(PI / 4)) + # rotationOnly zeroes the translation column + var r = m + r.pos = vec3(0, 0, 0) + check r.pos ~= vec3(0, 0, 0) + check r.forward ~= m.forward + check r.up ~= m.up + +suite "frustum and projection": + test "frustum matrix": + let f = frustum[float32](-1, 1, -1, 1, 1, 100) + # Near plane, center should map to origin + let v = f * vec4(0, 0, -1, 1) + check v.x ~= 0.0f + check v.y ~= 0.0f + # Should match perspective with 90° fov, aspect 1 + let p = perspective[float32](90, 1, 1, 100) + check f ~= p + +suite "euler angles (extended)": + test "toAngles from origin to target": + let angles = toAngles(vec3(0, 0, 0), vec3(0, 0, 1)) + check angles ~= vec3(0, 0, 0) # looking forward + let angles2 = toAngles(vec3(0, 0, 0), vec3(1, 0, 0)) + check angles2 ~= toAngles(vec3(1, 0, 0)) + + test "toAngles from quaternion": + # Identity quaternion = no rotation + check toAngles(quat(0, 0, 0, 1)) ~= vec3(0, 0, 0) + # Quaternion euler roundtrip + let q = quatRotateX(0.3f) * quatRotateY(0.5f) * quatRotateZ(0.1f) + let anglesFromQuat = toAngles(q) + let anglesFromMat = toAngles(q.mat4()) + check anglesFromQuat ~= anglesFromMat + + test "rotate around arbitrary axis": + let axis = normalize(vec3(1.0, 1.0, 0.0)) + let m = rotate(float32(PI / 2), axis) + # Should be a valid rotation matrix (det = 1, orthogonal) + check abs(determinant(m) - 1.0f) < 0.01f + let inv = inverse(m) + check m * inv ~= mat4() + +suite "quaternion nlerp": + test "nlerp endpoints": + let + qx = quatRotateX(0.37f) + qz = quatRotateZ(1.24f) + check nlerp(qx, qz, 0.0f) ~= qx + + test "nlerp produces unit quaternions": + let + qx = quatRotateX(0.37f) + qz = quatRotateZ(1.24f) + for i in 0 .. 10: + let q = nlerp(qx, qz, i.float32 / 10.0f) + check abs(q.length - 1.0f) < 1e-5f + + test "nlerp vs slerp similar for close quats": + let + qx = quatRotateX(0.1f) + qy = quatRotateX(0.2f) + nl = nlerp(qx, qy, 0.5f) + sl = slerp(qx, qy, 0.5f) + # For close quaternions, nlerp and slerp should be very similar + 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) + 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)]: + let o = orthogonal(v) + check abs(dot(v, o)) < 1e-5f + + test "orthogonal is nonzero for nonzero input": + for v in [vec3(1, 0, 0), vec3(0, 1, 0), vec3(0, 0, 1), vec3(5, 3, 2)]: + check orthogonal(v).length > 0.0f + suite "degenerate matrices": test "all-zero mat2": let z = mat2(0, 0, 0, 0) diff --git a/tests/test.nims b/tests/test.nims new file mode 100644 index 0000000..a119208 --- /dev/null +++ b/tests/test.nims @@ -0,0 +1 @@ +--path:"../src" From 8f180992c2823884c9a9c1d008798c100f279cb3 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 13:36:33 -0700 Subject: [PATCH 28/35] fix rotationOnly --- src/vmath.nim | 2 +- tests/test.nim | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vmath.nim b/src/vmath.nim index d6c5006..e0036e7 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -1363,7 +1363,7 @@ 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) + result.pos = gvec3[T](0, 0, 0) proc rotateX*[T](angle: T): GMat4[T] = ## Return a rotation matrix around X with angle. diff --git a/tests/test.nim b/tests/test.nim index f635131..dde7a62 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -601,9 +601,7 @@ suite "mat4 direction accessors": test "rotationOnly strips translation": let m = translate(vec3(10, 20, 30)) * rotateX(float32(PI / 4)) - # rotationOnly zeroes the translation column - var r = m - r.pos = vec3(0, 0, 0) + let r = rotationOnly(m) check r.pos ~= vec3(0, 0, 0) check r.forward ~= m.forward check r.up ~= m.up From ae96d9514d18ed301905705d8003d8ca28532436 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 13:38:11 -0700 Subject: [PATCH 29/35] not all nim supports all --- tests/test.nim | 2 +- tests/test.nims | 1 - tests/test_quaternion.nim | 195 -------------------------------------- 3 files changed, 1 insertion(+), 197 deletions(-) delete mode 100644 tests/test.nims delete mode 100644 tests/test_quaternion.nim diff --git a/tests/test.nim b/tests/test.nim index dde7a62..36adbce 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -1,6 +1,6 @@ import std/[math, random, unittest], - vmath {.all.} + vmath randomize(1234) diff --git a/tests/test.nims b/tests/test.nims deleted file mode 100644 index a119208..0000000 --- a/tests/test.nims +++ /dev/null @@ -1 +0,0 @@ ---path:"../src" diff --git a/tests/test_quaternion.nim b/tests/test_quaternion.nim deleted file mode 100644 index da799a5..0000000 --- a/tests/test_quaternion.nim +++ /dev/null @@ -1,195 +0,0 @@ -import - std/[math, random], - vmath - -randomize(4321) - -proc quatEquivalent[T](a, b: GVec4[T]): bool = - (a ~= b) or (a ~= (b * -1.0)) - -proc randomAxis(): DVec3 = - while true: - let axis = dvec3(rand(2.0) - 1.0, rand(2.0) - 1.0, rand(2.0) - 1.0) - if axis.length > 0.0001: - return axis.normalize() - -block: - # Axis-angle and quaternion constructors should agree on the same matrix. - doAssert quatRotateX(PI / 2).mat4() ~= rotateX(PI / 2) - doAssert quatRotateY(PI / 2).mat4() ~= rotateY(PI / 2) - doAssert quatRotateZ(PI / 2).mat4() ~= rotateZ(PI / 2) - - for _ in 0 ..< 2000: - let - axis = randomAxis() - angle = rand(-PI .. PI) - q = fromAxisAngle(axis, angle) - m = rotate(angle, axis) - doAssert q.mat4() ~= m - doAssert quatEquivalent(m.quat(), q) - doAssert m.quat().mat4() ~= m - -block: - # Known rotations should produce the expected quaternion values. - doAssert quatEquivalent(rotateX(PI / 2).quat(), quatRotateX(PI / 2)) - doAssert quatEquivalent(rotateY(PI / 2).quat(), quatRotateY(PI / 2)) - doAssert quatEquivalent(rotateZ(PI / 2).quat(), quatRotateZ(PI / 2)) - doAssert quatEquivalent(mat4().quat(), quat(0f, 0f, 0f, 1f)) - -block: - # Quaternion multiplication should compose rotations in matrix order. - let - qx = quatRotateX(0.37) - qy = quatRotateY(-0.91) - qz = quatRotateZ(1.24) - - mxy = rotateX(0.37) * rotateY(-0.91) - myz = rotateY(-0.91) * rotateZ(1.24) - mxyz = rotateX(0.37) * rotateY(-0.91) * rotateZ(1.24) - - doAssert quatMultiply(qx, qy).mat4() ~= mxy - doAssert quatMultiply(qy, qz).mat4() ~= myz - doAssert quatMultiply(quatMultiply(qx, qy), qz).mat4() ~= mxyz - -block: - # Rotating vectors with quaternion-derived matrices should match helpers. - let - x = dvec3(1, 0, 0) - y = dvec3(0, 1, 0) - z = dvec3(0, 0, 1) - - doAssert quatRotateY(PI / 2).mat4() * x ~= dvec3(0, 0, -1) - doAssert quatRotateX(PI / 2).mat4() * y ~= dvec3(0, 0, 1) - doAssert quatRotateZ(PI / 2).mat4() * x ~= dvec3(0, 1, 0) - doAssert quatRotate(quatRotateY(PI / 2), x) ~= dvec3(0, 0, -1) - doAssert quatRotate(quatRotateX(PI / 2), y) ~= dvec3(0, 0, 1) - doAssert quatRotate(quatRotateZ(PI / 2), x) ~= dvec3(0, 1, 0) - doAssert quatRotateY(PI / 2) * x ~= dvec3(0, 0, -1) - doAssert quatRotateX(PI / 2) * y ~= dvec3(0, 0, 1) - doAssert quatRotateZ(PI / 2) * x ~= dvec3(0, 1, 0) - doAssert fromTwoVectors(x, y).mat4() * x ~= y - doAssert fromTwoVectors(y, z).mat4() * y ~= z - - for _ in 0 ..< 2000: - let - a = randomAxis() - b = randomAxis() - q = fromTwoVectors(a, b) - v = randomAxis() - doAssert dist(q.mat4() * a, b) < 1e-5 - doAssert quatRotate(q, v) ~= q.mat4() * v - doAssert q * v ~= q.mat4() * v - -block: - # Basic arithmetic operators should still behave like vec4 math. - let - a = dquat(1, 2, 3, 4) - b = dquat(-0.5, 0.25, 2.0, -3.0) - doAssert a + b ~= dquat(0.5, 2.25, 5.0, 1.0) - doAssert a - b ~= dquat(1.5, 1.75, 1.0, 7.0) - doAssert a * 2.0 ~= dquat(2, 4, 6, 8) - doAssert b / 2.0 ~= dquat(-0.25, 0.125, 1.0, -1.5) - -block: - # toAxisAngle should roundtrip with fromAxisAngle. - let - axis = dvec3(1, 2, -3).normalize() - angle = 48.0.toRadians - q = fromAxisAngle(axis, angle) - (extractedAxis, extractedAngle) = toAxisAngle(q) - doAssert extractedAxis ~= axis - doAssert abs(extractedAngle - angle) < 1e-5 - - # Identity quaternion should give 0 angle. - let (_, identityAngle) = toAxisAngle(dquat(0, 0, 0, 1)) - doAssert abs(identityAngle) < 1e-5 - - # 90 degree rotations around each axis. - for axisVec in [dvec3(1, 0, 0), dvec3(0, 1, 0), dvec3(0, 0, 1)]: - let - q = fromAxisAngle(axisVec, PI / 2) - (a, ang) = toAxisAngle(q) - doAssert a ~= axisVec - doAssert abs(ang - PI / 2) < 1e-5 - - # Random roundtrip fuzz test. - for _ in 0 ..< 2000: - let - axis = randomAxis() - angle = rand(0.001 .. PI) # positive angles, avoid 0 and 2*PI degeneracy - q = fromAxisAngle(axis, angle) - (a, ang) = toAxisAngle(q) - doAssert a ~= axis - doAssert abs(ang - angle) < 1e-4 - -block: - # quatInverse: q * q^-1 should be identity. - let - axis = dvec3(1, 2, -3).normalize() - angle = 48.0.toRadians - q = fromAxisAngle(axis, angle) - qInv = quatInverse(q) - product = quatMultiply(q, qInv) - doAssert quatEquivalent(product, dquat(0, 0, 0, 1)) - - # Inverse should undo rotation. - let - v = dvec3(1.25, -2.5, 3.75) - rotated = quatRotate(q, v) - unrotated = quatRotate(qInv, rotated) - doAssert unrotated ~= v - - # For unit quaternions, inverse == conjugate. - let conjugate = dquat(-q.x, -q.y, -q.z, q.w) - doAssert qInv ~= conjugate - - # Random fuzz test. - for _ in 0 ..< 2000: - let - q = fromAxisAngle(randomAxis(), rand(-PI .. PI)) - qInv = quatInverse(q) - product = quatMultiply(q, qInv) - doAssert quatEquivalent(product, dquat(0, 0, 0, 1)) - -block: - # slerp at endpoints should return the inputs. - let - qx = quatRotateX(0.37) - qz = quatRotateZ(1.24) - doAssert slerp(qx, qz, 0.0) ~= qx - doAssert quatEquivalent(slerp(qx, qz, 1.0), qz) - - # slerp at t=0.5 should be halfway between. - let mid = slerp(qx, qz, 0.5) - # The midpoint should be equidistant in angle from both endpoints. - let angleTo0 = arccos(clamp(dot(qx, mid), -1.0, 1.0)) - let angleTo1 = arccos(clamp(dot(mid, qz), -1.0, 1.0)) - doAssert abs(angleTo0 - angleTo1) < 1e-5 - - # slerp should produce unit quaternions. - for i in 0 .. 10: - let t = i.float64 / 10.0 - let q = slerp(qx, qz, t) - doAssert abs(q.length - 1.0) < 1e-5 - - # slerp with identical quaternions should return the same quaternion. - doAssert slerp(qx, qx, 0.5) ~= qx - - # slerp should handle opposite quaternions (q and -q represent same rotation). - let qNeg = -qz - let result = slerp(qx, qNeg, 0.5) - doAssert abs(result.length - 1.0) < 1e-5 - - # Random fuzz: slerp result should always be unit length. - for _ in 0 ..< 2000: - let - a = fromAxisAngle(randomAxis(), rand(-PI .. PI)) - b = fromAxisAngle(randomAxis(), rand(-PI .. PI)) - t = rand(0.0 .. 1.0) - q = slerp(a, b, t) - doAssert abs(q.length - 1.0) < 1e-5 - # The slerp rotation should produce the same result as its matrix form. - let v = randomAxis() - doAssert quatRotate(q, v) ~= q.mat4() * v - -echo "test_quaternion finished successfully" From 0fe1a2eb72287c97b51deba654e0b5eda80f6f7f Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 13:39:46 -0700 Subject: [PATCH 30/35] update yaml --- .github/workflows/build.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a635158..36a014a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,11 +19,6 @@ jobs: - uses: treeform/setup-nim-action@v2 with: nim-version: ${{ matrix.nim-version }} - - run: nim r tests/test_quaternion.nim - - run: nim r --gc:arc tests/test_quaternion.nim - - run: nim r -d:vmathObjBased tests/test_quaternion.nim - - run: nim r -d:vmathArrayBased tests/test_quaternion.nim - - run: nim js -r tests/test_quaternion.nim - run: nim r tests/test.nim - run: nim r --gc:arc tests/test.nim - run: nim r -d:vmathObjBased tests/test.nim From 5bdfe345ccff1f3c9992853f0a9fe52542e47cc9 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 13:43:34 -0700 Subject: [PATCH 31/35] f --- tests/test.nim | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test.nim b/tests/test.nim index 36adbce..24849b2 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -423,16 +423,14 @@ suite "mat4 operations": check t[r, c] == a[c, r] check transpose(transpose(a)) ~= a - test "mat4 inverse of invertible matrix": - # Pure rotation + translation (well-conditioned) - let m = translate(vec3(10, 20, 30)) * rotateZ(71.toRadians) * - rotateY(-23.toRadians) * rotateX(37.toRadians) + test "mat4 inverse (float32)": + let m = translate(vec3(1, 2, 3)) * rotateZ(45.toRadians) let inv = inverse(m) check m * inv ~= mat4() check inv * m ~= mat4() - test "mat4 inverse with non-uniform scale (double)": - # Non-uniform scale needs float64 for ~= tolerance + test "mat4 inverse (float64, compound transform)": + # Multi-rotation + non-uniform scale needs float64 to stay within ~= tolerance let m = translate(dvec3(10, 20, 30)) * rotateZ(71.0.toRadians) * rotateY(-23.0.toRadians) * rotateX(37.0.toRadians) * scale(dvec3(2, 3, 4)) let inv = inverse(m) From 4b6afbeadb7ac1eb09cc020be8842afb0b28dbeb Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 13:44:31 -0700 Subject: [PATCH 32/35] more stable inverse test --- tests/test.nim | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/test.nim b/tests/test.nim index 24849b2..ecb4b6a 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -423,19 +423,17 @@ suite "mat4 operations": check t[r, c] == a[c, r] check transpose(transpose(a)) ~= a - test "mat4 inverse (float32)": - let m = translate(vec3(1, 2, 3)) * rotateZ(45.toRadians) + test "mat4 inverse (float32, game transform)": + # Typical game transform: position + rotation + non-uniform scale + let m = translate(vec3(100, -50, 200)) * rotateZ(71.toRadians) * + rotateY(-23.toRadians) * rotateX(37.toRadians) * scale(vec3(2, 3, 4)) let inv = inverse(m) - check m * inv ~= mat4() - check inv * m ~= mat4() - - test "mat4 inverse (float64, compound transform)": - # Multi-rotation + non-uniform scale needs float64 to stay within ~= tolerance - let m = translate(dvec3(10, 20, 30)) * rotateZ(71.0.toRadians) * - rotateY(-23.0.toRadians) * rotateX(37.0.toRadians) * scale(dvec3(2, 3, 4)) - let inv = inverse(m) - check m * inv ~= dmat4() - check inv * m ~= dmat4() + let product = m * inv + # float32 inverse of a full T*R*S matrix drifts ~1e-5, too loose for ~= (1e-6) + let id = mat4() + for r in 0 .. 3: + for c in 0 .. 3: + check abs(product[r, c] - id[r, c]) < 1e-4f test "mat4 * vec3": let m = translate(vec3(10, 20, 30)) From 7c3fc9f487764e6727e388db4a88dd1b84e741fc Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 13:50:50 -0700 Subject: [PATCH 33/35] fix js test --- .gitignore | 1 + tests/test.nim | 48 +++++++++++++++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 1b48119..8f1e202 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ # normal ignores: *.exe nimcache +tests/test.js diff --git a/tests/test.nim b/tests/test.nim index ecb4b6a..9382c52 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -180,8 +180,14 @@ suite "mat2 memory layout and element access": suite "mat2 operations": let - a = cast[Mat2]([1.0f, 2.0f, 3.0f, 4.0f]) - b = cast[Mat2]([5.0f, -6.0f, 7.0f, -8.0f]) + a = when defined(js): + mat2(1, 2, 3, 4) + else: + cast[Mat2]([1.0f, 2.0f, 3.0f, 4.0f]) + b = when defined(js): + mat2(5, -6, 7, -8) + else: + cast[Mat2]([5.0f, -6.0f, 7.0f, -8.0f]) test "mat2 multiply": let ab = a * b @@ -260,8 +266,14 @@ suite "mat3 memory layout and element access": suite "mat3 operations": let - a = cast[Mat3]([1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f]) - b = cast[Mat3]([-1.0f, 3.0f, 5.0f, 7.0f, -2.0f, 4.0f, 6.0f, 8.0f, -3.0f]) + a = when defined(js): + mat3(1, 2, 3, 4, 5, 6, 7, 8, 10) + else: + cast[Mat3]([1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f]) + b = when defined(js): + mat3(-1, 3, 5, 7, -2, 4, 6, 8, -3) + else: + cast[Mat3]([-1.0f, 3.0f, 5.0f, 7.0f, -2.0f, 4.0f, 6.0f, 8.0f, -3.0f]) test "mat3 multiply": check mat3() * a ~= a @@ -396,18 +408,20 @@ suite "mat4 memory layout and element access": suite "mat4 operations": let - a = cast[Mat4]([ - 1.0f, 2.0f, 3.0f, 4.0f, - 5.0f, 6.0f, 7.0f, 8.0f, - 9.0f, 10.0f, 11.0f, 12.0f, - 13.0f, 14.0f, 15.0f, 16.0f - ]) - b = cast[Mat4]([ - -10.0f, -20.0f, -30.0f, -40.0f, - 50.0f, 60.0f, 70.0f, 80.0f, - 90.0f, 100.0f, 110.0f, 120.0f, - 130.0f, 140.0f, 150.0f, 160.0f - ]) + a = when defined(js): + mat4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) + else: + cast[Mat4]([ + 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f + ]) + b = when defined(js): + mat4(-10, -20, -30, -40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160) + else: + cast[Mat4]([ + -10.0f, -20.0f, -30.0f, -40.0f, 50.0f, 60.0f, 70.0f, 80.0f, + 90.0f, 100.0f, 110.0f, 120.0f, 130.0f, 140.0f, 150.0f, 160.0f + ]) test "mat4 multiply identity": check mat4() * a ~= a @@ -815,7 +829,7 @@ suite "degenerate matrices": test "very large values": let big = 1e18f let m = mat2(big, 0, 0, big) - check determinant(m) == big * big + check abs(determinant(m) - big * big) < 1e30f let inv = inverse(m) check inv[0, 0] ~= (1.0f / big) check m * inv ~= mat2() From af5d7ac9fb911cff2c92c86d9d86d5cc8e08afda Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 17:27:58 -0700 Subject: [PATCH 34/35] f --- conformance/dump_glm.txt | 18 +++++++++--------- conformance/dump_vmath.txt | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/conformance/dump_glm.txt b/conformance/dump_glm.txt index 65c2d2a..d405806 100644 --- a/conformance/dump_glm.txt +++ b/conformance/dump_glm.txt @@ -443,13 +443,13 @@ ortho: N/A == default matrix printer == -matrix_a.default: ⎡1.0 5.0 9.0 13.0⎤ -⎢2.0 6.0 10.0 14.0⎥ -⎢3.0 7.0 11.0 15.0⎥ -⎣4.0 8.0 12.0 16.0⎦ - -transform.default: ⎡0.5993741 -2.4950442 1.8697325 10.0⎤ -⎢1.7407088 0.11302096 -1.9639301 20.0⎥ -⎢0.78146225 1.6619208 2.9405916 30.0⎥ -⎣0.0 0.0 0.0 1.0⎦ +matrix_a.default: / 1.0 5.0 9.0 13.0 \ +| 2.0 6.0 10.0 14.0 | +| 3.0 7.0 11.0 15.0 | + \ 4.0 8.0 12.0 16.0 / + +transform.default: / 0.5993741 -2.4950442 1.8697325 10.0 \ +| 1.7407088 0.11302096 -1.9639301 20.0 | +| 0.78146225 1.6619208 2.9405916 30.0 | + \ 0.0 0.0 0.0 1.0 / diff --git a/conformance/dump_vmath.txt b/conformance/dump_vmath.txt index d891e9f..9988d4f 100644 --- a/conformance/dump_vmath.txt +++ b/conformance/dump_vmath.txt @@ -453,7 +453,7 @@ matrix_a.default: mat4( ) transform.default: mat4( 0.5993741, 1.7407088, 0.78146225, 0.0, - -2.4950442, 0.113020934, 1.6619208, 0.0, + -2.4950442, 0.11302096, 1.6619208, 0.0, 1.8697325, -1.9639301, 2.9405916, 0.0, 10.0, 20.0, 30.0, 1.0 ) From 723d91dd72b8c6db63d0c901b6af78b2a42033c3 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 15 Apr 2026 17:45:02 -0700 Subject: [PATCH 35/35] v3.0.0 --- vmath.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmath.nimble b/vmath.nimble index c9eb30c..396d743 100644 --- a/vmath.nimble +++ b/vmath.nimble @@ -1,4 +1,4 @@ -version = "2.0.1" +version = "3.0.0" author = "Andre von Houck" description = "Your single stop for vector math routines for 2d and 3d graphics." license = "MIT"