Skip to content

Latest commit

 

History

51 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

quarkgo

A Go port of QuarkPhysics, a 2D physics engine for games.

Status

Some logic still does not match the original C++ logic, we are still in active development.

Features

  • Rigid bodies — convex polygons, circles, rectangles; Verlet integration; kinematic mode; collision response with friction and restitution
  • Soft bodies — mass-spring model with PBD; area-preserving; shape matching; self-collisions; internal springs
  • Area bodies — sensor/trigger volumes; OnCollisionEnter/OnCollisionExit events; gravity-free zones; linear force application
  • Joints — distance constraints with balance, groove mode (pull-only), pin joints, world-space anchors
  • Springs — particle-level distance constraints with distance limits and accumulated-force pipeline
  • Angle constraints — 3-particle angle limits with wrap-around handling
  • Raycasting — instance-based auto-update and static one-shot queries; AABB broadphase filter; layer masks
  • Broadphase — built-in Sweep-and-Prune; pluggable interface; spatial hashing extension
  • Platformer body — walk, jump (variable height, multi-jump, wall jump), slope walking, moving-platform snap
  • Serialization.qmesh JSON format for mesh loading/saving
  • Concave decomposition — pure-Go Hertel-Mehlhorn algorithm (ear clipping + convex merge)
  • Parallel narrowphase — optional goroutine-based collision detection (Phase 5)

Quick Start

# Build and test the core engine
go test ./...

# Run benchmarks
go test -bench=. -run=^$ ./physics/

# Run with race detector
go test -race ./...

Usage

Creating a World

import "github.com/Vmarcelo49/quarkgo/physics"

world := physics.NewWorld(
    physics.WithGravity(physics.Vec2{X: 0, Y: 0.2}),
    physics.WithIterations(4),
)

Rigid Body

box := physics.NewRigidBody()
box.AddMesh(physics.NewRectMesh(physics.Vec2{X: 32, Y: 32}, physics.Vec2Zero(), physics.Vec2Zero()))
box.SetPosition(physics.Vec2{X: 100, Y: 0})
world.AddRigidBody(box)

Static Body (Floor)

floor := physics.NewRigidBody()
floor.AddMesh(physics.NewRectMesh(physics.Vec2{X: 500, Y: 20}, physics.Vec2Zero(), physics.Vec2Zero()))
floor.SetPosition(physics.Vec2{X: 250, Y: 400})
floor.SetMode(physics.BodyModeStatic)
world.AddRigidBody(floor)

Soft Body

sb := physics.NewSoftBody()
sb.AddMesh(physics.NewPolygonMesh(16, 6, physics.Vec2Zero(), -1))
sb.SetPosition(physics.Vec2{X: 100, Y: 0})
sb.SetAreaPreservingEnabled(true)
sb.SetShapeMatchingEnabled(true, false)
world.AddSoftBody(sb)

Area Body (Sensor)

area := physics.NewAreaBody()
area.AddMesh(physics.NewCircleMesh(30, physics.Vec2Zero()))
area.SetPosition(physics.Vec2{X: 100, Y: 100})
area.OnCollisionEnter = func(ab *physics.AreaBody, b *physics.Body) {
    fmt.Println("Body entered area!")
}
world.AddAreaBody(area)

Joint

joint := physics.NewJoint(bodyA, anchorA, anchorB, bodyB)
joint.SetLength(50)
joint.SetRigidity(0.8)
world.AddJoint(joint)

Platformer Body

import "github.com/Vmarcelo49/quarkgo/ext/platformer"

player := platformer.New()
player.AddMesh(physics.NewRectMesh(physics.Vec2{X: 32, Y: 32}, physics.Vec2Zero(), physics.Vec2Zero()))
player.SetPosition(physics.Vec2{X: 100, Y: 300})
world.AddRigidBody(&player.RigidBody)
player.RegisterPostUpdate() // Required for PostUpdate dispatch

// In your game loop:
player.Walk(1)  // walk right
player.Jump(5.0, false)
player.ReleaseJump()

Parallel Narrowphase

world := physics.NewWorld(
    physics.WithGravity(physics.Vec2{X: 0, Y: 0.2}),
    physics.WithConcurrency(physics.ConcurrencyConfig{
        Enabled:    true,
        NumWorkers: 0, // 0 = runtime.NumCPU()
    }),
)

Loading .qmesh Files

import "github.com/Vmarcelo49/quarkgo/mesh/qmesh"

meshes, err := qmesh.LoadFile("path/to/mesh.qmesh")
if err != nil {
    panic(err)
}
for _, md := range meshes {
    body.AddMesh(physics.NewMeshFromData(md, true, true))
}

Concave Polygon Decomposition

import "github.com/Vmarcelo49/quarkgo/mesh/polypartition"

// Register once at startup
physics.SetConvexPartitioner(polypartition.ConvexPartitionFromParticles)

Spatial Hashing Broadphase

import "github.com/Vmarcelo49/quarkgo/ext/spatialhash"

world := physics.NewWorld(
    physics.WithGravity(physics.Vec2{X: 0, Y: 0.2}),
    physics.WithBroadphaseImpl(spatialhash.New(128.0)),
)

Event Listeners

body.OnPreStep = func(b *physics.Body) {
    // Called before each physics step
}

body.OnStep = func(b *physics.Body) {
    // Called after each physics step
}

body.OnCollision = func(b *physics.Body, info physics.CollisionInfo) bool {
    // Return false to ignore this collision
    return true
}

Raycasting

// One-shot raycast
contacts := physics.RaycastTo(world, rayPos, rayVec, 1, false)
for _, c := range contacts {
    fmt.Printf("Hit %v at (%.1f, %.1f)\n", c.Body, c.Position.X, c.Position.Y)
}

// Registered raycast (auto-updated each step)
ray := physics.NewRaycast(rayPos, rayVec, false)
world.AddRaycast(ray)
// After world.Update():
for _, c := range ray.Contacts() {
    // ...
}

Simulation Step

// Advance the simulation by one step
world.Update()

Call world.Update() once per frame in your game loop.

Package Layout

  • physics/ — core engine (one package, mirrors C++ friend-class encapsulation)
  • ext/spatialhash/ — uniform-grid broadphase
  • ext/platformer/ — character controller for platformer games
  • mesh/qmesh/.qmesh JSON I/O
  • mesh/polypartition/ — pure-Go convex decomposition (Hertel-Mehlhorn)
  • tests/parity/ — golden-file parity test harness
  • tests/benchmark/ — performance benchmarks

Design Decisions

Key porting decisions are documented in DECISIONS.md. Notable choices:

  • float32 throughout — preserves bit-for-bit parity with C++ float arithmetic
  • Per-World ContactPool — replaces the C++ global static pool (enables future concurrency)
  • Single physics package — mirrors C++ friend-class encapsulation without exported fields
  • No cgo — pure Go, including the polypartition port
  • Struct tag + switch for virtual dispatch — avoids interface overhead in the hot loop

Performance

Benchmark results (Intel Xeon, 2 cores):

Scenario Bodies ns/op
Rigid bodies stacking 10 ~5,500
Rigid bodies stacking 50 ~69,000
Rigid bodies stacking 100 ~345,000
Rigid bodies stacking 500 ~668,000
Soft bodies 5 ~7,200
Soft bodies 20 ~40,000
Soft bodies 50 ~153,000

The engine targets 60 FPS for 500+ rigid bodies on modern hardware.

License

MIT, matching the upstream QuarkPhysics license. See LICENSE.

About

A Go port of QuarkPhysics — a 2D physics engine for games.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages