Performance-oriented 3D math library for Go, optimized for graphics programming, game development, and scientific computing.
- Zero-allocation value types: Stack-allocated arrays for maximum performance
- Dual precision: Both float32 and float64 versions of all types
- Method chaining: In-place operations return
*Tfor fluent API - Immutable operations: Past-tense methods return copies without modifying originals
- OpenGL compatible: Column-major matrices, right-handed coordinates
- Cache-friendly: Optimized data structures for modern CPU caches
- Well-tested: Comprehensive test coverage with benchmarks
go get github.com/ungerik/go3dpackage main
import (
"github.com/ungerik/go3d/vec3"
"github.com/ungerik/go3d/mat4"
"github.com/ungerik/go3d/quaternion"
math "github.com/chewxy/math32"
)
func main() {
// Vector operations
a := vec3.T{1, 2, 3}
b := vec3.UnitX
// In-place modification with chaining
a.Add(&b).Scale(5) // a is now {10, 10, 15}
// Immutable operations
c := a.Scaled(0.5) // c = {5, 5, 7.5}, a unchanged
// Matrix transformations
var transform mat4.T
transform.AssignPerspective(math.Pi/4, 16.0/9.0, 0.1, 1000.0)
// Quaternions for rotations
q := quaternion.FromYAxisAngle(math.Pi / 4) // 45° around Y
rotated := q.RotatedVec3(&a)
}Every type has its own sub-package and is named T. Packages under float64/ use float64 instead of float32.
| Package | Description | Type Size |
|---|---|---|
vec2 |
2D vectors | 8 bytes |
vec3 |
3D vectors | 12 bytes |
vec4 |
4D vectors / homogeneous coordinates | 16 bytes |
mat2 |
2×2 matrices | 16 bytes |
mat3 |
3×3 matrices | 36 bytes |
mat4 |
4×4 matrices | 64 bytes (one cache line!) |
quaternion |
Quaternions for 3D rotations | 16 bytes |
All types are available in float64 precision under float64/:
float64/vec2,float64/vec3,float64/vec4float64/mat2,float64/mat3,float64/mat4float64/quaternion
generic- Generic matrix/vector interfaceshermit2- 2D Hermite splineshermit3- 3D Hermite splines
This library uses github.com/chewxy/math32 for float32 math functions. Import it as:
import math "github.com/chewxy/math32"The math32 package provides float32 versions of Go's standard math library functions, offering better performance for float32-based graphics calculations compared to converting to/from float64.
Present tense methods modify in place and return *T for chaining:
v := vec3.T{1, 2, 3}
v.Scale(2) // v is now {2, 4, 6}
v.Add(&vec3.UnitX) // v is now {3, 4, 6}Past tense methods return a modified copy:
v := vec3.T{1, 2, 3}
v2 := v.Scaled(2) // v2 = {2, 4, 6}, v unchanged
v3 := v.Added(&vec3.UnitX) // v3 = {2, 2, 3}, v unchangedresult := vec3.Zero
result.Add(&vec3.UnitX).Scale(5).Add(&vec3.UnitY)
// result = {5, 1, 0}Matrices use column-major layout (OpenGL convention):
mat[column][row]For DirectX (row-major), use Transpose().
- Right-handed coordinates: X × Y = Z
- Rotation direction: Counter-clockwise (right-hand rule)
- Angles: Radians (use
math32.Piconstants)
// Construction
v := vec3.T{1, 2, 3}
// Length
length := v.Length() // sqrt(x² + y² + z²)
lengthSq := v.LengthSqr() // x² + y² + z² (faster, no sqrt)
// Normalization
v.Normalize() // In-place
unit := v.Normalized() // Copy
// Arithmetic
v.Add(&other) // v += other
v.Sub(&other) // v -= other
v.Scale(2.5) // v *= 2.5
// Dot and cross products
dot := vec3.Dot(&a, &b)
cross := vec3.Cross(&a, &b)
// Distance
dist := vec3.Distance(&a, &b)// Identity and zero
m := mat4.Ident
z := mat4.Zero
// Translation
m.SetTranslation(&position)
// Rotation
m.AssignXRotation(angle)
m.AssignYRotation(angle)
m.AssignZRotation(angle)
m.AssignQuaternion(&quat)
// Scaling
m.AssignScaling(sx, sy, sz)
// Camera matrix
var view mat4.T
view.AssignLookAt(&eye, ¢er, &up)
// Projection matrices
var proj mat4.T
// Symmetric perspective (typical 3D game)
proj.AssignPerspective(fovy, aspect, znear, zfar)
// Asymmetric frustum
proj.AssignFrustum(left, right, bottom, top, znear, zfar)
// Orthographic (2D or CAD)
proj.AssignOrthogonalProjection(left, right, bottom, top, znear, zfar)
// Matrix multiplication
mvp := mat4.Ident
mvp.AssignMul(&projection, &view)
mvp.Mul(&model)// Construction
axis := vec3.T{0, 1, 0}
q := quaternion.FromAxisAngle(&axis, math.Pi/4)
// Convenience constructors
qx := quaternion.FromXAxisAngle(angle)
qy := quaternion.FromYAxisAngle(angle)
qz := quaternion.FromZAxisAngle(angle)
// From Euler angles
q := quaternion.FromEulerAngles(yaw, pitch, roll)
// Rotate vector
v := vec3.T{1, 0, 0}
q.RotateVec3(&v) // In-place
rotated := q.RotatedVec3(&v) // Copy
// Combine rotations (order matters!)
q3 := quaternion.Mul(&q1, &q2) // Apply q2 first, then q1
// Smooth interpolation
halfway := quaternion.Slerp(&start, &end, 0.5)
// Convert to matrix
var m mat4.T
m.AssignQuaternion(&q)// For graphics (sufficient for most cases)
import "github.com/ungerik/go3d/vec3"
// For scientific computing (higher precision)
import "github.com/ungerik/go3d/float64/vec3"// ✓ Good: Minimal allocations
v.Scale(2).Add(&other)
// ✗ Less efficient: Creates copies
result := v.Scaled(2).Added(&other)// ✓ Faster: No sqrt
thresholdSq := threshold * threshold
if v.LengthSqr() < thresholdSq {
// ...
}
// ✗ Slower: Uses sqrt
if v.Length() < threshold {
// ...
}// ✓ Efficient: No copy
dot := vec3.Dot(&a, &b)
// The API uses pointers for better performance// ✓ Good: Single allocation reused
var transform mat4.T
for i := range objects {
transform.AssignTranslation(&objects[i].Position)
// Use transform...
}
// ✗ Bad: Allocates every iteration
for i := range objects {
var transform mat4.T // New allocation
// ...
}type Rect struct {
Min vec2.T
Max vec2.T
}Operations:
r := vec2.Rect{Min: vec2.T{0, 0}, Max: vec2.T{100, 100}}
width := r.Width()
height := r.Height()
center := r.Center()
area := r.Area()
joined := r.Join(&other) // Bounding rectangle
clamped := r.Clamp(&point) // Clamp point to rectangle
contains := r.Contains(&point) // Point-in-rectangle testtype Box struct {
Min vec3.T
Max vec3.T
}Operations:
b := vec3.Box{Min: vec3.T{0, 0, 0}, Max: vec3.T{10, 10, 10}}
size := b.Size()
center := b.Center()
volume := b.Volume()
joined := b.Join(&other) // Bounding box
contains := b.Contains(&point) // Point-in-box testTransforms apply right to left:
// Mathematical notation: MVP = P × V × M
// In go3d:
var mvp mat4.T
mvp.AssignMul(&projection, &view) // mvp = P × V
mvp.Mul(&model) // mvp = (P × V) × M// Point in space (affected by translation): w=1
point := vec4.T{x, y, z, 1.0}
// Direction vector (not affected by translation): w=0
direction := vec4.T{x, y, z, 0.0}// Object transform
position := vec3.T{10, 5, 0}
rotation := quaternion.FromYAxisAngle(math.Pi / 4)
scale := vec3.T{2, 2, 2}
var model mat4.T
model.SetTranslation(&position)
model.AssignQuaternion(&rotation)
model.ScaleVec3(&scale)
// Camera
eye := vec3.T{0, 10, 20}
center := vec3.T{0, 0, 0}
up := vec3.UnitY
var view mat4.T
view.AssignLookAt(&eye, ¢er, &up)
// Projection
var projection mat4.T
projection.AssignPerspective(math.Pi/4, 16.0/9.0, 0.1, 1000.0)
// Combined MVP
var mvp mat4.T
mvp.AssignMul(&projection, &view)
mvp.Mul(&model)
// Transform vertex
vertex := vec4.T{1, 0, 0, 1}
transformed := mat4.MulVec4(&mvp, &vertex)func SmoothCamera(start, end quaternion.T, t float32) quaternion.T {
// Clamp t to [0, 1]
if t < 0 { t = 0 }
if t > 1 { t = 1 }
// Smooth interpolation
return quaternion.Slerp(&start, &end, t)
}Make objects always face the camera:
func CreateBillboard(objectPos, cameraPos, cameraUp vec3.T) mat4.T {
// Calculate direction to camera
direction := vec3.Sub(&cameraPos, &objectPos)
direction.Normalize()
// Calculate right vector
right := vec3.Cross(&cameraUp, &direction)
right.Normalize()
// Recalculate up vector
up := vec3.Cross(&direction, &right)
// Build matrix
var billboard mat4.T
billboard[0] = vec4.T{right[0], right[1], right[2], 0}
billboard[1] = vec4.T{up[0], up[1], up[2], 0}
billboard[2] = vec4.T{direction[0], direction[1], direction[2], 0}
billboard[3] = vec4.T{objectPos[0], objectPos[1], objectPos[2], 1}
return billboard
}The package is designed for performance over convenience where necessary:
- Pointer arguments: Reduces allocations and copies
- Stack allocation: Value types (arrays) avoid GC pressure
- Column-major matrices: Direct GPU upload without transpose
- Method chaining: Fluent API without sacrificing performance
Performance comparison (see mat4/mat4_test.go):
cd mat4
go test -bench=BenchmarkMulAddVec4_PassBy*Contributions are welcome! Please ensure:
- All tests pass:
go test ./... - Code is formatted:
go fmt ./... - Documentation is updated
- Benchmarks show no regressions
MIT - see LICENSE file for details.