geomancy

Vectors and matrix manipulation

Stackage Nightly 2026-11-09:0.4.0.0
Latest on Hackage:0.4.0.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.4.0.0@sha256:30270e8b509b93a77fbd6d60b63d5b9ddab9d7fc942a086ab9b955254a8d67e4,4562

Module documentation for 0.4.0.0

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 transformations (e.g. rendering instance data):

    poke
      10
        geomancy: OK
          42.3 ns ± 1.4 ns, 0.47x
        linear:   OK
          90.5 ns ± 3.1 ns
        linear/T: OK
          81.6 ns ± 2.7 ns
...
      1000
        geomancy: OK
          2.85 μs ±  37 ns, 0.36x
        linear:   OK
          7.93 μs ± 132 ns
        linear/T: OK
          7.08 μs ± 348 ns

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

    multiply
      2
        geomancy: OK
          12.3 ns ± 164 ps, 0.34x
        linear:   OK
          36.1 ns ± 1.3 ns
...
      1000
        geomancy: OK
          7.71 μs ±  93 ns, 0.23x
        linear:   OK
          34.0 μs ± 1.3 μ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.

Benchmarking and comparing

stack bench --ba '--csv baseline.csv --stdev 2 --timeout 120'
stack bench --ba '--baseline baseline.csv --fail-if-slower 5'

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.4.0.0

A generally safe upgrade, but quite a bit of changes across the board.

  • Added double-precision DVec2, DVec3 (with Packed), DVec4, DMat4 and DQuaternion. Includes SSE2/NEON kernels for DMat4 products and DMat4 !* DVec4.

  • Added DTransform (with DTransform.Tree), Vulkan.DProjection and Vulkan.DView. Compose in double and convert with fromTransform/toTransform to keep precision far from the origin.

  • Added DPoint2, DPoint3, DPoint3P and DPoint4. Point’s AffineSpace, qd, distance and lerp now work over any scalar, not just Float.

  • Added 64-bit integer vectors I64Vec2, I64Vec3 (with Packed), I64Vec4, U64Vec2, U64Vec3 (with Packed), U64Vec4.

  • Added fromVecN/toVecN, fromMat4/toMat4 and fromQuaternion/toQuaternion for single-precision conversions.

  • Added HasField instances for the lanes of every vector type, for v.x, v.y, v.z, v.w with OverloadedRecordDot.

    • Packed vectors have them too, and a Point forwards each lane to the vector it wraps.
    • Optimized code is the same as with withVecN: one unpack, and only the lanes used get read.
  • Added rewrite rules that fuse Vec4 and DVec4 producers into their consumers.

    • Intermediate vectors in expressions like dot (a + b) c, lerp t a b or (a + b) * c - d are no longer allocated.
    • Folds over such expressions got 20-30% faster.
    • When the rules fire, only the lanes the consumer uses are evaluated, so a fused expression may avoid forcing an undefined lane.
  • Added HasField swizzles for every vector family, in Geomancy.Swizzle.<Family>.Shuffle, .Down and .Up (families: Vec, DVec, IVec, UVec, I64Vec, U64Vec).

    • Shuffle keeps the size (v.zyx, and the identity v.xyz), Down narrows (v.xy of a Vec4), Up widens (v.xyxy of a Vec2).
    • The instances are orphans. import Geomancy.Swizzle () brings all of them, or import just the modules you need, like import Geomancy.Swizzle.Vec.Down ().
    • Chains like v.wzyx.zyx.yx.y compile to a single lane read.
  • Added Extend swizzles that widen a vector with constant lanes, in Geomancy.Swizzle.<Family>.Extend.

    • The source lanes stay in order and get 0 or 1 appended: flatPos.xy01, pos.xyz1, uv.xy0.
    • Masks compose from extending and narrowing: white.xyz0.wyz is (0, y, z).
  • Changed Geomancy.Swizzle labels to use the HasField swizzles, so v .@ #xzy works for every family.

  • Modified Quaternion rotation to use a unit shortcut.

    • The contract was already “use normalized” quaternions for rotations.
    • Float got a modest speedup, Double is twice-ish as fast. Both are faster than Linear.
  • Added Eq for Mat4, DMat4, Transform and DTransform. Elements are compared pointwise with IEEE semantics, like the derived Eq of Vec3: 0 == -0 and a matrix containing NaN is not equal to itself.

  • Changed Eq and Ord for Vec4 from bytewise to pointwise, matching Vec3.

    • Ord is now lexicographic over x, y, z, w. Previously it compared the raw bytes, which is not a numeric order.
    • Map/Set keyed by Vec4 will order differently, and keys differing only in the sign of zero now collapse.
      • Keys containing NaN are no longer found by lookups, since Ord is no longer total for them.
      • Basically, don’t use floats for keys.
  • Fixed abs for IVec3 and IVec4, which returned the vector unchanged.

  • Fixed Block layout for IVec3.Packed, whose alignment140 and sizeOf140 were swapped in 0.3.1.0 (now 16 and 12).

  • Fixed peek for Vec4 and Mat4 returning the wrong element under optimization.

    • Data.Vector.Storable indexing runs peek with a shared realWorld# token, which let GHC merge the buffer allocations of neighbouring reads, so every read in a strict context got the last element.
    • peek now loads on the caller’s token and builds the value in a private buffer.
    • Vec4 peek and poke are about twice as fast, since the 16-byte copies they replace were done bytewise.
  • Changed Vec4 and Mat4 storage from aligned pinned to regular unpinned byte arrays, like the new DVec4 and DMat4.

    • SIMD kernels now use unaligned loads and stores.
    • Code reaching into the exported constructors must not take byteArrayContents# or pass the array to safe foreign calls, since the GC may now move it.

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.