scene/shape.go

Functions Structs Interfaces

Functions

func AddShape

AddShape adds a shape to the composite.

func (cs *CompositeShape) AddShape(shape Shape) {
	cs.shapes = append(cs.shapes, shape)
}

func Bounds

Bounds returns the bounding rectangle.

func (rp *RegularPolygonShape) Bounds() Rect {
	// For simplicity, use the circumscribed circle bounds
	// More accurate bounds would require computing all vertices
	return Rect{
		MinX:	rp.CX - rp.R,
		MinY:	rp.CY - rp.R,
		MaxX:	rp.CX + rp.R,
		MaxY:	rp.CY + rp.R,
	}
}

func Bounds

Bounds returns the bounding rectangle.

func (p *PolygonShape) Bounds() Rect {
	if len(p.points) < 2 {
		return EmptyRect()
	}

	bounds := Rect{
		MinX:	p.points[0],
		MinY:	p.points[1],
		MaxX:	p.points[0],
		MaxY:	p.points[1],
	}

	for i := 2; i < len(p.points); i += 2 {
		bounds = bounds.UnionPoint(p.points[i], p.points[i+1])
	}

	return bounds
}

func Bounds

Bounds returns the bounding rectangle.

func (r *RectShape) Bounds() Rect {
	return Rect{
		MinX:	r.X,
		MinY:	r.Y,
		MaxX:	r.X + r.Width,
		MaxY:	r.Y + r.Height,
	}
}

func Bounds

Bounds returns the bounding rectangle.

func (l *LineShape) Bounds() Rect {
	return Rect{
		MinX:	min32(l.X1, l.X2),
		MinY:	min32(l.Y1, l.Y2),
		MaxX:	max32(l.X1, l.X2),
		MaxY:	max32(l.Y1, l.Y2),
	}
}

func Bounds

Bounds returns a conservative bounding rectangle.

func (a *ArcShape) Bounds() Rect {
	// Conservative: use the full ellipse bounds
	// More accurate bounds would compute arc extrema
	return Rect{
		MinX:	a.CX - a.RX,
		MinY:	a.CY - a.RY,
		MaxX:	a.CX + a.RX,
		MaxY:	a.CY + a.RY,
	}
}

func Bounds

Bounds returns a conservative bounding rectangle.

func (p *PieShape) Bounds() Rect {
	// Conservative: use the full circle bounds
	return Rect{
		MinX:	p.CX - p.R,
		MinY:	p.CY - p.R,
		MaxX:	p.CX + p.R,
		MaxY:	p.CY + p.R,
	}
}

func Bounds

Bounds returns the bounding rectangle.

func (s *RoundRectShape) Bounds() Rect {
	return s.Rect
}

func Bounds

Bounds returns the bounding rectangle.

func (r *RoundedRectShape) Bounds() Rect {
	return Rect{
		MinX:	r.X,
		MinY:	r.Y,
		MaxX:	r.X + r.Width,
		MaxY:	r.Y + r.Height,
	}
}

func Bounds

Bounds returns the bounding rectangle of the path.

func (ps *PathShape) Bounds() Rect {
	if ps.path == nil {
		return EmptyRect()
	}
	return ps.path.Bounds()
}

func Bounds

Bounds returns the transformed bounding rectangle.

Note: This is a conservative approximation.

func (ts *TransformShape) Bounds() Rect {
	if ts.shape == nil {
		return EmptyRect()
	}

	b := ts.shape.Bounds()
	if b.IsEmpty() {
		return b
	}

	// Transform all four corners and compute new bounds
	corners := [][2]float32{
		{b.MinX, b.MinY},
		{b.MaxX, b.MinY},
		{b.MaxX, b.MaxY},
		{b.MinX, b.MaxY},
	}

	result := EmptyRect()
	for _, c := range corners {
		x, y := ts.transform.TransformPoint(c[0], c[1])
		result = result.UnionPoint(x, y)
	}

	return result
}

func Bounds

Bounds returns the bounding rectangle.

func (s *StarShape) Bounds() Rect {
	// Use outer radius for bounds (conservative)
	return Rect{
		MinX:	s.CX - s.OuterRadius,
		MinY:	s.CY - s.OuterRadius,
		MaxX:	s.CX + s.OuterRadius,
		MaxY:	s.CY + s.OuterRadius,
	}
}

func Bounds

Bounds returns the bounding rectangle.

func (c *CircleShape) Bounds() Rect {
	return Rect{
		MinX:	c.CX - c.R,
		MinY:	c.CY - c.R,
		MaxX:	c.CX + c.R,
		MaxY:	c.CY + c.R,
	}
}

func Bounds

Bounds returns the bounding rectangle.

func (e *EllipseShape) Bounds() Rect {
	return Rect{
		MinX:	e.CX - e.RX,
		MinY:	e.CY - e.RY,
		MaxX:	e.CX + e.RX,
		MaxY:	e.CY + e.RY,
	}
}

func Bounds

Bounds returns the union of all shape bounds.

func (cs *CompositeShape) Bounds() Rect {
	result := EmptyRect()
	for _, shape := range cs.shapes {
		if shape != nil {
			result = result.Union(shape.Bounds())
		}
	}
	return result
}

func BuildPathInto

BuildPathInto builds the ellipse path into an existing Path.

func (e *EllipseShape) BuildPathInto(p *Path) {
	p.Reset()
	p.Ellipse(e.CX, e.CY, e.RX, e.RY)
}

func BuildPathInto

BuildPathInto builds the rectangle path into an existing Path, avoiding allocation.

func (r *RectShape) BuildPathInto(p *Path) {
	p.Reset()
	p.Rectangle(r.X, r.Y, r.Width, r.Height)
}

func BuildPathInto

BuildPathInto builds the circle path into an existing Path.

func (c *CircleShape) BuildPathInto(p *Path) {
	p.Reset()
	p.Circle(c.CX, c.CY, c.R)
}

func BuildPathInto

BuildPathInto builds the rounded rectangle path into an existing Path.

func (r *RoundedRectShape) BuildPathInto(p *Path) {
	p.Reset()
	p.RoundedRectangle(r.X, r.Y, r.Width, r.Height, r.Radius)
}

func BuildPathInto

BuildPathInto builds the polygon path into an existing Path.

func (p *PolygonShape) BuildPathInto(dst *Path) {
	dst.Reset()
	if len(p.points) < 4 {
		return
	}
	dst.MoveTo(p.points[0], p.points[1])
	for i := 2; i < len(p.points); i += 2 {
		dst.LineTo(p.points[i], p.points[i+1])
	}
	dst.Close()
}

func BuildPathInto

BuildPathInto builds the line path into an existing Path.

func (l *LineShape) BuildPathInto(p *Path) {
	p.Reset()
	p.MoveTo(l.X1, l.Y1)
	p.LineTo(l.X2, l.Y2)
}

func Contains

Contains returns true if the point (px, py) is inside the rectangle.

func (r *RectShape) Contains(px, py float32) bool {
	return px >= r.X && px <= r.X+r.Width &&
		py >= r.Y && py <= r.Y+r.Height
}

func Contains

Contains returns true if the point (px, py) is inside the ellipse.

func (e *EllipseShape) Contains(px, py float32) bool {
	if e.RX == 0 || e.RY == 0 {
		return false
	}
	dx := (px - e.CX) / e.RX
	dy := (py - e.CY) / e.RY
	return dx*dx+dy*dy <= 1
}

func Contains

Contains returns true if the point (px, py) is inside the circle.

func (c *CircleShape) Contains(px, py float32) bool {
	dx := px - c.CX
	dy := py - c.CY
	return dx*dx+dy*dy <= c.R*c.R
}

func Contains

Contains returns true if the point (px, py) is inside the rounded rectangle,

using SDF-based point containment for accurate corner testing.

func (s *RoundRectShape) Contains(px, py float32) bool {
	// Quick AABB reject
	if px < s.Rect.MinX || px > s.Rect.MaxX || py < s.Rect.MinY || py > s.Rect.MaxY {
		return false
	}

	cx := (s.Rect.MinX + s.Rect.MaxX) / 2
	cy := (s.Rect.MinY + s.Rect.MaxY) / 2
	halfW := s.Rect.Width() / 2
	halfH := s.Rect.Height() / 2
	r := min32(s.RadiusX, s.RadiusY)

	dist := sdfRoundRect(px, py, cx, cy, halfW, halfH, r)
	return dist <= 0
}

func Length

Length returns the length of the line segment.

func (l *LineShape) Length() float32 {
	dx := l.X2 - l.X1
	dy := l.Y2 - l.Y1
	return float32(math.Sqrt(float64(dx*dx + dy*dy)))
}

func NewArcShape

NewArcShape creates a new arc shape.

func NewArcShape(cx, cy, rx, ry, startAngle, endAngle float32, sweepClockwise bool) *ArcShape {
	return &ArcShape{
		CX:	cx, CY: cy, RX: rx, RY: ry,
		StartAngle:	startAngle, EndAngle: endAngle,
		SweepClockwise:	sweepClockwise,
	}
}

func NewCircleShape

NewCircleShape creates a new circle shape.

func NewCircleShape(cx, cy, r float32) *CircleShape {
	return &CircleShape{CX: cx, CY: cy, R: r}
}

func NewCompositeShape

NewCompositeShape creates a new composite shape.

func NewCompositeShape(shapes ...Shape) *CompositeShape {
	return &CompositeShape{shapes: shapes}
}

func NewEllipseShape

NewEllipseShape creates a new ellipse shape.

func NewEllipseShape(cx, cy, rx, ry float32) *EllipseShape {
	return &EllipseShape{CX: cx, CY: cy, RX: rx, RY: ry}
}

func NewGGPathShape

NewGGPathShape creates a PathShape from a gg.Path (float64 → float32 conversion).

Use this when working with paths from gg APIs (e.g., gg.ParseSVGPath).

func NewGGPathShape(ggPath *gg.Path) *PathShape {
	if ggPath == nil || ggPath.NumVerbs() == 0 {
		return &PathShape{path: NewPath()}
	}
	p := NewPath()
	ggPath.Iterate(func(verb gg.PathVerb, coords []float64) {
		switch verb {
		case gg.MoveTo:
			p.MoveTo(float32(coords[0]), float32(coords[1]))
		case gg.LineTo:
			p.LineTo(float32(coords[0]), float32(coords[1]))
		case gg.QuadTo:
			p.QuadTo(float32(coords[0]), float32(coords[1]), float32(coords[2]), float32(coords[3]))
		case gg.CubicTo:
			p.CubicTo(float32(coords[0]), float32(coords[1]), float32(coords[2]), float32(coords[3]), float32(coords[4]), float32(coords[5]))
		case gg.Close:
			p.Close()
		}
	})
	return &PathShape{path: p}
}

func NewLineShape

NewLineShape creates a new line shape.

func NewLineShape(x1, y1, x2, y2 float32) *LineShape {
	return &LineShape{X1: x1, Y1: y1, X2: x2, Y2: y2}
}

func NewPathShape

NewPathShape creates a new path shape.

func NewPathShape(path *Path) *PathShape {
	return &PathShape{path: path}
}

func NewPieShape

NewPieShape creates a new pie shape.

func NewPieShape(cx, cy, r, startAngle, endAngle float32, sweepClockwise bool) *PieShape {
	return &PieShape{
		CX:	cx, CY: cy, R: r,
		StartAngle:	startAngle, EndAngle: endAngle,
		SweepClockwise:	sweepClockwise,
	}
}

func NewPolygonShape

NewPolygonShape creates a new polygon shape from a list of points.

Points should be provided as x, y pairs.

func NewPolygonShape(points ...float32) *PolygonShape {
	if len(points)%2 != 0 {
		// Ignore the last point if odd number
		points = points[:len(points)-1]
	}
	ps := &PolygonShape{
		points: make([]float32, len(points)),
	}
	copy(ps.points, points)
	return ps
}

func NewRectShape

NewRectShape creates a new rectangle shape.

func NewRectShape(x, y, width, height float32) *RectShape {
	return &RectShape{X: x, Y: y, Width: width, Height: height}
}

func NewRegularPolygonShape

NewRegularPolygonShape creates a new regular polygon shape.

func NewRegularPolygonShape(cx, cy, r float32, sides int, rotation float32) *RegularPolygonShape {
	if sides < 3 {
		sides = 3
	}
	return &RegularPolygonShape{
		CX:	cx, CY: cy, R: r, Sides: sides, Rotation: rotation,
	}
}

func NewRoundRectShape

NewRoundRectShape creates a new rounded rectangle shape with independent X/Y corner radii.

Radii are clamped to half the rectangle's width/height respectively.

func NewRoundRectShape(rect Rect, rx, ry float32) *RoundRectShape {
	rx = min32(rx, rect.Width()/2)
	ry = min32(ry, rect.Height()/2)
	if rx < 0 {
		rx = 0
	}
	if ry < 0 {
		ry = 0
	}
	return &RoundRectShape{Rect: rect, RadiusX: rx, RadiusY: ry}
}

func NewRoundRectShapeUniform

NewRoundRectShapeUniform creates a new rounded rectangle shape with uniform corner radius.

The radius is clamped to half the smaller dimension.

func NewRoundRectShapeUniform(rect Rect, r float32) *RoundRectShape {
	return NewRoundRectShape(rect, r, r)
}

func NewRoundedRectShape

NewRoundedRectShape creates a new rounded rectangle shape.

func NewRoundedRectShape(x, y, width, height, radius float32) *RoundedRectShape {
	return &RoundedRectShape{
		X:	x, Y: y, Width: width, Height: height, Radius: radius,
	}
}

func NewStarShape

NewStarShape creates a new star shape.

func NewStarShape(cx, cy, outerRadius, innerRadius float32, points int, rotation float32) *StarShape {
	if points < 3 {
		points = 5	// Default to 5-pointed star
	}
	return &StarShape{
		CX:	cx, CY: cy,
		OuterRadius:	outerRadius, InnerRadius: innerRadius,
		Points:	points, Rotation: rotation,
	}
}

func NewTransformShape

NewTransformShape creates a transformed shape.

func NewTransformShape(shape Shape, transform Affine) *TransformShape {
	return &TransformShape{shape: shape, transform: transform}
}

func Point

Point returns the i-th vertex of the polygon.

func (p *PolygonShape) Point(i int) (x, y float32, ok bool) {
	idx := i * 2
	if idx < 0 || idx+1 >= len(p.points) {
		return 0, 0, false
	}
	return p.points[idx], p.points[idx+1], true
}

func PointCount

PointCount returns the number of vertices in the polygon.

func (p *PolygonShape) PointCount() int {
	return len(p.points) / 2
}

func ShapeCount

ShapeCount returns the number of shapes in the composite.

func (cs *CompositeShape) ShapeCount() int {
	return len(cs.shapes)
}

func ToPath

ToPath converts the line to a Path.

func (l *LineShape) ToPath() *Path {
	return NewPath().MoveTo(l.X1, l.Y1).LineTo(l.X2, l.Y2)
}

func ToPath

ToPath converts the shape to a transformed Path.

func (ts *TransformShape) ToPath() *Path {
	if ts.shape == nil {
		return NewPath()
	}
	return ts.shape.ToPath().Transform(ts.transform)
}

func ToPath

ToPath converts the ellipse to a Path.

func (e *EllipseShape) ToPath() *Path {
	return NewPath().Ellipse(e.CX, e.CY, e.RX, e.RY)
}

func ToPath

ToPath converts the regular polygon to a Path.

func (rp *RegularPolygonShape) ToPath() *Path {
	path := NewPath()

	angleStep := 2 * math.Pi / float64(rp.Sides)
	startAngle := float64(rp.Rotation) - math.Pi/2	// Start from top

	for i := 0; i < rp.Sides; i++ {
		angle := startAngle + angleStep*float64(i)
		x := rp.CX + rp.R*float32(math.Cos(angle))
		y := rp.CY + rp.R*float32(math.Sin(angle))

		if i == 0 {
			path.MoveTo(x, y)
		} else {
			path.LineTo(x, y)
		}
	}

	return path.Close()
}

func ToPath

ToPath converts the pie to a Path.

func (p *PieShape) ToPath() *Path {
	path := NewPath()

	// Start at center
	path.MoveTo(p.CX, p.CY)

	// Line to start of arc
	startX := p.CX + p.R*float32(math.Cos(float64(p.StartAngle)))
	startY := p.CY + p.R*float32(math.Sin(float64(p.StartAngle)))
	path.LineTo(startX, startY)

	// Add arc using the Path's Arc method (but we need the interior arc)
	// We'll manually add the arc segments
	arc := NewPath()
	arc.Arc(p.CX, p.CY, p.R, p.R, p.StartAngle, p.EndAngle, p.SweepClockwise)

	// Copy arc verbs and points (skip the initial MoveTo)
	for i, verb := range arc.verbs {
		if i == 0 && verb == MoveTo {
			continue	// Skip initial MoveTo
		}
		path.verbs = append(path.verbs, verb)
	}
	// Copy points (skip first 2 for the MoveTo)
	if len(arc.points) > 2 {
		path.points = append(path.points, arc.points[2:]...)
	}

	return path.Close()
}

func ToPath

ToPath converts the rounded rectangle to a Path.

func (s *RoundRectShape) ToPath() *Path {
	x := s.Rect.MinX
	y := s.Rect.MinY
	w := s.Rect.Width()
	h := s.Rect.Height()
	r := min32(s.RadiusX, s.RadiusY)
	return NewPath().RoundedRectangle(x, y, w, h, r)
}

func ToPath

ToPath converts the polygon to a Path.

func (p *PolygonShape) ToPath() *Path {
	if len(p.points) < 4 {	// Need at least 2 points (4 floats)
		return NewPath()
	}

	path := NewPath()
	path.MoveTo(p.points[0], p.points[1])

	for i := 2; i < len(p.points); i += 2 {
		path.LineTo(p.points[i], p.points[i+1])
	}

	return path.Close()
}

func ToPath

ToPath converts the arc to a Path.

func (a *ArcShape) ToPath() *Path {
	return NewPath().Arc(a.CX, a.CY, a.RX, a.RY, a.StartAngle, a.EndAngle, a.SweepClockwise)
}

func ToPath

ToPath converts the circle to a Path.

func (c *CircleShape) ToPath() *Path {
	return NewPath().Circle(c.CX, c.CY, c.R)
}

func ToPath

ToPath converts the rounded rectangle to a Path.

func (r *RoundedRectShape) ToPath() *Path {
	return NewPath().RoundedRectangle(r.X, r.Y, r.Width, r.Height, r.Radius)
}

func ToPath

ToPath returns the underlying path.

func (ps *PathShape) ToPath() *Path {
	return ps.path
}

func ToPath

ToPath converts all shapes to a single Path.

func (cs *CompositeShape) ToPath() *Path {
	result := NewPath()

	for _, shape := range cs.shapes {
		if shape == nil {
			continue
		}
		p := shape.ToPath()
		if p == nil || p.IsEmpty() {
			continue
		}

		// Append the path data
		result.verbs = append(result.verbs, p.verbs...)
		result.points = append(result.points, p.points...)
		result.bounds = result.bounds.Union(p.bounds)
	}

	return result
}

func ToPath

ToPath converts the rectangle to a Path.

func (r *RectShape) ToPath() *Path {
	return NewPath().Rectangle(r.X, r.Y, r.Width, r.Height)
}

func ToPath

ToPath converts the star to a Path.

func (s *StarShape) ToPath() *Path {
	path := NewPath()

	// Each star point alternates between outer and inner vertices
	angleStep := math.Pi / float64(s.Points)
	startAngle := float64(s.Rotation) - math.Pi/2	// Start from top

	for i := 0; i < s.Points*2; i++ {
		angle := startAngle + angleStep*float64(i)
		var r float32
		if i%2 == 0 {
			r = s.OuterRadius
		} else {
			r = s.InnerRadius
		}

		x := s.CX + r*float32(math.Cos(angle))
		y := s.CY + r*float32(math.Sin(angle))

		if i == 0 {
			path.MoveTo(x, y)
		} else {
			path.LineTo(x, y)
		}
	}

	return path.Close()
}

Structs

type RectShape struct

RectShape represents an axis-aligned rectangle.

type RectShape struct {
	X, Y		float32	// Top-left corner
	Width, Height	float32	// Dimensions
}

type RoundedRectShape struct

RoundedRectShape represents a rectangle with rounded corners.

type RoundedRectShape struct {
	X, Y		float32	// Top-left corner
	Width, Height	float32	// Dimensions
	Radius		float32	// Corner radius (same for all corners)
}

type CircleShape struct

CircleShape represents a circle.

type CircleShape struct {
	CX, CY	float32	// Center
	R	float32	// Radius
}

type EllipseShape struct

EllipseShape represents an axis-aligned ellipse.

type EllipseShape struct {
	CX, CY	float32	// Center
	RX, RY	float32	// Radii
}

type RoundRectShape struct

RoundRectShape represents a rounded rectangle with independent X/Y corner radii.

Unlike RoundedRectShape which only supports path-based rendering via ToPath(),

RoundRectShape supports dedicated SDF-based rendering in the tile renderer

for high-quality anti-aliased output without path encoding overhead.

type RoundRectShape struct {
	Rect			Rect	// Bounding rectangle
	RadiusX, RadiusY	float32	// Corner radii (clamped to half-width/height)
}

type LineShape struct

LineShape represents a line segment.

type LineShape struct {
	X1, Y1	float32	// Start point
	X2, Y2	float32	// End point
}

type PathShape struct

PathShape wraps a Path as a Shape.

type PathShape struct {
	path *Path
}

type PolygonShape struct

PolygonShape represents a closed polygon.

type PolygonShape struct {
	points []float32	// x1, y1, x2, y2, ...
}

type RegularPolygonShape struct

RegularPolygonShape represents a regular polygon (all sides equal length).

type RegularPolygonShape struct {
	CX, CY		float32	// Center
	R		float32	// Radius (distance from center to vertices)
	Sides		int	// Number of sides
	Rotation	float32	// Rotation angle in radians
}

type StarShape struct

StarShape represents a star shape.

type StarShape struct {
	CX, CY		float32	// Center
	OuterRadius	float32	// Outer radius (points)
	InnerRadius	float32	// Inner radius (valleys)
	Points		int	// Number of points
	Rotation	float32	// Rotation angle in radians
}

type ArcShape struct

ArcShape represents an arc (portion of an ellipse outline).

type ArcShape struct {
	CX, CY		float32	// Center
	RX, RY		float32	// Radii
	StartAngle	float32	// Start angle in radians
	EndAngle	float32	// End angle in radians
	SweepClockwise	bool	// Direction
}

type PieShape struct

PieShape represents a pie slice (wedge).

type PieShape struct {
	CX, CY		float32	// Center
	R		float32	// Radius
	StartAngle	float32	// Start angle in radians
	EndAngle	float32	// End angle in radians
	SweepClockwise	bool	// Direction
}

type TransformShape struct

TransformShape wraps a shape with a transformation.

type TransformShape struct {
	shape		Shape
	transform	Affine
}

type CompositeShape struct

CompositeShape combines multiple shapes into one.

type CompositeShape struct {
	shapes []Shape
}

Interfaces

type Shape interface

Shape is the interface for geometric shapes that can be converted to paths.

All shapes must be able to provide a path representation and their bounds.

type Shape interface {
	// ToPath converts the shape to a Path for encoding.
	ToPath() *Path

	// Bounds returns the bounding rectangle of the shape.
	Bounds() Rect
}

type PathBuilder interface

PathBuilder is an optional interface that shapes can implement to build

their path into an existing Path object, avoiding allocation of a new Path.

Scene.Fill/Stroke check for this interface and use a pooled Path when available.

type PathBuilder interface {
	// BuildPathInto resets p and builds the shape's path data into it.
	BuildPathInto(p *Path)
}