Skip to content

Repository files navigation

Kright Game Tools

Introduction

KrightGameTools consists of several independent modules in Scala and C++ for 2d and 3d applications: math and physics simulation. Most Scala modules support both JVM and Scala.js.

It contains some basic classes like vectors, rotors, matrices, and rigid body physics built on top of them. All of this is implemented from scratch and doesn't depend on other libraries. So I hope the library could be used within GraalVM or from Kotlin or Java.

All the code is under MIT license. Contributions are welcome, feel free to send a pull request.

Table of Contents

Examples

  1. examples/precession: Scala + sbt, simulate body precession
  2. examples/nbody: Gradle + Kotlin + Java on JVM and GraalVM, simulation of N-bodies in space.
  3. examples/indigodemo: Indigo game engine with scala js.
  4. cpp: C++ code for 3d physics simulation.

Getting started

Requires Scala 3.9 or newer (use version 0.10.1 for older Scala 3 versions).

You don't need all the modules listed below: pick the ones you use, see module dependencies.

sbt

libraryDependencies ++= Seq(
  "me.kright" %% "gametools-mathutil" % "0.12.0",
  "me.kright" %% "gametools-flatarray" % "0.12.0",
  "me.kright" %% "gametools-vector" % "0.12.0",
  "me.kright" %% "gametools-matrix" % "0.12.0",
  "me.kright" %% "gametools-pga2d" % "0.12.0",
  "me.kright" %% "gametools-pga2dgeom" % "0.12.0",
  "me.kright" %% "gametools-pga3d" % "0.12.0",
  "me.kright" %% "gametools-pga3dgeom" % "0.12.0",
  "me.kright" %% "gametools-physics1d" % "0.12.0",
  "me.kright" %% "gametools-pga3dphysics" % "0.12.0"
)

For Scala.js use %%% instead of %%:

libraryDependencies += "me.kright" %%% "gametools-pga3d" % "0.12.0"

Gradle

Note: suffix _3 is for Scala 3.

dependencies {
  implementation 'me.kright:gametools-mathutil_3:0.12.0'
  implementation 'me.kright:gametools-flatarray_3:0.12.0'
  implementation 'me.kright:gametools-vector_3:0.12.0'
  implementation 'me.kright:gametools-matrix_3:0.12.0'
  implementation 'me.kright:gametools-pga2d_3:0.12.0'
  implementation 'me.kright:gametools-pga2dgeom_3:0.12.0'
  implementation 'me.kright:gametools-pga3d_3:0.12.0'
  implementation 'me.kright:gametools-pga3dgeom_3:0.12.0'
  implementation 'me.kright:gametools-physics1d_3:0.12.0'
  implementation 'me.kright:gametools-pga3dphysics_3:0.12.0'
}

Library modules

Initially, it was a repo for simple math with matrices and vectors. I implemented physics for 3d on top of that math. During my development, I figured out that plane-based geometric algebra is a fantastic way to describe physics equations. So there is a code with geometric algebra, which includes math and physics too.

Modules

I'm inspired by https://bivector.net/PGADYN.html

I rewrote physics equations in PGA, it looks like PGA is a better way of describing physics.

If you are new to geometric algebra, start with my article Plane-based geometric algebra for rigid body motion: it explains the ideas behind this library, from the basics of geometric algebra to how rigid body motion and physics are expressed in PGA.

The concepts shared by the pga2d and pga3d modules (the products, bulk and weight, duality, exp/log, the narrowest-result-type rule) are described in pga-concepts.md.

  • mathutil: small utilities shared by the other modules - fast math operations, fast ranges for loop iterations, precision-related helpers.
  • flatarray: flat storage of small all-Double structs (PGA points, vectors, rotors, triangles, etc.) as runs of Doubles in an Array[Double]: the FlatDoubleSerializer typeclass (derivable, recursing into nested case classes) plus FlatArray/FlatBuffer/FlatView with a compile-time-inlined hot path, so bulk transforms avoid materializing the element objects (see the FlatArray benchmarks).
  • symbolic: simple implementation for AST like (1.0 + ("y" * "x")) with simplification rules. Used by the code generators to derive formulas symbolically.
  • ga: experimental support for geometric algebra (GA) and plane-based geometric algebra (PGA). See https://bivector.net for more details. Suitable for any dimensions and signatures, represents a multivector as a map from basis blade to coefficient. Flexible, but slower than the generated pga2d/pga3d classes; used as the source of truth for the code generators.
  • vector: Vector2d, Vector3d, Vector4d - simple immutable vector classes with the usual operations.
  • matrix: linear algebra over arrayview 2d views of doubles - there is no matrix class, any view (including a lazy transposed one or a slice) is a matrix; multiplication, determinants, the Jacobi eigendecomposition, Cholesky, projection matrices.
  • pga2d: efficient library for 2d PGA with generated code and some common cases—Pga2dLine, Pga2dPoint, Pga2dRotor, Pga2dMotor, etc. The 2d counterpart of pga3d with the same style and terminology, except that a grade-1 element is called a line instead of a plane, and the analog of Pga3dRotor is Pga2dRotor with just 2 fields (cos and sin of the half-angle).
  • pga3d: efficient library for 3d PGA with generated code and some common cases—Pga3dPlane, Pga3dPoint, Pga3dRotor, Pga3dBivector, etc. There is a huge number of similar methods (for each pair of classes for each type of multiplication). Because of generated methods for each case it's possible to know at compile time that, for example, dot product of two bivectors is a scalar or geometric product of two planes is a motor.
  • pgaNdCodeGen: hand-made code generator for the pga2d and pga3d modules. It does operations in symbolic form, and searches the most narrow subclass of multivector for the result. Generates Scala code for both, plus C++ code for 3d.
  • physics1d: physics independent of PGA. Scalar 1d force models - the SpringElasticity curve (a soft zone blending into a stiff branch) and rate-independent hysteretic friction for rubber bushings (HystereticFriction: Dahl, Berg, Iwan models) with an integrator-friendly pure-evaluation contract. And the universal ODE solvers (OdeSolverRK4 and others) over Integrable - an object with a state as a run of doubles and a place for normalization - so your own objects are integrated together with the rigid bodies of pga3dphysics.
  • pga3dphysics: some helper classes for implementing physics engine - body inertia, physics solvers, etc.
  • pga3dgeom: classes for geometry - edges, triangles, spheres, rays, axis-aligned bounding boxes
  • pga2dgeom: the 2d counterpart of pga3dgeom - edges, triangles, circles, rays, axis-aligned bounding boxes

Module dependencies

An arrow means "depends on". Only the published modules are shown, and transitive dependencies are omitted. The dashed arrayview is my own library too, under the same MIT license, it just lives in a separate repository. vector is standalone: no other published module depends on it.

graph TD
    subgraph g3d ["3d"]
        pga3dphysics --> pga3d
        pga3dgeom --> pga3d
    end
    subgraph g2d ["2d"]
        pga2dgeom --> pga2d
    end
    vector --> mathutil
    pga3dphysics --> physics1d
    pga3d --> flatarray
    pga3d --> matrix
    pga2d --> flatarray
    pga2d --> matrix
    physics1d --> mathutil
    flatarray --> mathutil
    matrix --> mathutil
    matrix --> arrayview["arrayview (separate repo)"]
    style arrayview stroke-dasharray: 5 5
Loading

It is enough to add the topmost module you need, the build tool fetches the rest. For example, gametools-pga3dphysics brings in pga3d, physics1d, matrix, flatarray, mathutil and arrayview.

C++ code

  • cpp: same code as in pga3d and pga3dphysics in Scala, but for C++.

The current implementation is experimental and may be changed in the future. I use C++ 20 because of Unreal Engine 5 requirements.

Code conventions

  • Immutable API; Seq(...) literals are created as ArraySeq(...) (array-backed), Seq stays as the interface type.
  • Generated code (pga2d, pga3d, cpp/pga3d) is never edited by hand - change pgaNdCodeGen and regenerate; CI checks for drift.
  • Queries that may find nothing (intersection, deepestContact in the geom modules) return T | Null with null for "no result" instead of Option[T] - no allocation on the hot path. The library is compiled with -Yexplicit-nulls, so the nullability is part of the type;

Tests

sbt test

I use scalaCheck and property-based approach. It goes well with checking math properties such as addition or multiplication associativity, zero and id elements, morphisms between rotors and corresponding matrices. For physics, it's ok to check that total energy and impulse are constant in body systems without friction.

How to change this library and try changes locally in another project

Change lib code, publish to local ivy repo:

sbt publishLocal

Or to the local maven:

sbt publishM2

In my case "/.ivy2/local/me.kright/gametools-pga3d_3/0.12.1-SNAPSHOT" and "/.m2/repository/me/kright/gametools-pga3d_3/0.12.1-SNAPSHOT"

After that, add the local library to another project. In my case, it was for sbt:

libraryDependencies += "me.kright" %% "gametools-pga3d" % "0.12.1-SNAPSHOT"

And for Gradle:

implementation "me.kright:gametools-pga3d_3:0.12.1-SNAPSHOT"

Maybe you will need to remove cached lib, it will be placed in path like "~/.cache/coursier/v1/https/repo1.maven.org/maven2/me/kright/".