gokg

package module
v1.3.4 Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2026 License: MIT Imports: 8 Imported by: 0

README

GOKG

aka "Golang kjkrol Geometry"

GOKG is a Go toolkit focused on practical 2D computational geometry. It splits into two packages: geom supplies numeric vectors (Vec), axis-aligned bounding boxes (AABB), and vector math; plane wraps those primitives in boundary-aware Space2D implementations (Euclidean2D and Toroidal2D) that keep boxes canonical to a surface. plane.AABB caches size and fragments so translations, wraps, and clamps obey the space’s rules without duplicating bookkeeping. The library stays focused on mathematical geometry; rendering or UI concerns live in neighbouring packages.

Space2D boundary handling

  • Euclidean2D spaces clamp boxes to the viewport while keeping their size consistent, so expansions never bleed beyond the defined world.

  • Toroidal2D spaces automatically wrap boxes that cross an edge and split them into fragments that continue on the opposite side, making toroidal worlds easy to model. Conceptually, glue the top edge of the plane to the bottom edge, then the left edge to the right; this seam-stitching turns the rectangle into the torus shown in the animation below.

    Torus

  • The helper methods Translate and Expand renormalise boxes on every call, updating cached fragments and ensuring touch/collision queries remain accurate without extra bookkeeping.

Usage example

This snippet shifts a contiguous plane.AABB by (-1,-1) across a 10×10 toroidal Space2D, so the box wraps past the right and bottom edges and automatically splits into the fragments returned by Fragments(). The exact situation is illustrated by the plot below.

Wrapped AABB fragments

package main

import (
	"fmt"

	"github.com/kjkrol/gokg/pkg/geom"
	"github.com/kjkrol/gokg/pkg/plane"
)

// Demonstrates how shifting a contiguous aabb beyond the toroidal plane boundary
// causes it to fragment into multiple wrapped pieces and prints those fragments.
func main() {
        toroidal := plane.NewToroidal2D(10, 10)

        box := geom.NewAABBAt(geom.NewVec(0, 0), 2, 2)
        aabb := toroidal.WrapAABB(box)

        shift := geom.NewVec(-1, -1)
        toroidal.Translate(&aabb, shift)

	fragments := aabb.Fragments()
	if len(fragments) < 3 {
		fmt.Printf("Unexpected fragment count (%d)\n", len(fragments))
		return
	}

	fmt.Printf("New position: %s\n", aabb)
	if fragment, ok := fragments[plane.FRAG_RIGHT]; ok {
		fmt.Printf("- Fragment %d: %s\n", plane.FRAG_RIGHT, fragment)
	}
	if fragment, ok := fragments[plane.FRAG_BOTTOM]; ok {
		fmt.Printf("- Fragment %d: %s\n", plane.FRAG_BOTTOM, fragment)
	}
	if fragment, ok := fragments[plane.FRAG_BOTTOM_RIGHT]; ok {
		fmt.Printf("- Fragment %d: %s\n", plane.FRAG_BOTTOM_RIGHT, fragment)
	}
	// Output:
	// New position: {(9,9) (10,10)}
	// - Fragment 0: {(0,9) (1,10)}
	// - Fragment 1: {(9,0) (10,1)}
	// - Fragment 2: {(0,0) (1,1)}
}

For more scenarios, browse the example-based tests under pkg/plane and pkg/geom, which double as runnable documentation.

Projects using GOKG

  • gokqGOKQ is a quadtree utility library that relies on geom.Vec, geom.AABB, and plane.AABB operations.
  • gokxGOKX is a Go library that provides a lightweight experimental framework for 2D graphics applications.

*Contributor Recommendations

Documentation

Index

Constants

View Source
const (
	// Plain is an entity that is nothing but geometry — see [spatial.Plain].
	Plain = spatial.Plain
	// AnyCapability accepts every entity — see [spatial.AnyCapability].
	AnyCapability = spatial.AnyCapability
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Capability added in v1.3.4

type Capability = spatial.Capability

Capability is what may be done with an entity — see spatial.Capability.

type Config

type Config struct {
	// Width of the physical simulation world.
	Width uint32
	// Height of the physical simulation world.
	Height uint32
	// Toroidal determines if the world wraps around its edges (true) or clamps them (false).
	Toroidal bool
	// BucketSize determines the resolution of a single cell in the spatial grid.
	BucketSize spatial.Resolution
	// BucketCapacity is the initial number of entities a single grid bucket can hold before allocating more memory.
	BucketCapacity int
	// OpsBufferSize is the size of the channel buffer used for queuing spatial index updates.
	OpsBufferSize int
	// TrackBucketDeltas records per-bucket entity movement for later
	// consumption. Off by default — see spatial.GridIndexConfig.
	TrackBucketDeltas bool
}

Config defines the properties of the Space.

type Space

type Space struct {
	Config
	// contains filtered or unexported fields
}

Space represents the main physical domain of the simulation. It acts as a facade that synchronizes boundary-aware geometry (plane.Space2D) with a highly optimized spatial index (spatial.GridIndexManager), providing a single, safe entry point for entity manipulation and querying.

func NewSpace

func NewSpace(cfg Config) (*Space, error)

NewSpace constructs a new Space with the given Config. It automatically handles asymmetric world dimensions by fitting them into the nearest power-of-two spatial grid internally, keeping the API simple and hiding complex topology.

func (*Space) Bounds added in v1.3.3

func (w *Space) Bounds() (width, height uint32, toroidal bool)

Bounds reports the world's dimensions and whether it wraps at its edges.

func (*Space) EntryAABB added in v1.3.3

func (w *Space) EntryAABB(id uid.UID64) (geom.AABB, bool)

EntryAABB returns the indexed box of a single entity.

func (*Space) Expand

func (w *Space) Expand(id uid.UID64, aabb *plane.AABB, margin float64)

Expand grows or shrinks the given AABB by the specified margin, and immediately queues an update to the spatial index.

func (*Space) ExpandOnly added in v1.2.11

func (w *Space) ExpandOnly(aabb *plane.AABB, margin float64)

ExpandOnly geometrically expands the AABB without updating the spatial index. This is useful for creating temporary probe boxes for broad-phase queries.

func (*Space) Flush

func (w *Space) Flush(onDirty func(geom.AABB))

Flush processes all pending queued operations (Insert, Remove, Translate, Expand) and applies them to the underlying bucket grid. The onDirty callback is invoked for every modified bucket area, which is useful for triggering visual redraws.

func (*Space) Insert

func (w *Space) Insert(id uid.UID64, aabb plane.AABB)

Insert adds a new entity to the space. It first normalizes the AABB according to the Space topology (e.g., wraps it if Toroidal) and then queues it for insertion into the spatial grid.

func (*Space) Neighbours added in v1.3.4

func (w *Space) Neighbours(box *plane.AABB, margin float64, want Capability, fn func(id uid.UID64, frag plane.FragPosition))

Neighbours calls fn for every entity within margin of box that shares a capability with want, box's own wrapped images included — without which a probe straddling a seam silently misses whatever lies across it.

box is the caller's scratch: it is expanded in place, so hand in a buffer held across ticks rather than a fresh copy each time. fn may see the same entity twice only if that entity is itself indexed in more than one image.

fn takes the same arguments Query's collector does, so it can be handed straight through rather than wrapped — a wrapper here would be one more indirect call per candidate found, on the hottest path there is.

func (*Space) Query

func (w *Space) Query(aabb geom.AABB, fn func(id uid.UID64, frag plane.FragPosition)) int

Query searches the spatial grid for all entities intersecting the provided AABB. The collector function fn is called for every entity found, providing its ID and the exact fragment that was hit.

func (*Space) Reindex added in v1.3.4

func (w *Space) Reindex(id uid.UID64, aabb plane.AABB)

Reindex queues one spatial-index update for a box already moved by TranslateOnly, so the index catches up with the geometry at the next Flush.

func (*Space) Remove

func (w *Space) Remove(id uid.UID64)

Remove queues the entity with the given ID for removal from the spatial grid.

func (*Space) Resolve added in v1.3.4

func (w *Space) Resolve(s *collide.Solver, iterations int, onContact func(i int, pen geom.Vec))

Resolve pushes the pairs gathered in s apart under this space's boundary rules — see collide.Solver.Solve. The index is not told anything: walk s.VisitMoved afterwards and Reindex what settled somewhere new.

func (*Space) Scan added in v1.3.3

func (w *Space) Scan(observer uid.UID64, cone raycast.Cone, v *raycast.View) bool

Scan fills v with what observer sees through cone, and reports whether the query was answerable. Read the result off v as entities, as an outline, or as both — see raycast.View. Keeping v across ticks reuses its buffers.

func (*Space) SetCapabilities added in v1.3.4

func (w *Space) SetCapabilities(id uid.UID64, c Capability)

SetCapabilities records what may be done with id, so a query for a capability can skip everything without it before reading any geometry.

Takes effect at the next Flush. Call it once per entity, after Insert — it is not a per-tick operation.

func (*Space) Translate

func (w *Space) Translate(id uid.UID64, aabb *plane.AABB, delta geom.Vec)

Translate moves the given AABB by the specified delta, recalculates its fragments based on the boundary rules, and queues a spatial index update to reflect the new position.

func (*Space) TranslateOnly added in v1.3.4

func (w *Space) TranslateOnly(aabb *plane.AABB, delta geom.Vec)

TranslateOnly moves the AABB under the space's boundary rules without queuing an index update — the counterpart of ExpandOnly. Use it when a box is moved repeatedly before it settles (an iterative contact solver, say): only where it ends up is worth indexing, and queuing every intermediate step can outrun OpsBufferSize. Pair it with Reindex once the box is final.

func (*Space) WrapAABB added in v1.3.2

func (w *Space) WrapAABB(aabb geom.AABB) plane.AABB

WrapAABB folds an arbitrary (possibly out-of-bounds) AABB into the space's canonical bounds — for a toroidal space this may split it into up to three additional wrapped fragments (Frags/FragMask), exactly as Insert/Translate already do internally for entity positions. Use this to build a valid Query box out of a rectangle that isn't already known to be canonical (e.g. one derived from screen coordinates).

Directories

Path Synopsis
Package collide separates overlapping boxes.
Package collide separates overlapping boxes.
Package geom provides generic 2D geometry primitives shared across the GOK modules.
Package geom provides generic 2D geometry primitives shared across the GOK modules.
Package plane defines 2D spaces (cartesian and torus) plus plane-aware boxes and metrics.
Package plane defines 2D spaces (cartesian and torus) plus plane-aware boxes and metrics.
Package raycast answers "what can this entity see" over a spatial index: every entity within a cone and range, minus those hidden behind nearer ones.
Package raycast answers "what can this entity see" over a spatial index: every entity within a cone and range, minus those hidden behind nearer ones.
Package spatial provides a discrete spatial index over a 2D power-of-two grid for storing and querying objects by integer coordinates, with support for range queries (AABB) and bulk operations.
Package spatial provides a discrete spatial index over a 2D power-of-two grid for storing and querying objects by integer coordinates, with support for range queries (AABB) and bulk operations.

Jump to

Keyboard shortcuts

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