geomancy

Vectors and matrix manipulation

Stackage Nightly 2026-07-08:0.3.1.0
Latest on Hackage:0.3.1.0

See all snapshots geomancy appears in

BSD-3-Clause licensed by IC Rainbow
Maintained by [email protected]
This version can be pinned in stack with:geomancy-0.3.1.0@sha256:21917ccdecebed3034b3b34a9f2f1efa89e1bafc51466dbf1c1d4d37940708ea,2588

Geomancy

Linear is nice, but slow. Those are naughty, but a bit faster.

  • All data types are monomorphic, unpacked and specialized.
  • Mat4 and Vec4 are ByteArray#.
  • Mat4xMat4 and Mat4xVec4 is done with SIMD.

The Numbers

Storing a list of 1000 transformations (e.g. rendering instance data):

benchmarking 4x4 poke/1000/geomancy
time                 11.76 μs   (11.66 μs .. 11.92 μs)
                     0.999 R²   (0.998 R² .. 1.000 R²)
mean                 11.75 μs   (11.69 μs .. 11.86 μs)
std dev              283.4 ns   (199.0 ns .. 399.0 ns)
variance introduced by outliers: 26% (moderately inflated)

If you’re willing to adjust your shaders, it’s only 2.4 times slower.

benchmarking 4x4 poke/1000/linear
time                 28.29 μs   (28.21 μs .. 28.38 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 28.40 μs   (28.34 μs .. 28.50 μs)
std dev              267.4 ns   (145.5 ns .. 419.9 ns)

Keeping your shaders straight make the affair 6.1x slower.

benchmarking 4x4 poke/1000/linear/T
time                 73.70 μs   (73.06 μs .. 74.49 μs)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 72.77 μs   (72.50 μs .. 73.22 μs)
std dev              1.129 μs   (793.5 ns .. 1.580 μs)

Folding down a gloss-style scene graph is where it is all started:

benchmarking 4x4 multiply/1000/geomancy
time                 20.79 μs   (20.77 μs .. 20.83 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 20.80 μs   (20.78 μs .. 20.83 μs)
std dev              76.71 ns   (60.01 ns .. 99.06 ns)

benchmarking 4x4 multiply/1000/linear
time                 173.9 μs   (173.6 μs .. 174.4 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 173.5 μs   (173.2 μs .. 174.4 μs)
std dev              1.733 μs   (727.8 ns .. 3.422 μs)

Add that time to the poking that’ll follow.

Sure, it is in the lower microseconds range, but this budget can be used elsewhere.

Conventions

Matrix layout

Transforms produced, composed, and applied to mimic the GLSL order (col-major):

  • vec4 vPosOut = P * V * M * vPosIn;
  • vPosOut = (p <> v <> m) !* vPosIn

This way you don’t have to transpose your transforms or fiddle with layout annotations.

Projections / Views

Geomancy.Vulkan.Projection is using the “reverse-depth” trick that remaps the vulkan default [0; 1] range to [1; 0]. This grants extra precision with one less parameter to specify (you only need “near” now), but makes handedness reasoning tricky. The default depth range the coordinate is left-handed (+X right, +Y down, +Z forward). But after reversing the depth it has to be paired with a right-handed view function like Geomancy.Vulkan.View.lookAtRH.

The intended up vector is still vec3 0 (-1) 0 – +Y down. Silly as it sounds, this matches the XY plane of the window with XY plane in front of a “first person” camera.

Rotations

Axis rotations (using rotateQ) will appear clockwise when looking along the axis.

Angle rotations follow Tait-Bryan angles (heading/elevation/bank or yaw/pitch/roll) in the y-x-z frame.

  • rotateZ (time * rate) will follow the clock hands in 2D scenes and roll in 3D.
  • rotateX will follow the sun from sunrise to sunset, increasing elevation / pitching UP.
  • rotateY will turn you right, increasing yaw/heading eastwards.

Using Geomancy.Quaternion.intrinsic roll pitch yaw will make a rotation from the 3 angles in one go. You can use it to rotate a point directly (e.g vec3 0 0 1 to get a direction vector from Quaternion) or commit to a matrix using Transform.rotateQ.

yaw-pitch-roll

You’re of course free to define your own transforms, just copy the modules and tune to your liking. Just make sure that you use matching row/column constructors and the math layer will do the rest, fast.

GLSL-like functions

To further facilitate conversion between the host and shader code Geomancy.Gl.Funs provides common functions like glFract and smoothstep.

Changes

Changelog for geomancy

0.3.1.0

  • Fixed Block.sizeOf40 for Packed 3-vectors (was 16, now 12).

0.3.0.1

  • Added Lift instances for data. Opt-out by disabling the th-lift flag.
  • Added Serialise instances for data. Opt-out by disabling the serialise flag.

0.3.0.0

[0] releases without row/col-major, handedness, and counter/clockwise confusion.

This release brings sanity and consistency around the transform stack.

  • Transforms have proper col-major definitions.
  • Operation ordering follows the GLSL convention.
    • The composition order is (p <> v <> m <> ...) (from global to local). Reverse your transforms to adjust.
    • The application order is m !* v (with the operators becoming infixr 5, just under the <>). It will do Mᵀ * v inside for SIMD reasons and to match what GLSL does for its M * v ops.
  • Geomancy.Vulkan.Projection is right-handed, BUT produces reverse-depth range ([1; 0], In 3D infinite-Z converges to 0).
    • Replace your depth tests with OP_GREATER and clear to 0.0 - get better precision for your migration troubles.
  • Geomancy.Vulkan.View is right-handed, with +Z being forward.
    • The intended up vector is still vec3 0 (-1) 0 – +Y down. Silly as it sounds, this matches the XY plane of the window with XY plane in front of a “first person” camera.
  • Axis rotations (using rotateQ) will appear clockwise when looking along the axis.
  • Angle rotations follow Tait-Bryan angles (heading/elevation/bank or yaw/pitch/roll) in the y-x-z frame.
    • rotateZ (time * rate) will follow the clock hands in 2D scenes and roll in 3D.
    • rotateX will follow the sun from sunrise to sunset, pitching UP / increasing elevation.
    • rotateY will turn you right, increasing yaw / heading eastwards.
  • You’re of course free to define your own transforms, just copy the modules and tune to your liking. Just make sure that you use matching row/column constructors and the math layer will do the rest, fast.
  • Added webcolor-labels instances for UVec3/Vec3/Vec4.

0.2.6.0

  • Geomancy.Gl.Block extract to gl-block package as Graphics.Gl.Block.
  • Added convert function to vector modules to facilitate type-changing operations like rounding.
  • Added Ix instances for integral vectors.
  • Added dot for integral vectors.

0.2.5.0

  • Added Geomancy.Gl.Block to derive packed/std140/std430 layouts generically. Originally a part of the unpublished glow package in the codex project by Edward Kmett. Add ptrdiff-0 to your stack resolvers.

0.2.4.2

  • Support ARM/aarch64 SIMD.

0.2.4.1

  • Support simple-affine-space-0.2

0.2.4.0

A bunch of experimental code to see what sticks.

  • Added simple-affine-space instances.
    • The classes are re-exported from Geomancy, bringing stuff like dot and normalize.
  • Added mono-traversable instances and Geomancy.Elementwise wrapper.
  • Added Point wrapper and PointN aliases.
  • Added Geomancy.Interpolate with generic linear, quadratic and cubic functions.
    • ⚠️ VecN.lerp has wrong (flipped) order of vector arguments. This is fixed in linear.
  • Added Geomancy.Swizzle overloaded labels.
  • Added Geomancy.Gl.Funs with kinda-GLSL math functions, appropriately overloaded.

0.2.3.0

  • Vec4 moved to ByteArray#.
  • Mat4 !* Vec4 is now SIMD too.

0.2.2.4

  • Add Transform trees.

0.2.2.3

  • Add IVec and UVec 32-bit integer vectors.

0.2.2.2

  • Fixed using Transform.apply with projection inverses.

0.2.2.1

  • Add Vec2.
  • Add pattern synonyms to Geomancy re-exports.
  • Add Fractional instances.
  • Add conversions from tuples and lower-dimension vecs.

0.2.2.0

  • Add pattern synonym alternatives to withVecN.
  • Add Mat4 converstion from Linear.M44.
  • Hide toList and toListTrans.
  • Add toList2d and publish its element-order wrappers.

0.2.1.0

Transform rewrite

  • Change perspective FoV to radians.
  • Add infinitePerspective.
  • Add Transform.inverse.
  • Hide mat4, withMat4.

0.2.0.0

Mat4 rewrite

  • Add rowMajor, withRowMajor, toListRowMajor.
  • Add colMajor, withColMajor, toListColMajor.
  • Add Mat4.inverse.
  • Expose matrixProduct.
  • Add scalarMultiply.
  • Add Mat4.(!*) to use with Vec4.
  • Extract transformations to Geomancy.Transform and use column notation.
  • Add Transform.(!.) and apply to use with Vec3.
  • Rename scale to scale3.
  • Add uniform scale.
  • Add scaleXY for flat meshes.
  • Rename mkTransformation to dirPos.
  • Add rotateQ via dirPos with empty translation.
  • Add Vec3.Packed newtype without extra padding.

0.1.3.0

  • Update tests
  • Add zipWith
  • Rename elementwise to pointwise
  • Rename colMajor to toList
  • Rename rowMajor to toListTrans

0.1.2.1

  • Add Mat4 multiplication test via linear
  • Add elementwise, colMajor, rowMajor

0.1.2.0

  • Move projections and views to Vulkan namespace.

0.1.1.2

  • Fix bug in quaternion rotationBetween.

0.1.1.1

  • Add lookAtUp and rotationBetween.