physics

package
v0.0.0-...-9691af7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Index

Constants

View Source
const MaxWorldSize float32 = 99999.0

Variables

This section is empty.

Functions

func AngleBetweenTwoVectors

func AngleBetweenTwoVectors(vector, referenceVector Vec2) float32

AngleBetweenTwoVectors returns the angle from referenceVector to vector, in radians. Matches QVector::AngleBetweenTwoVectors in qvector.cpp.

The C++ implementation computes:

refPerp   = referenceVector.Perpendicular()
dot       = vector · referenceVector
perpDot   = vector · refPerp        // = vector.X*refY - vector.Y*refX
totalLen  = vector.Length() + referenceVector.Length()
cosA      = dot / totalLen
sinA      = perpDot / totalLen
aSin      = asin(clamp(sinA, -1, 1))

AngleBetweenTwoVectors computes the signed angle from referenceVector to vector. Matches QVector::AngleBetweenTwoVectors in qvector.cpp:37-69 exactly.

C++ algorithm:

totalLength = vector.Length() + referenceVector.Length()
refPerp    = referenceVector.Perpendicular()
dot        = vector · referenceVector
perpDot    = vector · refPerp
cosA = totalLength != 0 ? dot/totalLength : 0
sinA = totalLength != 0 ? perpDot/totalLength : 0
aSin = clamp(asin(sinA), -1, 1)
return -atan2(aSin, cosA)

NOTE: The C++ formula divides by the SUM of lengths (not product), then applies asin. This is non-standard but is the reference behavior — it must be reproduced verbatim because AngleConstraint, polygon corner-angle tracking, and platformer slope detection all accumulate this value.

func AngleOfParticlesWithLocalPositions

func AngleOfParticlesWithLocalPositions(pA, pB, pC *Particle) float32

AngleOfParticlesWithLocalPositions computes the angle at pB formed by rays pB→pA and pB→pC, using LOCAL positions. Returns a value in [0, 2π).

func ApplyForceToParticleSegment

func ApplyForceToParticleSegment(pA, pB *Particle, force Vec2, fromPosition Vec2)

ApplyForceToParticleSegment distributes `force` across two particles forming a segment, weighted by where `fromPosition` projects onto the segment. Matches QParticle::ApplyForceToParticleSegment in qparticle.cpp:220-241.

Used by Manifold.Solve to apply collision response along a reference edge (2 particles) at the contact point.

func CanCollide

func CanCollide(bodyA, bodyB *Body, checkBodiesAreEnabled bool) bool

CanCollide reports whether two bodies can collide based on their state and layer bits.

func RegisterAreaBody

func RegisterAreaBody(b *Body, ab *AreaBody)

RegisterAreaBody associates a *Body with its *AreaBody container.

func RegisterPostUpdater

func RegisterPostUpdater(b *Body, fn func())

RegisterPostUpdater associates a *Body with a function that calls its PostUpdate. Called by ext/platformer when a PlatformerBody is added.

func RegisterRigidBody

func RegisterRigidBody(b *Body, rb *RigidBody)

RegisterRigidBody associates a *Body with its *RigidBody container. Called by World.AddBody when the body is a RigidBody.

func RegisterSoftBody

func RegisterSoftBody(b *Body, sb *SoftBody)

RegisterSoftBody associates a *Body with its *SoftBody container.

func SetConvexPartitioner

func SetConvexPartitioner(f func(polygon []*Particle) [][]*Particle)

SetConvexPartitioner registers the function used for concave polygon decomposition. Pass nil to disable (concave polygons will use the full polygon without decomposition, which may cause collision artifacts).

func SortParticlesHorizontal

func SortParticlesHorizontal(a, b *Particle) bool

SortParticlesHorizontal sorts particles by AABB min.X (ascending), breaking ties by AABB max.Y (descending). Matches the C++ comparator QParticle::SortParticlesHorizontal in qparticle.h:273-278.

Used by CircleAndCircle for sweep-and-prune pair generation.

Types

type AABB

type AABB struct {
	Min, Max Vec2
}

AABB is an axis-aligned bounding box, the cheap pre-collision filter.

Convention: Min is the top-left corner, Max is the bottom-right corner (Y axis points down, matching the rest of the engine).

func Combine

func Combine(b1, b2 AABB) AABB

Combine returns the smallest AABB containing both b1 and b2.

func GetAABBFromParticles

func GetAABBFromParticles(particles []*Particle) AABB

GetAABBFromParticles returns the bounding box of a set of particles, expanded by each particle's radius.

The particle slice is required because AABB is a value type and cannot reference particles directly. This is the main place where the Go port differs structurally from C++ (which used a vector<QParticle*>&).

func NewAABB

func NewAABB(min, max Vec2) AABB

NewAABB constructs an AABB from min and max corners.

func (AABB) Area

func (a AABB) Area() float32

Area returns the area (width × height).

func (AABB) CenterPosition

func (a AABB) CenterPosition() Vec2

CenterPosition returns the midpoint of the AABB.

func (AABB) Fatten

func (a AABB) Fatten(amount float32) AABB

Fatten returns a new AABB expanded by amount on all sides.

func (AABB) FattenedWithRate

func (a AABB) FattenedWithRate(rate float32) AABB

FattenedWithRate returns a new AABB expanded proportionally to its size. rate=0.1 expands by 5% on each side (10% total).

func (AABB) IsCollidingWith

func (a AABB) IsCollidingWith(other AABB) bool

IsCollidingWith reports whether a and other overlap.

func (AABB) IsContain

func (a AABB) IsContain(other AABB) bool

IsContain reports whether otherAABB is entirely contained within a.

func (AABB) Perimeter

func (a AABB) Perimeter() float32

Perimeter returns the perimeter of the AABB (2 * (w + h)).

func (AABB) SetMinMax

func (a AABB) SetMinMax(min, max Vec2) AABB

SetMinMax returns a new AABB with min and max replaced. Go the idiomatic approach is to construct a new AABB directly avoid this.

func (AABB) Size

func (a AABB) Size() Vec2

Size returns (Max - Min).

type AngleConstraint

type AngleConstraint struct {
	// contains filtered or unexported fields
}

AngleConstraint is a 3-particle angle constraint.

Constrains the angle at pB (between rays pB→pA and pB→pC) to stay within [minAngle, maxAngle]. When the angle exceeds the bounds, forces are applied to pA and pC to rotate them back toward the target.

Wrap-around handling: angles are tracked via prevAngle and updated by the angle difference (computed via AngleBetweenTwoVectors on unit vectors). This allows the constraint to track angles across the 0/2π boundary.

func NewAngleConstraint

func NewAngleConstraint(pA, pB, pC *Particle, angleRange float32) *AngleConstraint

NewAngleConstraint creates an angle constraint between three particles, auto-calculating min/max angles from the current local positions.

func NewAngleConstraintWithBounds

func NewAngleConstraintWithBounds(pA, pB, pC *Particle, minAngle, maxAngle float32) *AngleConstraint

NewAngleConstraintWithBounds creates an angle constraint with explicit min and max angles.

func (*AngleConstraint) CurrentAngle

func (ac *AngleConstraint) CurrentAngle() float32

func (*AngleConstraint) Enabled

func (ac *AngleConstraint) Enabled() bool

func (*AngleConstraint) MaxAngle

func (ac *AngleConstraint) MaxAngle() float32

func (*AngleConstraint) MinAngle

func (ac *AngleConstraint) MinAngle() float32

func (*AngleConstraint) ParticleA

func (ac *AngleConstraint) ParticleA() *Particle

func (*AngleConstraint) ParticleB

func (ac *AngleConstraint) ParticleB() *Particle

func (*AngleConstraint) ParticleC

func (ac *AngleConstraint) ParticleC() *Particle

func (*AngleConstraint) Rigidity

func (ac *AngleConstraint) Rigidity() float32

func (*AngleConstraint) SetEnabled

func (ac *AngleConstraint) SetEnabled(b bool) *AngleConstraint

func (*AngleConstraint) SetMaxAngle

func (ac *AngleConstraint) SetMaxAngle(v float32) *AngleConstraint

func (*AngleConstraint) SetMinAngle

func (ac *AngleConstraint) SetMinAngle(v float32) *AngleConstraint

func (*AngleConstraint) SetParticleA

func (ac *AngleConstraint) SetParticleA(p *Particle) *AngleConstraint

func (*AngleConstraint) SetParticleB

func (ac *AngleConstraint) SetParticleB(p *Particle) *AngleConstraint

func (*AngleConstraint) SetParticleC

func (ac *AngleConstraint) SetParticleC(p *Particle) *AngleConstraint

func (*AngleConstraint) SetRigidity

func (ac *AngleConstraint) SetRigidity(r float32) *AngleConstraint

func (*AngleConstraint) Update

func (ac *AngleConstraint) Update(specifiedRigidity float32, addToAccumulatedForces bool)

Update applies the angle constraint.

Parameters:

  • specifiedRigidity: override rigidity (-1.0 = use the constraint's own rigidity)
  • addToAccumulatedForces: if true, route forces through the accumulated-force pipeline

type AreaBody

type AreaBody struct {
	Body

	// Event listeners (function fields, replace std::function)
	OnCollisionEnter func(*AreaBody, *Body)
	OnCollisionExit  func(*AreaBody, *Body)

	// ComputeLinearForceListener returns a per-body linear force.
	// If set, overrides linearForceToApply.
	ComputeLinearForceListener func(*Body) Vec2
	// contains filtered or unexported fields
}

AreaBody is a sensor/trigger body.

Area bodies do NOT respond to collisions or receive forces — they only REPORT collisions. They maintain a set of currently-collided bodies and dispatch OnCollisionEnter / OnCollisionExit events when bodies enter or leave the area.

Supported features:

  • OnCollisionEnter/OnCollisionExit events (virtual + function field)
  • gravityFree mode (disables gravity on contained bodies)
  • linearForceToApply (continuous force applied to contained bodies)
  • ComputeLinearForce per-body callback (custom force per contained body)

func NewAreaBody

func NewAreaBody() *AreaBody

NewAreaBody constructs an AreaBody with default values.

func (*AreaBody) AsBody

func (ab *AreaBody) AsBody() *Body

AsBody returns a *Body pointer for this AreaBody.

func (*AreaBody) CheckBodies

func (ab *AreaBody) CheckBodies()

CheckBodies re-tests all currently-collided bodies and dispatches enter/exit events. Called once per physics step by World.Update.

func (*AreaBody) ComputeLinearForce

func (ab *AreaBody) ComputeLinearForce(body *Body) Vec2

ComputeLinearForce returns the force to apply to a specific body. If ComputeLinearForceListener is set, uses it; otherwise uses linearForceToApply.

func (*AreaBody) GetBodies

func (ab *AreaBody) GetBodies() []*Body

GetBodies returns the currently-collided bodies.

func (*AreaBody) GravityFreeEnabled

func (ab *AreaBody) GravityFreeEnabled() bool

GravityFreeEnabled reports whether the gravity-free zone is active.

func (*AreaBody) HasBody

func (ab *AreaBody) HasBody(b *Body) bool

HasBody reports whether a body is currently in the area.

func (*AreaBody) LinearForceToApply

func (ab *AreaBody) LinearForceToApply() Vec2

LinearForceToApply returns the continuous force applied to contained bodies.

func (*AreaBody) SetGravityFreeEnabled

func (ab *AreaBody) SetGravityFreeEnabled(b bool) *AreaBody

SetGravityFreeEnabled enables/disables the gravity-free zone. When enabled, contained bodies are exempt from gravity.

func (*AreaBody) SetLinearForceToApply

func (ab *AreaBody) SetLinearForceToApply(v Vec2) *AreaBody

SetLinearForceToApply sets a continuous force applied to contained bodies.

type Body

type Body struct {

	// Event listeners (function fields, replace std::function)
	OnPreStep   func(*Body)
	OnStep      func(*Body)
	OnCollision func(*Body, CollisionInfo) bool
	// contains filtered or unexported fields
}

Body is the base type for all physics bodies.

Body is abstract in the C++ engine (has virtual methods). In Go, we embed it as a struct field in RigidBody, SoftBody, and AreaBody. The BodyType field drives dispatch in World.Update via a switch (see D006 in DECISIONS.md).

Key concepts:

  • Verlet integration: velocity is implicit (position - prevPosition)
  • Meshes carry particles, which carry their own positions
  • The AABB is recomputed from particle positions
  • Sleeping bodies skip integration but still collide

func NewBody

func NewBody() *Body

func (*Body) AABB

func (b *Body) AABB() AABB

AABB returns the body's axis-aligned bounding box.

func (*Body) AddMesh

func (b *Body) AddMesh(m *Mesh) *Body

AddMesh attaches a mesh to the body. Matches QBody::AddMesh in qbody.cpp:154-162.

func (*Body) AddPosition

func (b *Body) AddPosition(v Vec2, withPreviousPosition ...bool) *Body

AddPosition adds a vector to the body's position.

func (*Body) AddPreviousPosition

func (b *Body) AddPreviousPosition(v Vec2) *Body

AddPreviousPosition adds a vector to the body's previous position.

func (*Body) AddPreviousRotation

func (b *Body) AddPreviousRotation(angleRadian float32) *Body

AddPreviousRotation adds to the body's previous rotation.

func (*Body) AddRotation

func (b *Body) AddRotation(angleRadian float32, withPreviousRotation ...bool) *Body

AddRotation adds to the body's rotation in radians.

func (*Body) AirFriction

func (b *Body) AirFriction() float32

AirFriction returns the body's air friction (drag) coefficient.

func (*Body) AllowKinematicCollisions

func (b *Body) AllowKinematicCollisions() bool

AllowKinematicCollisions reports whether this kinematic body reacts to collisions with other kinematic bodies.

func (*Body) ApplyForce

func (b *Body) ApplyForce(force Vec2) *Body

ApplyForce applies an immediate force to the body. The base implementation is a no-op; RigidBody and SoftBody override.

func (*Body) BodyType

func (b *Body) BodyType() BodyType

BodyType returns the body's type (rigid, soft, or area).

func (*Body) CanGiveCollisionResponseTo

func (b *Body) CanGiveCollisionResponseTo(other *Body) bool

CanGiveCollisionResponseTo reports whether this body should receive collision responses from otherBody. Matches QBody::CanGiveCollisionResponseTo.

func (*Body) CanSleep

func (b *Body) CanSleep() bool

CanSleep reports whether the body is allowed to sleep.

func (*Body) Circumference

func (b *Body) Circumference() float32

Circumference returns the total perimeter of all meshes' polygons. Caches the computed perimeter for the warm path.

func (*Body) CollidableLayersBit

func (b *Body) CollidableLayersBit() int

CollidableLayersBit returns the bitmask of layers this body can collide with.

func (*Body) CustomGravity

func (b *Body) CustomGravity() Vec2

CustomGravity returns the per-body gravity vector (if enabled).

func (*Body) CustomGravityEnabled

func (b *Body) CustomGravityEnabled() bool

CustomGravityEnabled reports whether a per-body gravity override is active.

func (*Body) Enabled

func (b *Body) Enabled() bool

Enabled reports whether the body is active.

func (*Body) Friction

func (b *Body) Friction() float32

Friction returns the body's dynamic friction coefficient.

func (*Body) IgnoreGravity

func (b *Body) IgnoreGravity() bool

IgnoreGravity reports whether the body is exempt from gravity. Set by QAreaBody when gravityFree is enabled.

func (*Body) Inertia

func (b *Body) Inertia() float32

Inertia returns the body's rotational inertia. Computed lazily. The clamped value is cached in b.inertiaCache so callers after the first compute see the same floor that C++ returns from its private `float inertia` field. Without this cache, small rigid bodies (e.g. 10×10 with mass 1, area*2*mass ≈ 200 < 500) would compute ~2.5x larger torque on every ApplyForce/ApplyImpulse call after the first.

func (*Body) IntegratedVelocitiesEnabled

func (b *Body) IntegratedVelocitiesEnabled() bool

IntegratedVelocitiesEnabled reports whether Verlet integration is active.

func (*Body) IsKinematic

func (b *Body) IsKinematic() bool

IsKinematic reports whether the body is kinematic (user-controlled, not affected by forces).

func (*Body) IsSleeping

func (b *Body) IsSleeping() bool

IsSleeping reports whether the body is currently sleeping.

func (*Body) LayersBit

func (b *Body) LayersBit() int

LayersBit returns the bitmask of layers this body is on.

func (*Body) Mass

func (b *Body) Mass() float32

Mass returns the body's mass.

func (*Body) MeshAt

func (b *Body) MeshAt(i int) *Mesh

MeshAt returns the mesh at the given index.

func (*Body) MeshCount

func (b *Body) MeshCount() int

MeshCount returns the number of meshes.

func (*Body) Meshes

func (b *Body) Meshes() []*Mesh

Meshes returns the body's meshes.

func (*Body) Mode

func (b *Body) Mode() BodyMode

Mode returns whether the body is dynamic or static.

func (*Body) OverlapWithCollidableLayersBit

func (b *Body) OverlapWithCollidableLayersBit(layersBit int) bool

OverlapWithCollidableLayersBit reports whether this body can collide with bodies on the given layers bitmask.

func (*Body) OverlapWithLayersBit

func (b *Body) OverlapWithLayersBit(layersBit int) bool

OverlapWithLayersBit reports whether this body is on any of the given layers.

func (*Body) Position

func (b *Body) Position() Vec2

Position returns the body's world-space position.

func (*Body) PostUpdate

func (b *Body) PostUpdate()

PostUpdate is called after all bodies have completed their Update step. Base implementation is a no-op; RigidBody and PlatformerBody override.

func (*Body) PreviousPosition

func (b *Body) PreviousPosition() Vec2

PreviousPosition returns the body's previous position (Verlet velocity source).

func (*Body) PreviousRotation

func (b *Body) PreviousRotation() float32

PreviousRotation returns the body's previous rotation.

func (*Body) RemoveMeshAt

func (b *Body) RemoveMeshAt(i int) *Body

RemoveMeshAt removes the mesh at the given index.

func (*Body) Restitution

func (b *Body) Restitution() float32

Restitution returns the body's restitution (bounciness).

func (*Body) Rotation

func (b *Body) Rotation() float32

Rotation returns the body's rotation in radians.

func (*Body) RotationDegree

func (b *Body) RotationDegree() float32

RotationDegree returns the body's rotation in degrees.

func (*Body) SetAirFriction

func (b *Body) SetAirFriction(v float32) *Body

SetAirFriction sets the air friction (drag) coefficient.

func (*Body) SetAllowKinematicCollisions

func (b *Body) SetAllowKinematicCollisions(v bool) *Body

SetAllowKinematicCollisions controls kinematic-kinematic collision response.

func (*Body) SetBodySpecificTimeScale

func (b *Body) SetBodySpecificTimeScale(value float32) *Body

SetBodySpecificTimeScale sets a per-body time scale. When the value changes AND body-specific time scale is enabled, the body's implicit velocity (position - prevPosition, and per-particle for soft bodies) is rescaled to preserve continuity across the time-scale change. Matches qbody.h:579-615.

velocityTimeScaleFactor logic (C++ qbody.h:583-590):

  • If old scale == 0: factor = 0 (no rescale; old was frozen)
  • If new < old: factor = (1/old) * new (slow down further)
  • If new >= old: factor = 1.0 (no slow-down needed)

For RIGID bodies: rescale (position - prevPosition) and (rotation - prevRotation). For SOFT bodies: rescale each particle's (globalPosition - prevGlobalPosition). For AREA bodies: no rescale (they don't integrate).

func (*Body) SetBodySpecificTimeScaleEnabled

func (b *Body) SetBodySpecificTimeScaleEnabled(v bool) *Body

SetBodySpecificTimeScaleEnabled toggles whether the body uses its own time scale instead of the world's. Matches qbody.h:570-573.

func (*Body) SetCanSleep

func (b *Body) SetCanSleep(v bool) *Body

SetCanSleep controls whether the body is allowed to sleep.

func (*Body) SetCollidableLayersBit

func (b *Body) SetCollidableLayersBit(v int) *Body

SetCollidableLayersBit sets the bitmask of layers this body can collide with.

func (*Body) SetCustomGravity

func (b *Body) SetCustomGravity(v Vec2) *Body

SetCustomGravity sets the per-body gravity vector.

func (*Body) SetCustomGravityEnabled

func (b *Body) SetCustomGravityEnabled(v bool) *Body

SetCustomGravityEnabled controls whether a per-body gravity override is active.

func (*Body) SetEnabled

func (b *Body) SetEnabled(v bool) *Body

SetEnabled enables or disables the body.

func (*Body) SetFriction

func (b *Body) SetFriction(v float32) *Body

SetFriction sets the dynamic friction coefficient.

func (*Body) SetIntegratedVelocitiesEnabled

func (b *Body) SetIntegratedVelocitiesEnabled(v bool) *Body

SetIntegratedVelocitiesEnabled controls whether Verlet integration runs.

func (*Body) SetKinematic

func (b *Body) SetKinematic(v bool) *Body

SetKinematic controls whether the body is kinematic. (Defined on RigidBody in C++; we expose it on Body for convenience.)

func (*Body) SetLayersBit

func (b *Body) SetLayersBit(v int) *Body

SetLayersBit sets the bitmask of layers this body is on.

func (*Body) SetMass

func (b *Body) SetMass(v float32) *Body

SetMass sets the body's mass.

func (*Body) SetMode

func (b *Body) SetMode(m BodyMode) *Body

SetMode sets the body to dynamic or static.

func (*Body) SetPosition

func (b *Body) SetPosition(v Vec2, withPreviousPosition ...bool) *Body

SetPosition sets the body's world-space position. If withPreviousPosition is true (the default), prevPosition is also set, zeroing the implicit velocity.

func (*Body) SetPreviousPosition

func (b *Body) SetPreviousPosition(v Vec2) *Body

SetPreviousPosition sets the body's previous position (Verlet velocity source).

func (*Body) SetPreviousRotation

func (b *Body) SetPreviousRotation(angleRadian float32) *Body

SetPreviousRotation sets the body's previous rotation.

func (*Body) SetRestitution

func (b *Body) SetRestitution(v float32) *Body

SetRestitution sets the body's restitution (bounciness).

func (*Body) SetRotation

func (b *Body) SetRotation(angleRadian float32, withPreviousRotation ...bool) *Body

SetRotation sets the body's rotation in radians.

func (*Body) SetRotationDegree

func (b *Body) SetRotationDegree(degree float32, withPreviousRotation ...bool) *Body

SetRotationDegree sets the body's rotation in degrees.

func (*Body) SetStaticFriction

func (b *Body) SetStaticFriction(v float32) *Body

SetStaticFriction sets the static friction coefficient.

func (*Body) SetVelocityLimit

func (b *Body) SetVelocityLimit(v float32) *Body

SetVelocityLimit sets the maximum velocity (0 = unlimited).

func (*Body) StaticFriction

func (b *Body) StaticFriction() float32

StaticFriction returns the body's static friction coefficient.

func (*Body) TotalInitialArea

func (b *Body) TotalInitialArea() float32

TotalInitialArea returns the sum of all meshes' initial areas.

func (*Body) TotalPolygonsArea

func (b *Body) TotalPolygonsArea() float32

TotalPolygonsArea returns the sum of all meshes' current polygon areas.

func (*Body) TotalPolygonsInitialArea

func (b *Body) TotalPolygonsInitialArea() float32

TotalPolygonsInitialArea returns the sum of all meshes' initial polygon areas.

func (*Body) Update

func (b *Body) Update()

Update is the per-step integration hook. The base implementation just resets lazy collisions; RigidBody and SoftBody override.

func (*Body) UpdateAABB

func (b *Body) UpdateAABB()

UpdateAABB recomputes the body's AABB from all particle positions.

func (*Body) UpdateMeshTransforms

func (b *Body) UpdateMeshTransforms()

UpdateMeshTransforms applies the body's position and rotation to all mesh particles. Matches QBody::UpdateMeshTransforms in qbody.cpp:227-251.

Critical: the prevGlobalPosition update differs by body type:

  • RIGID: prev = current globalPosition (preserves velocity direction)
  • SOFT/AREA: prev = new computed position (zeroes velocity for that step)

This is the Verlet velocity mechanism for particles.

func (*Body) VelocityLimit

func (b *Body) VelocityLimit() float32

VelocityLimit returns the maximum velocity; 0 means unlimited.

func (*Body) WakeUp

func (b *Body) WakeUp() *Body

WakeUp un-sleeps the body. Matches QBody::WakeUp in qbody.h:679-682.

func (*Body) World

func (b *Body) World() *World

World returns the world this body belongs to, or nil if not added.

type BodyMode

type BodyMode int

BodyMode determines whether a body reacts to forces and collisions.

const (
	// Reacts to forces, constraints, and collisions.
	BodyModeDynamic BodyMode = iota

	// Does not react; it provides collision surfaces for dynamic bodies.
	BodyModeStatic
)

type BodyPair

type BodyPair struct {
	A, B *Body
}

BodyPair represents an unordered pair of bodies. Used by broadphase to report candidate collision pairs.

func (BodyPair) Canonicalize

func (p BodyPair) Canonicalize() BodyPair

Canonicalize returns the pair in canonical order (by index in the world's bodies slice, which is stable). Used for deduplication in broadphase.

func (BodyPair) IsSelf

func (p BodyPair) IsSelf() bool

IsSelf reports whether the pair is a body with itself.

type BodyType

type BodyType int
const (
	// Non-deformable solid body simulated with Verlet integration.
	BodyTypeRigid BodyType = iota

	// Sensor/trigger body that reports collisions but doesn't respond.
	BodyTypeArea

	// Deformable body using mass-spring model with PBD.
	BodyTypeSoft
)

type BroadPhase

type BroadPhase interface {
	// Insert adds a body to the broadphase index.
	Insert(b *Body)

	// Remove removes a body from the broadphase index.
	Remove(b *Body)

	// Clear removes all bodies from the index.
	Clear()

	// Pairs returns the current set of candidate collision pairs.
	// Called once per solver iteration. The returned slice is owned by
	// the caller (the broadphase may reuse its internal buffer on the
	// next call).
	Pairs() []BodyPair
}

BroadPhase is the interface for broadphase collision pair generation. Implementations reduce O(n²) body pair checks to near-linear by partitioning bodies spatially.

The default implementation is Sweep-and-Prune (sapPairs in broadphase_internal.go). QSpatialHashing (ext/spatialhash) is an alternative. Users can provide custom implementations via World.SetBroadphase.

type CollisionBehavior

type CollisionBehavior int

CollisionBehavior enumerates how a mesh participates in collision detection.

const (
	// CollisionCircles treats particles as circles. Used when no polygon
	// is defined (e.g., a single-particle circle mesh).
	CollisionCircles CollisionBehavior = iota

	// CollisionPolygons treats the polygon as a solid convex/concave shape.
	// Used by rigid bodies.
	CollisionPolygons

	// CollisionPolyline treats the polygon as a deformable rope. Used by
	// soft bodies, where the polygon may self-intersect.
	CollisionPolyline
)

type CollisionInfo

type CollisionInfo struct {
	// Position is the world-space contact position.
	Position Vec2

	// Body is the other body involved in the collision.
	Body *Body

	// Normal is the collision normal.
	Normal Vec2

	// Penetration is the overlap depth.
	Penetration float32
}

CollisionInfo is passed to OnCollision event listeners.

type ConcurrencyConfig

type ConcurrencyConfig struct {
	// Enabled controls whether parallel narrowphase is active.
	// Default: false (single-threaded, matching C++ original engine).
	Enabled bool

	// NumWorkers is the number of goroutines to use for parallel narrowphase.
	// If 0, defaults to runtime.NumCPU().
	NumWorkers int
}

ConcurrencyConfig controls parallel narrowphase execution.

type Contact

type Contact struct {
	// Position is the world-space contact point.
	Position Vec2

	// Particle is the incident particle involved in the collision.
	// May be nil for ray-vs-polygon contacts that don't map to a particle.
	Particle *Particle

	// Normal is the contact normal, pointing from bodyB toward bodyA.
	Normal Vec2

	// Penetration is how far the two shapes overlap along Normal.
	Penetration float32

	// ReferenceParticles holds the 1-2 particles forming the reference
	// edge. Used by Manifold.Solve to distribute response forces along
	// the contact segment (barycentric weighting).
	ReferenceParticles []*Particle

	// Solved tracks whether this contact has already been processed by
	// Manifold.Solve, to avoid double-application within an iteration.
	Solved bool
}

Contact is the data structure produced by collision detection and consumed by collision resolution (Manifold). Matches QCollision::Contact in qcollision.h:53-91.

One Contact represents a single point where two bodies touch: the position in world space, the incident particle, the contact normal (pointing from bodyB toward bodyA), the penetration depth, and the reference-face particles used by the friction solver.

Contacts are pooled via ContactPool to avoid GC pressure — they are allocated and freed many times per physics step.

func GetCollisions

func GetCollisions(bodyA, bodyB *Body, pool *ContactPool, applyHotSolvers bool) []*Contact

GetCollisions runs narrowphase collision detection between two bodies and returns a list of contacts.

Dispatches by the collision behavior of each body's meshes:

  • Polygons × Polygons → PolygonAndPolygon
  • Circles × Polygons → CircleAndPolygon
  • Circles × Circles → CircleAndCircle
  • Polyline × Polygons → CircleAndPolygon (hot-solved if applyHotSolvers)
  • PolylineAndPolygon

When applyHotSolvers is true AND the body pair is Polyline×Polygons, the CircleAndPolygon contacts are immediately solved via a temp Manifold (hot-solving) BEFORE PolylineAndPolygon runs. This matches qworld.cpp:1017-1025. Hot-solving mutates body state during narrowphase, so it MUST NOT be used when parallel narrowphase is enabled — the caller is responsible for passing applyHotSolvers=false in that mode.

func (*Contact) Configure

func (c *Contact) Configure(
	particle *Particle,
	position Vec2,
	normal Vec2,
	penetration float32,
	referenceParticles []*Particle,
)

Configure resets an existing Contact with new values. Used by the ContactPool to recycle Contact objects without allocation. Matches QCollision::Contact::Configure in qcollision.h:80-87.

type ContactPool

type ContactPool struct {
	// contains filtered or unexported fields
}

ContactPool recycles *Contact objects to avoid GC pressure during collision solving. Contacts are created and discarded many times per physics step (once per candidate pair, per iteration), so pooling is essential for performance.

In the C++ engine (qcollision.h:93) this is a global static QObjectPool<Contact> shared across all QWorld instances — a porting hazard for concurrency (analysis doc §8.2 R4). In this Go port each World owns its own ContactPool, which:

  1. Enables future per-World concurrency (Phase 5)
  2. Avoids global state contamination between independent worlds
  3. Has minimal overhead — sync.Pool is optimized for this access pattern

Reference: analysis doc §7.5, §8.2 R4

func NewContactPool

func NewContactPool() *ContactPool

NewContactPool creates an empty pool. The pool lazily allocates Contact objects on first Get and recycles them on Put.

func (*ContactPool) Get

func (p *ContactPool) Get() *Contact

Get returns a *Contact from the pool, allocating if necessary. The returned Contact has all fields zeroed (matches the C++ contactPool FreeAll + Create pattern at qworld.cpp:123).

func (*ContactPool) Put

func (p *ContactPool) Put(c *Contact)

Put returns a *Contact to the pool for reuse. The Contact's fields are cleared to avoid holding stale references (which would prevent GC of the referenced Particle/Body objects).

type Gizmo

type Gizmo struct {
	Type GizmoType
	// Circle fields
	Position Vec2
	Radius   float32
	// Line fields
	PointA  Vec2
	PointB  Vec2
	IsArrow bool
	// Rect field
	Rect AABB
}

Gizmo is a debug visualization primitive. Matches QGizmo in qgizmos.h.

Gizmos are recorded by the engine during collision solving when World.DebugGizmos() is true. Renderers (e.g., the Ebitengine example renderer) consume them for visual debugging.

func NewGizmoCircle

func NewGizmoCircle(pos Vec2, radius float32) *Gizmo

NewGizmoCircle creates a circle gizmo.

func NewGizmoLine

func NewGizmoLine(from, to Vec2, arrow bool) *Gizmo

NewGizmoLine creates a line gizmo.

func NewGizmoRect

func NewGizmoRect(rect AABB) *Gizmo

NewGizmoRect creates a rectangle gizmo.

type GizmoType

type GizmoType int

GizmoType enumerates gizmo kinds.

const (
	GizmoCircle GizmoType = iota
	GizmoLine
	GizmoRectangle
)

type Joint

type Joint struct {
	// contains filtered or unexported fields
}

Joint is a distance constraint between two rigid bodies. Matches QJoint in qjoint.h, qjoint.cpp.

Either bodyA or bodyB may be nil, in which case the corresponding anchor is treated as a fixed point in world space.

The joint enforces a target distance (`length`) between the two anchor points. When the current distance differs from the target, forces are applied to both bodies to close the gap. The `balance` parameter (0.0=A-side, 1.0=B-side, default 0.5) controls the force distribution.

Groove mode (`grooveEnabled=true`) makes the joint pull-only: it only enforces the constraint when the current distance exceeds the target (useful for "rope" or "tether" constraints).

func NewJoint

func NewJoint(bodyA *RigidBody, anchorWorldPositionA, anchorWorldPositionB Vec2, bodyB *RigidBody) *Joint

NewJoint creates a joint between two bodies. If bodyB is nil, anchorB is treated as a fixed point in world space. Same for bodyA.

func NewPinJoint

func NewPinJoint(bodyA *RigidBody, commonAnchor Vec2, bodyB *RigidBody) *Joint

NewPinJoint creates a joint with zero length (pin joint).

func (*Joint) AnchorAGlobalPosition

func (j *Joint) AnchorAGlobalPosition() Vec2

func (*Joint) AnchorAPosition

func (j *Joint) AnchorAPosition() Vec2

func (*Joint) AnchorBGlobalPosition

func (j *Joint) AnchorBGlobalPosition() Vec2

func (*Joint) AnchorBPosition

func (j *Joint) AnchorBPosition() Vec2

func (*Joint) Balance

func (j *Joint) Balance() float32

func (*Joint) BodyA

func (j *Joint) BodyA() *RigidBody

func (*Joint) BodyB

func (j *Joint) BodyB() *RigidBody

func (*Joint) CollisionEnabled

func (j *Joint) CollisionEnabled() bool

func (*Joint) Enabled

func (j *Joint) Enabled() bool

func (*Joint) GrooveEnabled

func (j *Joint) GrooveEnabled() bool

func (*Joint) Length

func (j *Joint) Length() float32

func (*Joint) Rigidity

func (j *Joint) Rigidity() float32

func (*Joint) SetAnchorAPosition

func (j *Joint) SetAnchorAPosition(worldPosition Vec2) *Joint

SetAnchorAPosition sets anchorA in world coordinates; internally stored as body-local if bodyA is set. Matches qjoint.h:154-161.

func (*Joint) SetAnchorBPosition

func (j *Joint) SetAnchorBPosition(worldPosition Vec2) *Joint

SetAnchorBPosition sets anchorB in world coordinates.

func (*Joint) SetBalance

func (j *Joint) SetBalance(b float32) *Joint

func (*Joint) SetBodyA

func (j *Joint) SetBodyA(b *RigidBody) *Joint

func (*Joint) SetBodyB

func (j *Joint) SetBodyB(b *RigidBody) *Joint

func (*Joint) SetCollisionEnabled

func (j *Joint) SetCollisionEnabled(b bool) *Joint

SetCollisionEnabled controls whether the jointed bodies collide. When false (default), a collision exception is registered so the bodies pass through each other. Matches QJoint::SetCollisionEnabled in qjoint.cpp:72-82.

func (*Joint) SetEnabled

func (j *Joint) SetEnabled(b bool) *Joint

func (*Joint) SetGrooveEnabled

func (j *Joint) SetGrooveEnabled(b bool) *Joint

func (*Joint) SetLength

func (j *Joint) SetLength(l float32) *Joint

func (*Joint) SetRigidity

func (j *Joint) SetRigidity(r float32) *Joint

func (*Joint) Update

func (j *Joint) Update()

Update solves the joint constraint.

For each step:

  1. Transform anchors from body-local to world-space
  2. Compute the current distance between anchors
  3. Compute the distance delta (target - current)
  4. Apply forces to both bodies proportional to the delta and rigidity
  5. Distribute forces based on `balance` (or mass/static status)

type Manifold

type Manifold struct {
	// contains filtered or unexported fields
}

Manifold holds collision data between two bodies and resolves it.

Key C++ conventions:

  • bodyA/bodyB are ordered by pointer (smaller first) — NOT reference/incident
  • referenceBody = owner of contact.referenceParticles (the polygon that was hit)
  • incidentBody = owner of contact.particle (the penetrating particle)
  • normal = points from referenceBody toward incidentBody
  • refResponseForce = -responseForce (applied to referenceBody via ApplyForce/ApplyForceToParticleSegment)
  • incResponseForce = +responseForce (applied to incidentBody via ApplyForce/ApplyForceAt)
  • invMass = 1 / (bodyA.mass + bodyB.mass) — uses RAW masses, no zeroing for static
  • isCollisionOneSide = true if either body can't give response (includes static!)

func (*Manifold) BodyA

func (m *Manifold) BodyA() *Body

func (*Manifold) BodyB

func (m *Manifold) BodyB() *Body

func (*Manifold) Contacts

func (m *Manifold) Contacts() []*Contact

func (*Manifold) Solve

func (m *Manifold) Solve()

Solve applies position correction to resolve overlaps.

func (*Manifold) SolveFrictionAndVelocities

func (m *Manifold) SolveFrictionAndVelocities()

SolveFrictionAndVelocities applies restitution and friction.

type Mesh

type Mesh struct {
	// contains filtered or unexported fields
}

Mesh is the shape + topology container for a body.

A Mesh carries:

  • Particles (the smallest building blocks; positioned by the body)
  • Polygon (the subset of particles forming the collision boundary)
  • Springs (distance constraints between particles — used by soft bodies)
  • AngleConstraints (3-particle angle limits — used by soft bodies)
  • SubConvexPolygons (concave polygon decomposed into convex pieces for SAT)
  • PolygonBisectors (cached bisector vectors for collision response)
  • UVMaps (triangle index lists for rendering)

The CollisionBehavior field determines which collision algorithm runs:

  • CIRCLES — particles treated as circles (no polygon)
  • POLYGONS — rigid body with a polygon (uses SAT + clipping)
  • POLYLINE — soft body with a polygon (treated as a deformable rope)

func NewCircleMesh

func NewCircleMesh(radius float32, centerPosition Vec2) *Mesh

NewCircleMesh creates a single-particle mesh representing a circle.

func NewMesh

func NewMesh() *Mesh

NewMesh creates an empty mesh.

func NewMeshFromData

func NewMeshFromData(data MeshData, enableSprings, enablePolygons bool) *Mesh

NewMeshFromData constructs a mesh from a MeshData struct. This is the universal factory — all other factories (rect, polygon) build a MeshData and delegate here.

func NewPolygonMesh

func NewPolygonMesh(radius float32, sideCount int, centerPosition Vec2, polarGrid int, opts ...MeshFactoryOption) *Mesh

NewPolygonMesh creates a regular N-gon mesh. If polarGrid > 0, generates concentric rings of internal particles connected by springs.

func NewRectMesh

func NewRectMesh(size, centerPosition, grid Vec2, opts ...MeshFactoryOption) *Mesh

NewRectMesh creates a 4-corner rectangle mesh. If grid is non-zero, generates an internal grid of particles with cross-diagonal springs (used for soft bodies). For rigid bodies, pass Vec2Zero for grid.

func (*Mesh) AddAngleConstraint

func (m *Mesh) AddAngleConstraint(ac *AngleConstraint) *Mesh

AddAngleConstraint attaches an angle constraint to the mesh.

func (*Mesh) AddParticle

func (m *Mesh) AddParticle(p *Particle) *Mesh

AddParticle appends a particle to the mesh and sets its owner.

func (*Mesh) AddSpring

func (m *Mesh) AddSpring(s *Spring) *Mesh

AddSpring attaches a spring to the mesh.

func (*Mesh) AngleConstraints

func (m *Mesh) AngleConstraints() []*AngleConstraint

AngleConstraints returns the angle constraints owned by this mesh.

func (*Mesh) ApplyAngleConstraintsToPolygon

func (m *Mesh) ApplyAngleConstraintsToPolygon()

ApplyAngleConstraintsToPolygon applies per-vertex angle constraints to the polygon.

Algorithm:

  1. Intersection test: check if the polygon is self-intersecting via pairwise segment intersection. If so, apply a shape-matching fallback (pull particles toward the rest shape with force factor 0.2), clear lastPolygonCornerAngles, and return.
  2. First-frame skip: if lastPolygonCornerAngles size doesn't match polygon size, initialize to zeros and set beginToSaveAngles=true. On the first frame, just save angles without applying constraints.
  3. Angle tracking with unwrap: compute the raw atan2 angle for each vertex, then compute angleDifference = AngleBetweenTwoVectors( AngleToUnitVector(angleRad), AngleToUnitVector(lastSaved)). The unwrapped angle is lastSaved + angleDifference. This prevents wrap-around jumps at ±π.
  4. Position-based correction: if angle > maxAngle or < minAngle, directly SetGlobalPosition on the neighbors (NOT ApplyForce). Force factor 0.5. Check pp.Enabled() and np.Enabled() (NOT p.Enabled()).

func (*Mesh) Area

func (m *Mesh) Area() float32

Area returns the area computed from global particle positions.

func (*Mesh) Circumference

func (m *Mesh) Circumference() float32

Circumference returns the total perimeter of the polygon (using local particle positions).

func (*Mesh) CollisionBehavior

func (m *Mesh) CollisionBehavior() CollisionBehavior

CollisionBehavior returns the collision behavior, computing it lazily if a recomputation is pending.

func (*Mesh) GlobalPosition

func (m *Mesh) GlobalPosition() Vec2

GlobalPosition returns the mesh's world-space position.

func (*Mesh) GlobalRotation

func (m *Mesh) GlobalRotation() float32

GlobalRotation returns the mesh's world-space rotation.

func (*Mesh) InitialArea

func (m *Mesh) InitialArea() float32

InitialArea returns the area computed from local particle positions, including both polygon area and circle areas (for particles with r > 0.5).

func (*Mesh) OwnerBody

func (m *Mesh) OwnerBody() *Body

OwnerBody returns the body that owns this mesh.

func (*Mesh) ParticleAt

func (m *Mesh) ParticleAt(i int) *Particle

ParticleAt returns the particle at the given index.

func (*Mesh) ParticleCount

func (m *Mesh) ParticleCount() int

ParticleCount returns the number of particles in the mesh.

func (*Mesh) ParticleIndex

func (m *Mesh) ParticleIndex(p *Particle) int

ParticleIndex returns the index of the given particle, or -1 if not found.

func (*Mesh) Particles

func (m *Mesh) Particles() []*Particle

Particles returns the slice of particles owned by this mesh.

func (*Mesh) Polygon

func (m *Mesh) Polygon() []*Particle

Polygon returns the particles forming the collision boundary.

func (*Mesh) PolygonArea

func (m *Mesh) PolygonArea() float32

PolygonArea returns the polygon area (local or global).

func (*Mesh) Position

func (m *Mesh) Position() Vec2

Position returns the mesh's local position (relative to the owning body).

func (*Mesh) RemoveParticle

func (m *Mesh) RemoveParticle(p *Particle) *Mesh

RemoveParticle removes the given particle from the mesh.

func (*Mesh) RemoveParticleAt

func (m *Mesh) RemoveParticleAt(i int) *Mesh

RemoveParticleAt removes the particle at the given index and cascades the removal to polygon, springs, UV maps, and angle constraints.

C++ calls RemoveParticleFromPolygon, RemoveMatchingSprings, RemoveMatchingUVMaps, RemoveMatchingAngleConstraints before erasing the particle from the vector. Then sets dirty flags (collisionBehaviorNeedsUpdate, polygonBisectorsNeedsUpdate, inertiaNeedsUpdate, circumferenceNeedsUpdate) and updates static body transforms if applicable.

func (*Mesh) Rotation

func (m *Mesh) Rotation() float32

Rotation returns the mesh's local rotation (radians).

func (*Mesh) SetGlobalPosition

func (m *Mesh) SetGlobalPosition(v Vec2) *Mesh

SetGlobalPosition sets the mesh's world-space position.

func (*Mesh) SetPolygonForCollisionsDisabled

func (m *Mesh) SetPolygonForCollisionsDisabled(b bool) *Mesh

SetPolygonForCollisionsDisabled disables the polygon for collisions, forcing the mesh to use circle-based collision on its particles.

func (*Mesh) SetPosition

func (m *Mesh) SetPosition(v Vec2) *Mesh

SetPosition sets the mesh's local position.

func (*Mesh) SetRotation

func (m *Mesh) SetRotation(r float32) *Mesh

SetRotation sets the mesh's local rotation.

func (*Mesh) SpringCount

func (m *Mesh) SpringCount() int

SpringCount returns the number of springs.

func (*Mesh) Springs

func (m *Mesh) Springs() []*Spring

Springs returns the springs owned by this mesh.

func (*Mesh) SubConvexPolygons

func (m *Mesh) SubConvexPolygons() [][]*Particle

SubConvexPolygons returns the cached convex decomposition.

func (*Mesh) UpdateCollisionBehavior

func (m *Mesh) UpdateCollisionBehavior()

UpdateCollisionBehavior recomputes the collision behavior based on the owning body type and polygon presence. Matches QMesh::UpdateCollisionBehavior.

func (*Mesh) UpdateSubConvexPolygons

func (m *Mesh) UpdateSubConvexPolygons(majorUpdate bool)

UpdateSubConvexPolygons recomputes the convex decomposition of the mesh's polygon. If the polygon is convex, the decomposition is just the polygon itself. If concave, it's decomposed via polypartition.

NOTE: The C++ engine uses the vendored polypartition library directly. The Go port uses the mesh/polypartition sub-package, but to avoid a circular import (polypartition imports physics, physics can't import polypartition), the decomposition is performed by an external function set via SetConvexPartitioner. The World sets this up at initialization.

type MeshData

type MeshData struct {
	ParticlePositions      []Vec2
	ParticleRadValues      []float32
	ParticleInternalValues []bool
	ParticleEnabledValues  []bool
	ParticleLazyValues     []bool
	SpringList             [][2]int
	InternalSpringList     [][2]int
	Polygon                []int
	UVMaps                 [][]int
	Position               Vec2
	Rotation               float32
}

MeshData is the serializable description of a mesh. Used as input to CreateWithMeshData and as the on-disk .qmesh format (Phase 3). Matches QMesh::MeshData in qmesh.h:104-145.

func GeneratePolygonMeshData

func GeneratePolygonMeshData(radius float32, sideCount int, centerPosition Vec2, polarGrid int, particleRadius float32) MeshData

GeneratePolygonMeshData produces a MeshData for a regular N-gon of the given radius and side count. If polarGrid > 0, generates concentric rings of internal particles.

func GenerateRectangleMeshData

func GenerateRectangleMeshData(size, centerPosition, grid Vec2, particleRadius float32) MeshData

GenerateRectangleMeshData produces a MeshData for a rectangle of the given size, centered at centerPosition. If grid.X or grid.Y > 1, an internal grid of particles is generated with cross-diagonal springs.

type MeshFactoryConfig

type MeshFactoryConfig struct {
	// contains filtered or unexported fields
}

MeshFactoryConfig holds optional parameters for mesh factory methods.

type MeshFactoryOption

type MeshFactoryOption func(*MeshFactoryConfig)

MeshFactoryOption configures a mesh factory.

func WithParticleRadius

func WithParticleRadius(r float32) MeshFactoryOption

WithParticleRadius sets the particle radius used by mesh factories.

func WithPolygons

func WithPolygons(b bool) MeshFactoryOption

WithPolygons enables/disables polygon generation in mesh factories.

func WithSprings

func WithSprings(b bool) MeshFactoryOption

WithSprings enables/disables spring generation in mesh factories.

type Particle

type Particle struct {
	// contains filtered or unexported fields
}

Particle is the smallest building block of a physics mesh.

In rigid bodies, particles are positioned collectively via body transformations (see Body.UpdateMeshTransforms). In soft bodies, particles move individually via Verlet integration and are connected by springs.

Velocities are implicit: a particle's velocity is (globalPosition - prevGlobalPosition) per step.

func NewParticle

func NewParticle(posX, posY, radius float32) *Particle

NewParticle constructs a Particle at local position (posX, posY) with the given radius. Matches QParticle(float posX, float posY, float radius).

func NewParticleFromVec

func NewParticleFromVec(pos Vec2, radius float32) *Particle

NewParticleFromVec constructs a Particle at a Vec2 position.

func (*Particle) AABB

func (p *Particle) AABB() AABB

AABB returns the particle's axis-aligned bounding box (lazily computed).

func (*Particle) AddAccumulatedForce

func (p *Particle) AddAccumulatedForce(v Vec2) *Particle

AddAccumulatedForce appends a force to the accumulated list. The accumulated forces are averaged and applied via ApplyAccumulatedForces. Used by spring solvers to prevent iteration-order bias.

func (*Particle) AddForce

func (p *Particle) AddForce(v Vec2) *Particle

AddForce adds to the particle's queued force.

func (*Particle) AddGlobalPosition

func (p *Particle) AddGlobalPosition(v Vec2) *Particle

AddGlobalPosition adds a vector to the particle's world-space position.

func (*Particle) AddPosition

func (p *Particle) AddPosition(v Vec2) *Particle

AddPosition adds a vector to the particle's local position.

func (*Particle) AddPreviousGlobalPosition

func (p *Particle) AddPreviousGlobalPosition(v Vec2) *Particle

AddPreviousGlobalPosition adds a vector to the particle's previous position.

func (*Particle) ApplyAccumulatedForces

func (p *Particle) ApplyAccumulatedForces() *Particle

ApplyAccumulatedForces computes the arithmetic mean of accumulated forces and applies it via ApplyForce, then clears the list. Matches QParticle::ApplyAccumulatedForces in qparticle.cpp:201-213.

func (*Particle) ApplyForce

func (p *Particle) ApplyForce(force Vec2) *Particle

ApplyForce applies an immediate force to the particle by translating its global position. Matches QParticle::ApplyForce in qparticle.cpp:168-175.

Safe to call before the physics step (e.g., in OnPreStep). Calling after the step may break the simulation — use SetForce/AddForce for next-step-safe force application.

func (*Particle) ClearAccumulatedForces

func (p *Particle) ClearAccumulatedForces() *Particle

ClearAccumulatedForces empties the accumulated forces list.

func (*Particle) ClearOneTimeCollisions

func (p *Particle) ClearOneTimeCollisions()

ClearOneTimeCollisions empties both the current and previous one-time collision sets. Matches QParticle::ClearOneTimeCollisions.

func (*Particle) Enabled

func (p *Particle) Enabled() bool

Enabled reports whether the particle is active. Disabled particles still get collision-tested but their manifolds are not solved, and their force/velocity integrations are skipped.

func (*Particle) Force

func (p *Particle) Force() Vec2

Force returns the particle's currently-queued force (applied next step).

func (*Particle) GlobalPosition

func (p *Particle) GlobalPosition() Vec2

GlobalPosition returns the particle's world-space position.

func (*Particle) IgnoreGravity

func (p *Particle) IgnoreGravity() bool

IgnoreGravity reports whether the particle is exempt from gravity. Set by QAreaBody when gravityFree is enabled.

func (*Particle) IsConnectedWithSpring

func (p *Particle) IsConnectedWithSpring(other *Particle) bool

IsConnectedWithSpring reports whether this particle is connected to `other` via a spring. Backed by a set for O(1) lookup.

func (*Particle) IsInternal

func (p *Particle) IsInternal() bool

IsInternal reports whether this is an internal (non-boundary) particle. Internal particles don't participate in collision detection but provide structural rigidity in soft-body grids.

func (*Particle) IsLazy

func (p *Particle) IsLazy() bool

IsLazy reports whether the particle's lazy feature is enabled. Lazy particles react once to a collision, then ignore the colliding body until they exit and re-enter the collision.

func (*Particle) Mass

func (p *Particle) Mass() float32

Mass returns the particle's mass.

func (*Particle) OwnerMesh

func (p *Particle) OwnerMesh() *Mesh

OwnerMesh returns the mesh that owns this particle, or nil if detached.

func (*Particle) Position

func (p *Particle) Position() Vec2

Position returns the particle's local position relative to its owning mesh.

func (*Particle) PreviousGlobalPosition

func (p *Particle) PreviousGlobalPosition() Vec2

PreviousGlobalPosition returns the particle's previous world-space position. Used for implicit velocity: vel = globalPosition - prevGlobalPosition.

func (*Particle) Radius

func (p *Particle) Radius() float32

Radius returns the particle's collision radius.

func (*Particle) ResetOneTimeCollisions

func (p *Particle) ResetOneTimeCollisions()

ResetOneTimeCollisions moves the previous set into the current set, then clears the previous. Called once per step for lazy particles. Matches QParticle::ResetOneTimeCollisions.

func (*Particle) SetEnabled

func (p *Particle) SetEnabled(b bool) *Particle

SetEnabled enables or disables the particle.

func (*Particle) SetForce

func (p *Particle) SetForce(v Vec2) *Particle

SetForce sets the particle's queued force (applied at next step). Matches QParticle::SetForce in qparticle.cpp:176-184.

func (*Particle) SetGlobalPosition

func (p *Particle) SetGlobalPosition(v Vec2) *Particle

SetGlobalPosition sets the particle's world-space position and marks the AABB dirty. Matches QParticle::SetGlobalPosition in qparticle.cpp:77-95.

func (*Particle) SetIsInternal

func (p *Particle) SetIsInternal(b bool) *Particle

SetIsInternal marks the particle as internal (non-boundary).

func (*Particle) SetIsLazy

func (p *Particle) SetIsLazy(b bool) *Particle

SetIsLazy enables or disables the lazy collision feature.

func (*Particle) SetMass

func (p *Particle) SetMass(m float32) *Particle

SetMass sets the particle's mass.

func (*Particle) SetOwnerMesh

func (p *Particle) SetOwnerMesh(m *Mesh) *Particle

SetOwnerMesh sets the mesh that owns this particle.

func (*Particle) SetPosition

func (p *Particle) SetPosition(v Vec2) *Particle

SetPosition sets the particle's local position. Matches qparticle.cpp:108-124.

func (*Particle) SetPreviousGlobalPosition

func (p *Particle) SetPreviousGlobalPosition(v Vec2) *Particle

SetPreviousGlobalPosition sets the particle's previous world-space position. Used by the Verlet integrator and by ApplyImpulse.

func (*Particle) SetRadius

func (p *Particle) SetRadius(r float32) *Particle

SetRadius sets the particle's collision radius. Matches qparticle.cpp:139-148.

func (*Particle) UpdateAABB

func (p *Particle) UpdateAABB()

UpdateAABB recomputes the particle's AABB from its current global position and radius. Matches QParticle::UpdateAABB in qparticle.cpp:45-56.

type Raycast

type Raycast struct {
	// contains filtered or unexported fields
}

Raycast casts a ray into the world and reports collision contacts.

Two modes:

  • Instance-based: register a Raycast with World.AddRaycast; contacts are auto-updated each step via UpdateContacts().
  • Static one-shot: call RaycastTo() for a fire-and-forget query.

func NewRaycast

func NewRaycast(position, rayVector Vec2, enableContainingBodies bool) *Raycast

NewRaycast creates a raycast.

func (*Raycast) CollidableLayersBit

func (r *Raycast) CollidableLayersBit() int

func (*Raycast) Contacts

func (r *Raycast) Contacts() []RaycastContact

func (*Raycast) EnabledContainingBodies

func (r *Raycast) EnabledContainingBodies() bool

func (*Raycast) Position

func (r *Raycast) Position() Vec2

func (*Raycast) RayVector

func (r *Raycast) RayVector() Vec2

func (*Raycast) Rotation

func (r *Raycast) Rotation() float32

func (*Raycast) SetCollidableLayersBit

func (r *Raycast) SetCollidableLayersBit(b int) *Raycast

func (*Raycast) SetEnabledContainingBodies

func (r *Raycast) SetEnabledContainingBodies(b bool) *Raycast

func (*Raycast) SetPosition

func (r *Raycast) SetPosition(v Vec2) *Raycast

func (*Raycast) SetRayVector

func (r *Raycast) SetRayVector(v Vec2) *Raycast

func (*Raycast) SetRotation

func (r *Raycast) SetRotation(rad float32) *Raycast

func (*Raycast) UpdateContacts

func (r *Raycast) UpdateContacts()

UpdateContacts re-computes the raycast contacts. Called automatically by World.Update for registered raycasts. Matches QRaycast::UpdateContacts in qraycast.cpp:86-90.

func (*Raycast) World

func (r *Raycast) World() *World

World returns the world this raycast belongs to.

type RaycastContact

type RaycastContact struct {
	Body     *Body
	Position Vec2
	Normal   Vec2
	Distance float32
}

RaycastContact is a single ray hit. Matches QRaycast::Contact.

func RaycastTo

func RaycastTo(world *World, rayPosition, rayVector Vec2, collidableLayers int, enableContainingBodies bool) []RaycastContact

RaycastTo performs a one-shot raycast against the world.

Filters bodies by AABB and layer bits, then tests each body's meshes for ray-polygon or ray-circle intersection.

type RigidBody

type RigidBody struct {
	Body
	// contains filtered or unexported fields
}

RigidBody is a non-deformable solid body simulated with Verlet integration.

Verlet integration: velocity is implicit, computed as (position - prevPosition). Forces are applied by translating position directly (and prevPosition for impulses). Rotation works the same way via prevRotation.

Key behaviors preserved from C++:

  • Float-drift clamp: velocity components < 0.01 are zeroed (qrigidbody.cpp:146-153)
  • Velocity limit clamping
  • Air friction drag
  • Gravity (custom or world)
  • Force/angularForce accumulation (applied next step)
  • ApplyImpulse modifies prevPosition (Verlet-style impulse)

func GetRigidBody

func GetRigidBody(b *Body) *RigidBody

GetRigidBody returns the *RigidBody that embeds the given *Body, or nil. This is the exported version of asRigidBody, for use by external packages (e.g., the examples' mouse drag handler).

func NewRigidBody

func NewRigidBody() *RigidBody

NewRigidBody constructs a RigidBody with default values.

func (*RigidBody) AddAngularForce

func (rb *RigidBody) AddAngularForce(v float32) *RigidBody

AddAngularForce adds to the queued angular force.

func (*RigidBody) AddForce

func (rb *RigidBody) AddForce(v Vec2) *RigidBody

AddForce adds to the queued force.

func (*RigidBody) AngularForce

func (rb *RigidBody) AngularForce() float32

AngularForce returns the currently-queued angular force.

func (*RigidBody) ApplyForce

func (rb *RigidBody) ApplyForce(force Vec2) *RigidBody

ApplyForce applies an immediate force at the body's center (no torque).

func (*RigidBody) ApplyForceAt

func (rb *RigidBody) ApplyForceAt(force, r Vec2, updateMeshTransforms bool) *RigidBody

ApplyForce applies an immediate force at an offset. Translates the body by `force` and rotates by r · force.Perpendicular() / inertia.

Safe before the physics step (e.g., in OnPreStep). Calling after the step may break the simulation — use SetForce/AddForce for next-step-safe.

func (*RigidBody) ApplyImpulse

func (rb *RigidBody) ApplyImpulse(impulse, r Vec2) *RigidBody

ApplyImpulse applies an impulse by modifying prevPosition (Verlet-style). Matches QRigidBody::ApplyImpulse in qrigidbody.cpp:84-96.

func (*RigidBody) AsBody

func (rb *RigidBody) AsBody() *Body

AsBody returns a *Body pointer for this RigidBody. Useful when an API requires a *Body (e.g., World.AddBody).

func (*RigidBody) FixedRotationEnabled

func (rb *RigidBody) FixedRotationEnabled() bool

FixedRotationEnabled reports whether rotation is locked.

func (*RigidBody) Force

func (rb *RigidBody) Force() Vec2

Force returns the currently-queued force (applied next step).

func (*RigidBody) KinematicCollisionsEnabled

func (rb *RigidBody) KinematicCollisionsEnabled() bool

KinematicCollisionsEnabled reports whether kinematic-kinematic collisions react.

func (*RigidBody) KinematicEnabled

func (rb *RigidBody) KinematicEnabled() bool

KinematicEnabled reports whether the body is kinematic.

func (*RigidBody) PostUpdate

func (rb *RigidBody) PostUpdate()

PostUpdate is a no-op for rigid bodies. (PlatformerBody overrides.)

func (*RigidBody) SetAngularForce

func (rb *RigidBody) SetAngularForce(v float32) *RigidBody

SetAngularForce sets the queued angular force. Matches QRigidBody::SetAngularForce in qrigidbody.cpp:102-107.

func (*RigidBody) SetFixedRotationEnabled

func (rb *RigidBody) SetFixedRotationEnabled(v bool) *RigidBody

SetFixedRotationEnabled controls whether rotation is locked.

func (*RigidBody) SetForce

func (rb *RigidBody) SetForce(v Vec2) *RigidBody

SetForce sets the queued force for the next step. Matches QRigidBody::SetForce in qrigidbody.cpp:67-72.

func (*RigidBody) SetKinematicCollisionsEnabled

func (rb *RigidBody) SetKinematicCollisionsEnabled(v bool) *RigidBody

SetKinematicCollisionsEnabled controls kinematic-kinematic collision response.

func (*RigidBody) SetKinematicEnabled

func (rb *RigidBody) SetKinematicEnabled(v bool) *RigidBody

SetKinematicEnabled controls whether the body is kinematic.

func (*RigidBody) SetPositionAndCollide

func (rb *RigidBody) SetPositionAndCollide(v Vec2, withPreviousPosition bool) *RigidBody

SetPositionAndCollide sets the position and immediately runs collision resolution against the world. Use this for teleport-style movement. Matches QRigidBody::SetPositionAndCollide in qrigidbody.cpp:41-48.

func (*RigidBody) Update

func (rb *RigidBody) Update()

Update performs Verlet integration for one step.

CRITICAL: This method preserves the C++ float-drift clamp at lines 146-153 — velocity components < 0.01 are zeroed to fight float drift. Do NOT remove or adjust without re-running the parity suite.

type SAPBroadPhase

type SAPBroadPhase struct {
	// contains filtered or unexported fields
}

SAPBroadPhase is the default Sweep-and-Prune implementation.

func NewSAPBroadPhase

func NewSAPBroadPhase() *SAPBroadPhase

NewSAPBroadPhase constructs an empty SAP broadphase.

func (*SAPBroadPhase) Clear

func (s *SAPBroadPhase) Clear()

Clear removes all bodies.

func (*SAPBroadPhase) Insert

func (s *SAPBroadPhase) Insert(b *Body)

Insert adds a body (if not already present).

func (*SAPBroadPhase) Pairs

func (s *SAPBroadPhase) Pairs() []BodyPair

Pairs returns candidate collision pairs via Sweep-and-Prune.

func (*SAPBroadPhase) Remove

func (s *SAPBroadPhase) Remove(b *Body)

Remove removes a body.

type Side

type Side int

Side enumerates the four cardinal directions, used by GetVectorSide. Matches the QSides enum in qvector.h:36-42.

const (
	SideUp    Side = iota // 0
	SideRight             // 1
	SideDown              // 2
	SideLeft              // 3
	SideNone              // 4
)

func GetVectorSide

func GetVectorSide(vector, referenceUpVector Vec2, maxAngleDefiningSide float32) Side

GetVectorSide classifies a vector relative to a reference "up" direction. maxAngleDefiningSide defaults to π/4 (45°) in callers. Matches QVector::GetVectorSide in qvector.cpp:71-86.

type SoftBody

type SoftBody struct {
	Body
	// contains filtered or unexported fields
}

SoftBody is a deformable body using mass-spring model with PBD. Matches QSoftBody in qsoftbody.h, qsoftbody.cpp.

Soft bodies have:

  • Per-particle Verlet integration (each particle moves independently)
  • Springs connecting particles (structural rigidity)
  • Optional area-preserving (pressure-based volume conservation)
  • Optional shape matching (pulls particles toward rest shape)
  • Optional self-collisions (particles collide with each other)

The simulation model is MASS_SPRING (vs RIGID_BODY for rigid bodies). This affects how the World.Update loop dispatches the body's Update.

func NewSoftBody

func NewSoftBody() *SoftBody

NewSoftBody constructs a SoftBody with default values.

func (*SoftBody) ApplyForce

func (sb *SoftBody) ApplyForce(force Vec2) *SoftBody

ApplyForce applies a force to all particles in the soft body. Matches QSoftBody::ApplyForce in qsoftbody.cpp:74-91.

func (*SoftBody) ApplyShapeMatching

func (sb *SoftBody) ApplyShapeMatching()

ApplyShapeMatching pulls particles toward their rest shape.

Computes the average position and rotation of the current particles, then computes target positions by rotating the rest (local) positions by that rotation. Applies a quadratic force toward each target.

func (*SoftBody) AreaPreservingEnabled

func (sb *SoftBody) AreaPreservingEnabled() bool

AreaPreservingEnabled reports whether area preserving is active.

func (*SoftBody) AreaPreservingRate

func (sb *SoftBody) AreaPreservingRate() float32

AreaPreservingRate returns the rate at which the target area is applied.

func (*SoftBody) AreaPreservingRigidity

func (sb *SoftBody) AreaPreservingRigidity() float32

AreaPreservingRigidity returns the rigidity of area-preserving constraints.

func (*SoftBody) AsBody

func (sb *SoftBody) AsBody() *Body

AsBody returns a *Body pointer for this SoftBody.

func (*SoftBody) Mass

func (sb *SoftBody) Mass() float32

Mass returns the body's mass (or particle-specific mass if enabled).

func (*SoftBody) ParticleSpecificMass

func (sb *SoftBody) ParticleSpecificMass() float32

ParticleSpecificMass returns the per-particle mass (if enabled).

func (*SoftBody) ParticleSpecificMassEnabled

func (sb *SoftBody) ParticleSpecificMassEnabled() bool

ParticleSpecificMassEnabled reports whether per-particle mass is active.

func (*SoftBody) PassivationOfInternalSpringsEnabled

func (sb *SoftBody) PassivationOfInternalSpringsEnabled() bool

PassivationOfInternalSpringsEnabled reports whether internal springs are passive.

func (*SoftBody) PostUpdate

func (sb *SoftBody) PostUpdate()

PostUpdate is a no-op for soft bodies.

func (*SoftBody) PreserveAreas

func (sb *SoftBody) PreserveAreas()

PreserveAreas applies the area-preserving pressure force.

Computes the difference between the target area and the current polygon area, then pushes each polygon vertex along its edge normal to restore the target area. Includes the area stability hysteresis and the ±5× area clamp from the C++ engine.

func (*SoftBody) Rigidity

func (sb *SoftBody) Rigidity() float32

Rigidity returns the body's rigidity (spring stiffness multiplier).

func (*SoftBody) SelfCollisionsEnabled

func (sb *SoftBody) SelfCollisionsEnabled() bool

SelfCollisionsEnabled reports whether particles self-collide.

func (*SoftBody) SelfCollisionsSpecifiedRadius

func (sb *SoftBody) SelfCollisionsSpecifiedRadius() float32

SelfCollisionsSpecifiedRadius returns the self-collision particle radius.

func (*SoftBody) SetAreaPreservingEnabled

func (sb *SoftBody) SetAreaPreservingEnabled(b bool) *SoftBody

SetAreaPreservingEnabled enables or disables area preserving. When enabled, the target area is computed from the initial polygon area.

func (*SoftBody) SetAreaPreservingRate

func (sb *SoftBody) SetAreaPreservingRate(r float32) *SoftBody

SetAreaPreservingRate sets the area preserving rate (0.0–1.0).

func (*SoftBody) SetAreaPreservingRigidity

func (sb *SoftBody) SetAreaPreservingRigidity(r float32) *SoftBody

SetAreaPreservingRigidity sets the area preserving rigidity.

func (*SoftBody) SetParticleSpecificMass

func (sb *SoftBody) SetParticleSpecificMass(m float32) *SoftBody

SetParticleSpecificMass sets the per-particle mass.

func (*SoftBody) SetParticleSpecificMassEnabled

func (sb *SoftBody) SetParticleSpecificMassEnabled(b bool) *SoftBody

SetParticleSpecificMassEnabled enables/disables per-particle mass.

func (*SoftBody) SetPassivationOfInternalSpringsEnabled

func (sb *SoftBody) SetPassivationOfInternalSpringsEnabled(b bool) *SoftBody

SetPassivationOfInternalSpringsEnabled enables/disables internal spring passivation.

func (*SoftBody) SetRigidity

func (sb *SoftBody) SetRigidity(r float32) *SoftBody

SetRigidity sets the body's rigidity (affects spring stiffness).

func (*SoftBody) SetSelfCollisionsEnabled

func (sb *SoftBody) SetSelfCollisionsEnabled(b bool) *SoftBody

SetSelfCollisionsEnabled enables or disables particle self-collisions.

func (*SoftBody) SetSelfCollisionsSpecifiedRadius

func (sb *SoftBody) SetSelfCollisionsSpecifiedRadius(r float32) *SoftBody

SetSelfCollisionsSpecifiedRadius sets the self-collision particle radius.

func (*SoftBody) SetShapeMatchingEnabled

func (sb *SoftBody) SetShapeMatchingEnabled(b bool, withoutInternals bool) *SoftBody

SetShapeMatchingEnabled enables/disables shape matching. withoutInternals controls whether internal particles are included.

func (*SoftBody) SetShapeMatchingFixedPosition

func (sb *SoftBody) SetShapeMatchingFixedPosition(v Vec2) *SoftBody

SetShapeMatchingFixedPosition sets the fixed target position.

func (*SoftBody) SetShapeMatchingFixedRotation

func (sb *SoftBody) SetShapeMatchingFixedRotation(r float32) *SoftBody

SetShapeMatchingFixedRotation sets the fixed target rotation.

func (*SoftBody) SetShapeMatchingFixedTransformEnabled

func (sb *SoftBody) SetShapeMatchingFixedTransformEnabled(b bool) *SoftBody

SetShapeMatchingFixedTransformEnabled enables/disables fixed target transform.

func (*SoftBody) SetShapeMatchingRate

func (sb *SoftBody) SetShapeMatchingRate(r float32) *SoftBody

SetShapeMatchingRate sets the shape matching rate (0.0–1.0).

func (*SoftBody) SetTargetPreservationArea

func (sb *SoftBody) SetTargetPreservationArea(a float32) *SoftBody

SetTargetPreservationArea sets the explicit target area.

func (*SoftBody) ShapeMatchingEnabled

func (sb *SoftBody) ShapeMatchingEnabled() bool

ShapeMatchingEnabled reports whether shape matching is active.

func (*SoftBody) ShapeMatchingFixedPosition

func (sb *SoftBody) ShapeMatchingFixedPosition() Vec2

ShapeMatchingFixedPosition returns the fixed target position.

func (*SoftBody) ShapeMatchingFixedRotation

func (sb *SoftBody) ShapeMatchingFixedRotation() float32

ShapeMatchingFixedRotation returns the fixed target rotation.

func (*SoftBody) ShapeMatchingFixedTransformEnabled

func (sb *SoftBody) ShapeMatchingFixedTransformEnabled() bool

ShapeMatchingFixedTransformEnabled reports whether a fixed target transform is used.

func (*SoftBody) ShapeMatchingRate

func (sb *SoftBody) ShapeMatchingRate() float32

ShapeMatchingRate returns the shape matching rate.

func (*SoftBody) TargetPreservationArea

func (sb *SoftBody) TargetPreservationArea() float32

TargetPreservationArea returns the target area for area preserving.

func (*SoftBody) Update

func (sb *SoftBody) Update()

Update performs per-particle Verlet integration.

type Spring

type Spring struct {
	// contains filtered or unexported fields
}

Spring is a distance constraint between two particles. Matches QSpring in qspring.h, qspring.cpp.

Springs are used both internally by soft-body meshes (to maintain structural rigidity) and externally as world springs (e.g., mouse drag). The Update method applies corrective forces to both particles to bring them toward the rest length.

Two application paths:

  • Direct (internalsException=false): ApplyForce on each particle immediately.
  • Accumulated (internalsException=true): AddAccumulatedForce, then the caller must call ApplyAccumulatedForces later. This prevents iteration order bias when many springs share particles.

func NewSpring

func NewSpring(pA, pB *Particle, internal bool) *Spring

NewSpring creates a spring between two particles, auto-calculating the rest length from the current distance.

func NewSpringWithLength

func NewSpringWithLength(pA, pB *Particle, length float32, internal bool) *Spring

NewSpringWithLength creates a spring with an explicit rest length.

func (*Spring) DistanceLimitEnabled

func (s *Spring) DistanceLimitEnabled() bool

DistanceLimitEnabled reports whether the distance limit feature is active.

func (*Spring) Enabled

func (s *Spring) Enabled() bool

Enabled reports whether the spring is active.

func (*Spring) IsInternal

func (s *Spring) IsInternal() bool

IsInternal reports whether this is an internal spring (affects area-preserving).

func (*Spring) Length

func (s *Spring) Length() float32

Length returns the spring's rest length.

func (*Spring) MaximumDistanceFactor

func (s *Spring) MaximumDistanceFactor() float32

MaximumDistanceFactor returns the max distance factor.

func (*Spring) MinimumDistanceFactor

func (s *Spring) MinimumDistanceFactor() float32

MinimumDistanceFactor returns the min distance factor (relative to rest length).

func (*Spring) ParticleA

func (s *Spring) ParticleA() *Particle

ParticleA returns spring's first particle.

func (*Spring) ParticleB

func (s *Spring) ParticleB() *Particle

ParticleB returns spring's second particle.

func (*Spring) Rigidity

func (s *Spring) Rigidity() float32

Rigidity returns the spring's rigidity (0.0-1.0).

func (*Spring) SetDistanceLimitEnabled

func (s *Spring) SetDistanceLimitEnabled(b bool) *Spring

SetDistanceLimitEnabled enables/disables the distance limit. When enabled, the spring applies full-strength (rigidity=1.0) correction when the current distance falls outside [length*minFactor, length*maxFactor].

func (*Spring) SetEnabled

func (s *Spring) SetEnabled(b bool) *Spring

SetEnabled enables or disables the spring.

func (*Spring) SetIsInternal

func (s *Spring) SetIsInternal(b bool) *Spring

SetIsInternal marks the spring as internal.

func (*Spring) SetLength

func (s *Spring) SetLength(l float32) *Spring

SetLength sets the spring's rest length.

func (*Spring) SetMaximumDistanceFactor

func (s *Spring) SetMaximumDistanceFactor(v float32) *Spring

SetMaximumDistanceFactor sets the max distance factor.

func (*Spring) SetMinimumDistanceFactor

func (s *Spring) SetMinimumDistanceFactor(v float32) *Spring

SetMinimumDistanceFactor sets the min distance factor.

func (*Spring) SetParticleA

func (s *Spring) SetParticleA(p *Particle) *Spring

SetParticleA sets the first particle.

func (*Spring) SetParticleB

func (s *Spring) SetParticleB(p *Particle) *Spring

SetParticleB sets the second particle.

func (*Spring) SetRigidity

func (s *Spring) SetRigidity(r float32) *Spring

SetRigidity sets the spring's rigidity (0.0-1.0).

func (*Spring) Update

func (s *Spring) Update(rigidity float32, internalsException bool, isWorldSpring bool)

Update applies spring constraints and updates particle positions. Matches QSpring::Update in qspring.cpp:50-166.

Parameters:

  • rigidity: override rigidity (multiplied with the spring's own rigidity)
  • internalsException: if true and spring is internal, use the accumulated-force path
  • isWorldSpring: if true, skip particles owned by rigid/static bodies

type Vec2

type Vec2 struct {
	X, Y float32
}

Vec2 is a 2D float32 vector. Matches QVector in qvector.h.

Operations preserve the C++ engine's behavior, including:

  • Normalized() returns Vec2Zero() for zero-length vectors (never NaN)
  • The Y axis points down, matching QVector::Down() = (0, 1)

Reference: QuarkPhysics/qvector.h, qvector.cpp

func AngleToUnitVector

func AngleToUnitVector(radianAngle float32) Vec2

AngleToUnitVector returns the unit vector pointing in the direction of radianAngle. Matches QVector::AngleToUnitVector in qvector.cpp.

func ComputeFriction

func ComputeFriction(bodyA, bodyB *Body, normal Vec2, penetration float32, relativeVelocity Vec2) Vec2

ComputeFriction calculates the friction force for a collision.

Uses Coulomb friction: tangent = relativeVelocity projected onto the contact plane; if |jt| < penetration * staticFriction, use static friction, otherwise use dynamic friction.

func GetAveragePositionAndRotation

func GetAveragePositionAndRotation(particles []*Particle) (Vec2, float32)

GetAveragePositionAndRotation computes the average position and rotation of a set of particles. Used by shape matching to find the target transform.

The rotation is computed by finding the angle that best aligns the current particle positions with their local (rest) positions.

func GetBisectorUnitVector

func GetBisectorUnitVector(pointA, pointB, pointC Vec2, checkPointsAreCCW bool) Vec2

GetBisectorUnitVector returns the unit bisector vector of the angle formed at pointB by rays (pointB→pointA) and (pointB→pointC). Matches QVector::GeteBisectorUnitVector in qvector.cpp:88-119.

The C++ implementation:

fromPrev        = pointB - pointA
toNext          = pointC - pointB
prevToNext      = pointC - pointA
prevToNextPerp  = prevToNext.Perpendicular()
bisectorUnit    = prevToNextPerp.Normalized()
if fromPrev · prevToNextPerp < 0:
    if checkPointsAreCCW:
        toCenterPos = prevToNext*0.5 - fromPrev
        if toCenterPos · bisectorUnit < 0: bisectorUnit = -bisectorUnit
else:
    if checkPointsAreCCW:
        toCenterPos = prevToNext*0.5 - fromPrev
        if toCenterPos · bisectorUnit > 0: bisectorUnit = -bisectorUnit
    else:
        bisectorUnit = bisectorUnit  (no-op)
return -bisectorUnit

func GetMatchingParticlePositions

func GetMatchingParticlePositions(particles []*Particle, targetPosition Vec2, targetRotation float32) []Vec2

GetMatchingParticlePositions computes the target positions for shape matching. Each particle's LOCAL position is rotated by -targetRotation and translated to targetPosition.

func LineIntersectionLine

func LineIntersectionLine(d1A, d1B, d2A, d2B Vec2) Vec2

LineIntersectionLine computes the intersection of two line segments. Returns Vec2NaN() if no intersection. Matches QCollision::LineIntersectionLine.

func Vec2Down

func Vec2Down() Vec2

Vec2Down returns (0, 1).

func Vec2Left

func Vec2Left() Vec2

Vec2Left returns (-1, 0).

func Vec2NaN

func Vec2NaN() Vec2

Vec2NaN returns a vector with NaN components, used as a sentinel for "no intersection" in LineIntersectionLine.

func Vec2Right

func Vec2Right() Vec2

Vec2Right returns (1, 0).

func Vec2Up

func Vec2Up() Vec2

Vec2Up returns (0, -1). Matches QVector::Up (Y is inverted: up is negative).

func Vec2Zero

func Vec2Zero() Vec2

Vec2Zero returns the zero vector (0, 0).

func (Vec2) Add

func (v Vec2) Add(other Vec2) Vec2

Add returns v + other.

func (*Vec2) AddAssign

func (v *Vec2) AddAssign(other Vec2) *Vec2

AddAssign mutates v in place: v += other. Returns v for chaining.

func (Vec2) Div

func (v Vec2) Div(s float32) Vec2

Div returns v / scalar. Panics on division by zero — callers must guard.

func (Vec2) DivVec

func (v Vec2) DivVec(other Vec2) Vec2

DivVec returns v / other (component-wise).

func (Vec2) Dot

func (v Vec2) Dot(other Vec2) float32

Dot returns the dot product of v and other. Matches QVector::Dot in qvector.h:161-163.

func (Vec2) Equal

func (v Vec2) Equal(other Vec2) bool

Equal reports whether v and other have identical components. Note: uses exact equality, matching qvector.h:138-140.

func (Vec2) IsNaN

func (v Vec2) IsNaN() bool

IsNaN reports whether both components are NaN. Matches QVector::isNaN in qvector.h:183-188 (returns true only if BOTH x and y are NaN).

func (Vec2) Length

func (v Vec2) Length() float32

Length returns |v|. If you only need to compare lengths, use LengthSquared to avoid the sqrt. Matches QVector::Length in qvector.h:164-166.

func (Vec2) LengthSquared

func (v Vec2) LengthSquared() float32

LengthSquared returns |v|². Cheaper than Length (no sqrt). Matches QVector::LengthSquared in qvector.h:179-181.

func (Vec2) Mul

func (v Vec2) Mul(s float32) Vec2

Mul returns v * scalar.

func (*Vec2) MulAssign

func (v *Vec2) MulAssign(s float32) *Vec2

MulAssign mutates v in place: v *= scalar.

func (Vec2) Neg

func (v Vec2) Neg() Vec2

Neg returns -v.

func (Vec2) Normalized

func (v Vec2) Normalized() Vec2

Normalized returns the unit vector in the direction of v. Returns Vec2Zero() if v is zero-length — never returns NaN. Matches QVector::Normalized in qvector.h:167-175.

func (Vec2) NotEqual

func (v Vec2) NotEqual(other Vec2) bool

NotEqual reports whether v and other differ in any component.

func (Vec2) Perpendicular

func (v Vec2) Perpendicular() Vec2

Perpendicular returns the vector (v.Y, -v.X), rotated 90° clockwise. Matches QVector::Perpendicular in qvector.h:176-178.

func (Vec2) Rotated

func (v Vec2) Rotated(radianAngle float32) Vec2

Rotated returns v rotated by radianAngle (radians, clockwise in screen space). Matches QVector::Rotated in qvector.cpp.

func (Vec2) Sub

func (v Vec2) Sub(other Vec2) Vec2

Sub returns v - other.

func (*Vec2) SubAssign

func (v *Vec2) SubAssign(other Vec2) *Vec2

SubAssign mutates v in place: v -= other.

type World

type World struct {
	// contains filtered or unexported fields
}

World manages a physics simulation. Matches QWorld in qworld.h, qworld.cpp.

The World owns all bodies, joints, springs, raycasts, and the contact pool. One call to Update() advances the simulation by one step.

func NewWorld

func NewWorld(opts ...WorldOption) *World

NewWorld constructs a World with the given options.

func (*World) AddAreaBody

func (w *World) AddAreaBody(ab *AreaBody) *World

AddAreaBody convenience: adds an AreaBody to the world and registers it.

func (*World) AddBody

func (w *World) AddBody(b *Body) *World

AddBody adds a body to the world and links it back. Matches QWorld::AddBody.

func (*World) AddCollisionException

func (w *World) AddCollisionException(a, b *Body) *World

AddCollisionException marks two bodies as never colliding.

func (*World) AddGizmo

func (w *World) AddGizmo(g *Gizmo)

AddGizmo records a debug gizmo (only if debugGizmos is true).

func (*World) AddJoint

func (w *World) AddJoint(j *Joint) *World

AddJoint adds a joint to the world.

func (*World) AddRaycast

func (w *World) AddRaycast(r *Raycast) *World

AddRaycast registers a raycast for auto-updating each step.

func (*World) AddRigidBody

func (w *World) AddRigidBody(rb *RigidBody) *World

AddRigidBody convenience: adds a RigidBody to the world and registers it.

func (*World) AddSoftBody

func (w *World) AddSoftBody(sb *SoftBody) *World

AddSoftBody convenience: adds a SoftBody to the world and registers it.

func (*World) AddSpring

func (w *World) AddSpring(s *Spring) *World

AddSpring adds a world-level spring to the world.

func (*World) Bodies

func (w *World) Bodies() []*Body

Bodies returns the slice of bodies.

func (*World) BodyAt

func (w *World) BodyAt(i int) *Body

BodyAt returns the body at the given index.

func (*World) BodyCount

func (w *World) BodyCount() int

BodyCount returns the number of bodies in the world.

func (*World) BodyIndex

func (w *World) BodyIndex(b *Body) int

BodyIndex returns the index of the given body, or -1 if not found.

func (*World) Broadphase

func (w *World) Broadphase() BroadPhase

Broadphase returns the custom broadphase implementation, or nil.

func (*World) BroadphaseEnabled

func (w *World) BroadphaseEnabled() bool

BroadphaseEnabled reports whether broadphase is enabled.

func (*World) CheckCollisionException

func (w *World) CheckCollisionException(a, b *Body) bool

CheckCollisionException reports whether two bodies have a collision exception.

func (*World) ClearGizmos

func (w *World) ClearGizmos()

ClearGizmos removes all debug gizmos.

func (*World) CollideWithWorld

func (w *World) CollideWithWorld(body *Body) bool

CollideWithWorld runs collision detection and resolution for a single body against all others. Used by RigidBody.SetPositionAndCollide and QPlatformerBody. Matches QWorld::CollideWithWorld in qworld.cpp:588-604.

func (*World) ContactPool

func (w *World) ContactPool() *ContactPool

ContactPool returns the world's contact pool (for Manifold).

func (*World) DebugGizmos

func (w *World) DebugGizmos() bool

DebugGizmos reports whether debug gizmo recording is enabled.

func (*World) Enabled

func (w *World) Enabled() bool

Enabled reports whether the world is enabled (running).

func (*World) Gizmos

func (w *World) Gizmos() []*Gizmo

Gizmos returns the recorded debug gizmos from the last step.

func (*World) Gravity

func (w *World) Gravity() Vec2

Gravity returns the world's gravity vector.

func (*World) IterationCount

func (w *World) IterationCount() int

IterationCount returns the solver iteration count.

func (*World) JointCount

func (w *World) JointCount() int

JointCount returns the number of joints.

func (*World) Joints

func (w *World) Joints() []*Joint

Joints returns the slice of joints.

func (*World) RaycastCount

func (w *World) RaycastCount() int

RaycastCount returns the number of registered raycasts.

func (*World) Raycasts

func (w *World) Raycasts() []*Raycast

Raycasts returns the slice of registered raycasts.

func (*World) RemoveBody

func (w *World) RemoveBody(b *Body) *World

RemoveBody removes a body from the world.

func (*World) RemoveBodyAt

func (w *World) RemoveBodyAt(i int) *World

RemoveBodyAt removes the body at the given index.

func (*World) RemoveCollisionException

func (w *World) RemoveCollisionException(a, b *Body) *World

RemoveCollisionException removes a collision exception.

func (*World) RemoveJoint

func (w *World) RemoveJoint(j *Joint) *World

RemoveJoint removes a joint from the world.

func (*World) RemoveMatchingCollisionExceptions

func (w *World) RemoveMatchingCollisionExceptions(body *Body) *World

RemoveMatchingCollisionExceptions removes all exceptions involving body.

func (*World) RemoveRaycast

func (w *World) RemoveRaycast(r *Raycast) *World

RemoveRaycast removes a raycast from the world.

func (*World) RemoveSpring

func (w *World) RemoveSpring(s *Spring) *World

RemoveSpring removes a spring from the world.

func (*World) SetBroadphase

func (w *World) SetBroadphase(bp BroadPhase) *World

SetBroadphase sets a custom broadphase implementation.

func (*World) SetBroadphaseEnabled

func (w *World) SetBroadphaseEnabled(b bool) *World

SetBroadphaseEnabled enables or disables broadphase.

func (*World) SetDebugGizmos

func (w *World) SetDebugGizmos(b bool) *World

SetDebugGizmos enables or disables debug gizmo recording.

func (*World) SetEnabled

func (w *World) SetEnabled(b bool) *World

SetEnabled enables or disables the world.

func (*World) SetGravity

func (w *World) SetGravity(g Vec2) *World

SetGravity sets the world's gravity vector.

func (*World) SetIterationCount

func (w *World) SetIterationCount(n int) *World

SetIterationCount sets the solver iteration count.

func (*World) SetSleepingEnabled

func (w *World) SetSleepingEnabled(b bool) *World

SetSleepingEnabled enables or disables sleeping.

func (*World) SetSoftBodyCollisionHysteresis

func (w *World) SetSoftBodyCollisionHysteresis(v float32) *World

SetSoftBodyCollisionHysteresis sets the global hysteresis factor. Matches qworld.h:301. Range [0,1].

func (*World) SetTimeScale

func (w *World) SetTimeScale(ts float32) *World

SetTimeScale sets the world's time scale.

func (*World) SleepingEnabled

func (w *World) SleepingEnabled() bool

SleepingEnabled reports whether sleeping is enabled.

func (*World) SoftBodyCollisionHysteresis

func (w *World) SoftBodyCollisionHysteresis() float32

SoftBodyCollisionHysteresis returns the global hysteresis factor for soft-body-vs-soft-body collisions. Matches qworld.h:181. Default 0.2.

func (*World) SpringCount

func (w *World) SpringCount() int

SpringCount returns the number of world-level springs.

func (*World) Springs

func (w *World) Springs() []*Spring

Springs returns the slice of world-level springs.

func (*World) Step

func (w *World) Step() int

Step returns the current step counter (for parity tests).

func (*World) TestCollisionWithWorld

func (w *World) TestCollisionWithWorld(body *Body) []Manifold

TestCollisionWithWorld runs collision detection (no solving) and returns the manifolds. Used by QPlatformerBody probes.

func (*World) TimeScale

func (w *World) TimeScale() float32

TimeScale returns the world's time scale (1.0 = real-time).

func (*World) Update

func (w *World) Update()

Update advances the simulation by one step. Matches QWorld::Update in qworld.cpp:63-434

  1. Per-body Update (Verlet integration)
  2. OnPreStep events
  3. Broadphase prep
  4. Iteration loop: narrowphase + Solve + SolveFrictionAndVelocities
  5. Global AABB update
  6. Sleeping
  7. OnStep events

func (*World) UpdateConstraints

func (w *World) UpdateConstraints()

UpdateConstraints solves all soft body springs, angle constraints, world springs, and joints. Matches QWorld::UpdateConstraints in qworld.cpp:1175-1236.

Called once per solver iteration. For soft bodies, springs and angle constraints use the accumulated-force pipeline to prevent iteration order bias: forces are accumulated per-particle, then averaged and applied at the end of each constraint type's pass.

type WorldOption

type WorldOption func(*World)

WorldOption configures a World at construction.

func WithBroadphase

func WithBroadphase(b bool) WorldOption

WithBroadphase enables or disables broadphase (default enabled).

func WithBroadphaseImpl

func WithBroadphaseImpl(bp BroadPhase) WorldOption

WithBroadphaseImpl sets a custom broadphase implementation.

func WithConcurrency

func WithConcurrency(config ConcurrencyConfig) WorldOption

WithConcurrency enables parallel narrowphase with the given config.

func WithDebugGizmos

func WithDebugGizmos(b bool) WorldOption

WithDebugGizmos enables or disables debug gizmo recording.

func WithGravity

func WithGravity(g Vec2) WorldOption

WithGravity sets the world's gravity vector.

func WithIterations

func WithIterations(n int) WorldOption

WithIterations sets the solver iteration count (default 4).

func WithSleeping

func WithSleeping(b bool) WorldOption

WithSleeping enables or disables sleeping (default enabled).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL