internal

package
v0.16.4 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: LGPL-3.0 Imports: 11 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AttachColliderFixtures

func AttachColliderFixtures(entityID cardinal.EntityID, shapes []component.ColliderShape) error

AttachColliderFixtures creates one shape per ColliderShape on the C-side body identified by entityID. shapeIndex is the slice index i in shapes. Local offsets and rotations are applied so geometry defined in shape space is placed correctly in body space.

func ColliderShapeMutableFieldsEqual

func ColliderShapeMutableFieldsEqual(a, b component.ColliderShape) bool

ColliderShapeMutableFieldsEqual compares per-shape fields that Box2D can update without recreating the fixture shape.

func CreateBody

func CreateBody(
	entityID cardinal.EntityID,
	transform component.Transform2D,
	velocity component.Velocity2D,
	pb component.PhysicsBody2D,
) error

CreateBody creates a body on the C-side Box2D world via cbridge. It does not attach shapes; use AttachColliderFixtures next.

func CreateBodyWithCollider

func CreateBodyWithCollider(
	entityID cardinal.EntityID,
	transform component.Transform2D,
	velocity component.Velocity2D,
	pb component.PhysicsBody2D,
) error

CreateBodyWithCollider creates a body and attaches all shapes. If shape attachment fails, the body is destroyed and an error is returned.

func FlushBufferedContacts

func FlushBufferedContacts()

FlushBufferedContacts turns buffered contact records into physics2d events via the runtime emitter, then clears the buffer and nils Emitter.

On normal ticks, every Begin adds to rt.ActiveContacts and every End removes from it. After applying the step buffer, sustained overlaps are refreshed from the live contact list so IsSensor and filter bits stay aligned if a shape toggles sensor/solid or filters change while the pair remains touching.

On the first step after a rebuild (SuppressContactsStep was true), the buffer is empty because the listener suppressed callbacks. Instead, we diff rt.ActiveContacts (loaded from the persisted ECS component) against the live contact list:

  • Pairs in live but not in the map -> emit Begin, add to map
  • Pairs in the map but not in live -> emit End, remove from map
  • Pairs in both -> no event (game already knows)

If NoPersistedActiveContactsBaseline is set (missing singleton on a suppressed step), the suppressed flush adopts live contacts into the map without emitting events so one-shot Begin handlers do not all fire spuriously; the flag is cleared.

func FullRebuildFromECS

func FullRebuildFromECS(gravity component.Vec2, entries []PhysicsRebuildEntry) error

FullRebuildFromECS replaces all derived physics state for the package Runtime in one deterministic pass: destroys every body in the C-side world, clears maps and contact buffer, optionally applies gravity, recreates bodies/shapes from entries, then writes shadow snapshots. Entries are sorted by EntityID before processing; creation order follows that sort.

Requires ResetRuntime (or prior init) so Runtime() is non-nil. World is created on first rebuild using gravity; later rebuilds reuse the world and call SetGravity.

On any creation error, bodies created in this pass are destroyed and runtime maps are left empty (same as post-clear); the world remains allocated with no bodies.

After a successful rebuild, Emitter is cleared and SuppressContactsStep is set true for the next simulation step. The step driver must call SetStepEmitter before the step and FlushBufferedContacts after; that flush clears SuppressContactsStep automatically.

func ReconcileFromECS

func ReconcileFromECS(entries []PhysicsRebuildEntry) error

ReconcileFromECS incrementally syncs the C-side Box2D world from authoritative ECS entries using shadow-copy diffing. It is the hot-path counterpart to FullRebuildFromECS.

Structural vs mutable changes:

  • Structural: anything that changes shape identity -- shape count/order, per-shape type, local offset/rotation, or geometry (radius, half-extents, vertices, chain points). Handled by destroying all shapes on the body and re-attaching from ECS.

  • Mutable: body transform, linear/angular velocity, body type/damping/gravity scale, and per-shape sensor, friction, restitution, density, and filter category/mask/group. Applied in place without recreating shapes.

Requires non-nil Runtime() with a live C-side world (for example after an initial FullRebuildFromECS). Entries are sorted by EntityID; duplicate IDs are an error. Entities absent from entries are removed from the runtime (body destroyed, shadow dropped).

ReconcileFromECS does not touch SuppressContactsStep or Emitter; it does not step the world.

func ResetRuntime

func ResetRuntime()

ResetRuntime replaces the package runtime with a fresh PhysicsRuntime. If a C-side world exists, it is destroyed first.

func SetBufferedContactsFromStep

func SetBufferedContactsFromStep(events []cbridge.ContactEvent)

SetBufferedContactsFromStep converts cbridge.ContactEvent results from Step into BufferedContactEvents and stores them in rt.BufferedContacts for the next flush. Called by the pipeline between cbridge.Step and FlushBufferedContacts.

func SetStepEmitter

func SetStepEmitter(emitter event.ContactEventEmitter)

SetStepEmitter stores the contact event sink for the upcoming simulation step. The step driver should set this before the step and call FlushBufferedContacts after the step.

func ShapesStructuralEqual

func ShapesStructuralEqual(a, b []component.ColliderShape) bool

ShapesStructuralEqual reports whether two shape slices match for Box2D fixture shape definition: shape count/order (topology) and, per index, shape type, local transform, and geometry. Differences confined to sensor flag, friction, restitution, density, or filter (category, mask, group) are not structural and can be applied with fixture setters.

func WritebackFromStepResults

func WritebackFromStepResults(states []cbridge.BodyState, entries []WritebackEntry)

WritebackFromStepResults reads post-step positions, rotations, and velocities from the cbridge.Step body states and writes them into the corresponding ECS Transform2D and Velocity2D components. It also updates the shadow state so the next ReconcileFromECS tick sees no diff for these values.

Writeback applies to dynamic and kinematic bodies only. Static bodies (BodyType==1) never move. Manual bodies (ECS BodyTypeManual==4) are skipped because ECS/gameplay code owns their position. Since both BodyTypeKinematic and BodyTypeManual map to Box2D kinematic bodies, the ECS body type is checked rather than the bridge body type.

Types

type BufferedContactEvent

type BufferedContactEvent struct {
	Kind               ContactLifecycleKind
	FilterA            event.FixtureFilterBits
	FilterB            event.FixtureFilterBits
	EntityA            cardinal.EntityID
	EntityB            cardinal.EntityID
	ShapeIndexA        int
	ShapeIndexB        int
	IsSensorContact    bool
	Normal             component.Vec2
	NormalValid        bool
	Point              component.Vec2
	PointValid         bool
	ManifoldPointCount int
}

BufferedContactEvent is one contact record collected after a physics step for post-step consumption. It is not an engine/game event type — callers translate these records later.

IsSensorContact is true if either shape is a sensor (overlap / trigger semantics). Sensors do not build collision manifolds, so NormalValid and PointValid are usually false for pure sensor overlaps.

FilterA/FilterB are the shape filter bits at event time (category/mask/group), matching ECS per-shape bits so consumers can route without re-querying colliders.

type ContactLifecycleKind

type ContactLifecycleKind uint8

ContactLifecycleKind distinguishes BeginContact vs EndContact.

const (
	// ContactLifecycleBegin is emitted when two shapes start touching.
	ContactLifecycleBegin ContactLifecycleKind = iota
	// ContactLifecycleEnd is emitted when two shapes stop touching.
	ContactLifecycleEnd
)

type ContactPairInfo

type ContactPairInfo struct {
	IsSensor           bool
	FilterA            event.FixtureFilterBits
	FilterB            event.FixtureFilterBits
	Normal             component.Vec2
	NormalValid        bool
	Point              component.Vec2
	PointValid         bool
	ManifoldPointCount int
}

ContactPairInfo stores metadata for an active contact pair. FilterA/FilterB correspond to (EntityA, ShapeIndexA) and (EntityB, ShapeIndexB) after normalization. Manifold fields are best-effort from the last live sample (not serialized to snapshots).

type ContactPairKey

type ContactPairKey struct {
	EntityA     cardinal.EntityID
	ShapeIndexA int
	EntityB     cardinal.EntityID
	ShapeIndexB int
}

ContactPairKey identifies a unique fixture-pair contact. Always normalized so that (EntityA, ShapeIndexA) < (EntityB, ShapeIndexB) lexicographically.

type PhysicsRebuildEntry

type PhysicsRebuildEntry struct {
	EntityID    cardinal.EntityID
	Transform   component.Transform2D
	Velocity    component.Velocity2D
	PhysicsBody component.PhysicsBody2D
}

PhysicsRebuildEntry is one entity's authoritative physics components as read from ECS.

type PhysicsRuntime

type PhysicsRuntime struct {
	// KnownEntities tracks which Cardinal entities have bodies in the C-side Box2D world.
	KnownEntities map[cardinal.EntityID]struct{}

	// Shadow holds per-entity reconciler snapshots (diff against ECS each tick).
	Shadow map[cardinal.EntityID]ShadowState

	// BufferedContacts collects contact events from cbridge.Step for post-step flush.
	BufferedContacts []BufferedContactEvent

	// Emitter is the current tick's contact flush sink, set by the step driver before Step
	// and cleared in FlushBufferedContacts. Nil means skip emitting for this flush.
	Emitter event.ContactEventEmitter

	// SuppressContactsStep, when true, skips emitting contact/trigger begin/end for this step
	// (e.g. first step after restore).
	SuppressContactsStep bool

	// ActiveContacts is the in-memory working copy of which Begin events have been emitted
	// without a matching End. nil means "not yet loaded from ECS" (e.g. after ResetRuntime);
	// the step system populates it from the persisted ActiveContacts component on first access.
	ActiveContacts map[ContactPairKey]ContactPairInfo

	// ActiveContactsDirty is set when ActiveContacts was mutated during the current flush.
	// The step system checks this to decide whether to Set() the ECS component.
	ActiveContactsDirty bool

	// NoPersistedActiveContactsBaseline, when true, the next suppressed contact flush seeds
	// ActiveContacts from the live contact list without emitting Begin/End (physics singleton entity missing).
	NoPersistedActiveContactsBaseline bool
}

PhysicsRuntime owns derived physics state for one Cardinal world instance. ECS remains authoritative; this struct is disposable and rebuilt from components when needed.

func NewPhysicsRuntime

func NewPhysicsRuntime() *PhysicsRuntime

NewPhysicsRuntime returns an empty runtime. Maps are initialized; Emitter is nil. SuppressContactsStep is true so the next armed simulation step does not record contact begin/end; the following FlushBufferedContacts clears suppression when that flush is paired with an emitter (see contact_flush.go). ActiveContacts is nil, signaling "load from ECS on next step".

func Runtime

func Runtime() *PhysicsRuntime

Runtime returns the current package-scoped physics runtime. It does not create one lazily: callers must invoke ResetRuntime first; otherwise this returns nil.

func (*PhysicsRuntime) ActiveContactsToComponent

func (rt *PhysicsRuntime) ActiveContactsToComponent() component.ActiveContacts

ActiveContactsToComponent converts the working map to the ECS component format (sorted slice for deterministic snapshots).

func (*PhysicsRuntime) LoadActiveContactsFromComponent

func (rt *PhysicsRuntime) LoadActiveContactsFromComponent(ac component.ActiveContacts)

LoadActiveContactsFromComponent populates the in-memory working map from the persisted ECS component. Called by the step system after a restore when ActiveContacts is nil.

func (*PhysicsRuntime) PruneActiveContactsInvolvingEntity

func (rt *PhysicsRuntime) PruneActiveContactsInvolvingEntity(entityID cardinal.EntityID)

PruneActiveContactsInvolvingEntity removes every active-contact key that references entityID. Call when that entity's body is destroyed or its fixtures are structurally replaced so end-of-tick persistence and the next suppressed diff do not retain stale pair keys.

func (*PhysicsRuntime) WorldExists

func (rt *PhysicsRuntime) WorldExists() bool

WorldExists reports whether the C-side Box2D world has been created and is alive.

type ShadowState

type ShadowState struct {
	Transform   component.Transform2D
	Velocity    component.Velocity2D
	PhysicsBody component.PhysicsBody2D
}

ShadowState is a deep snapshot of the ECS physics components last applied to Box2D for one entity. It must not share slice backing with live ECS data so in-place component edits do not corrupt the snapshot.

func NewShadowState

NewShadowState returns a shadow snapshot with deep-copied shapes (Shapes and per-shape Vertices / ChainPoints are cloned).

func (ShadowState) BodyParamsDiffer

func (s ShadowState) BodyParamsDiffer(p component.PhysicsBody2D) bool

BodyParamsDiffer reports whether rigidbody simulation parameters differ from the shadow.

func (ShadowState) PhysicsDiffers

PhysicsDiffers is true if any reconciled field differs from the given live components.

func (ShadowState) ShapesDiffer

func (s ShadowState) ShapesDiffer(p component.PhysicsBody2D) bool

ShapesDiffer reports deep differences in compound collider data: shape count/order (topology), per-shape type, transform, sensor, geometry, friction/restitution/density, and filter (category, mask, group).

func (ShadowState) TransformDiffers

func (s ShadowState) TransformDiffers(t component.Transform2D) bool

TransformDiffers reports whether the live transform differs from the shadow.

func (ShadowState) VelocityDiffers

func (s ShadowState) VelocityDiffers(v component.Velocity2D) bool

VelocityDiffers reports whether the live velocity differs from the shadow.

type WritebackEntry

type WritebackEntry struct {
	EntityID    cardinal.EntityID
	Transform   cardinal.Ref[component.Transform2D]
	Velocity    cardinal.Ref[component.Velocity2D]
	PhysicsBody cardinal.Ref[component.PhysicsBody2D]
}

WritebackEntry holds the ECS refs needed to write Box2D results back to components.

Directories

Path Synopsis
Package cbridge wraps Box2D v3 (C) via CGO for the physics2d plugin.
Package cbridge wraps Box2D v3 (C) via CGO for the physics2d plugin.

Jump to

Keyboard shortcuts

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