Skip to content

kids.kapish.maths

unity

Unity-native geometry primitives and algorithms. Bezier curves, polygon triangulation, and related utilities using Vector2/Vector3.

Architecture

The package provides pure-C# geometry primitives that operate on Unity's Vector2/Vector3 types. No MonoBehaviours, no GameObjects -- just data structures and algorithms suitable for use from any context.

Bezier Curves

The core primitive is Bezier, an immutable 2D Bezier curve supporting three types:

TypeControl PointsConstruction
Linear2 (start, end)Bezier.Linear(start, end)
Quadratic3 (start, handle, end)Bezier.Quadratic(start, handle, end)
Cubic4 (start, h1, h2, end)Bezier.Cubic(start, h1, h2, end)

Each curve computes its bounding Rect at construction time (analytically, not via sampling) and pre-tessellates into a polyline. Tessellation defaults to adaptive mode -- recursive midpoint subdivision that places more points on tight curves and fewer on straight sections. The flatness tolerance defaults to 0.5 world units.

Key operations:

  • Solve(t) -- evaluate position at parameter t.
  • Derivative(t) / GetDirection(t) -- tangent at t.
  • Subdivide(t) -- De Casteljau split into two sub-curves.
  • TessellatedPoints -- cached polyline as ReadOnlySpan<Vector2>.
  • ArcLength -- approximated from tessellated segments.
  • Tessellate(settings) -- on-demand re-tessellation with different settings.

Composite Structures

TypePurpose
BezierLoopClosed loop of curves. Auto-closes with a linear segment if needed. Point-in-loop via winding number. Extracts tessellated polygon.
BezierStripOpen path of curves. Extracts tessellated polyline.
BezierMultiLoopCompound shape (outers + holes). Combined winding number containment test.
BezierFactoryFluent builder -- AddLinear/Quadratic/Cubic then BuildLoop() or BuildStrip().

All composite types implement IReadOnlyList<Bezier> for iteration and indexing.

Tessellation Settings

BezierTessellationSettings controls how curves are converted to polylines:

ModeDescription
LinearStepsFixed number of evenly-spaced points. Predictable but not curvature-aware.
AdaptiveErrorRecursive midpoint subdivision. Splits where deviation exceeds MaxError. Optimal point density. Default.

Convenience factories: BezierTessellationSettings.Linear(steps) and BezierTessellationSettings.Adaptive(maxError, maxDepth).

Polygon Utilities

PolygonUtils provides standalone polygon operations:

  • SignedArea -- shoelace formula. Positive = CCW, negative = CW.
  • GetWindingDirection -- CW or CCW from signed area.
  • Contains -- ray-casting even-odd point-in-polygon test.
  • ComputeBounds -- axis-aligned bounding rect.
  • Centroid -- vertex average.
  • Perimeter -- closed polygon edge length sum.

All methods accept both ReadOnlySpan<Vector2> and IReadOnlyList<Vector2>.

Triangulation

EarClipTriangulator implements ear-clipping triangulation for simple (non-self-intersecting) polygons. Auto-detects winding direction and normalises to CCW before processing. Returns triangle indices into the original vertex array.

TriangulationExtensions bridges Bezier geometry to triangulation: call .Triangulate() on a BezierLoop or BezierMultiLoop to get triangle indices and vertices in one step.

Interop

MathsInterop provides extension methods for converting between the Carrot.Maths DLL's platform-independent types (Point2f, Vector2f, Vector3f, Bounds2f) and Unity-native types (Vector2, Vector3, Rect). All conversions are AggressiveInlining for zero overhead.

Assembly

  • Assembly name: Carrot.Geometry
  • Root namespace: Carrot.Geometry
  • References: Carrot.Precompiled

Key Design Decisions

  • Immutable curves. Bezier instances are constructed once and never mutated. Tessellation is cached at construction. Re-tessellation with different settings returns a new array without modifying the instance.
  • Adaptive tessellation by default. Produces visually smooth curves with minimal point count. The 0.5 world-unit flatness tolerance is a good general default; use Adaptive(0.1f) for high-detail rendering.
  • Analytical bounds. Bounding rects are computed by solving the derivative for extrema, not by sampling -- accurate regardless of tessellation resolution.
  • Span-first API. Core methods accept ReadOnlySpan<Vector2> for zero-allocation polygon processing. IReadOnlyList<Vector2> overloads copy to a temporary array.
  • Winding number containment. BezierLoop.Contains uses the winding number algorithm (not ray-casting) for correct results with self-overlapping loops.

Usage Guide

Unity-native geometry primitives and algorithms. Bezier curves, polygon triangulation, and related utilities using Vector2/Vector3.

Setup

Add kids.kapish.maths to your Unity project's package manifest.

Common Patterns

1. Create a Bezier curve

csharp
using Carrot.Geometry;
using UnityEngine;

// Linear segment
Bezier line = Bezier.Linear(new Vector2(0, 0), new Vector2(10, 0));

// Quadratic curve
Bezier quad = Bezier.Quadratic(
    new Vector2(0, 0),
    new Vector2(5, 10),   // control handle
    new Vector2(10, 0));

// Cubic curve
Bezier cubic = Bezier.Cubic(
    new Vector2(0, 0),
    new Vector2(3, 10),   // handle 1
    new Vector2(7, 10),   // handle 2
    new Vector2(10, 0));

2. Evaluate and query a curve

csharp
using Carrot.Geometry;

Bezier curve = Bezier.Cubic(start, h1, h2, end);

Vector2 midpoint  = curve.Solve(0.5f);
Vector2 tangent   = curve.GetDirection(0.5f);
float   arcLength = curve.ArcLength;
Rect    bounds    = curve.Bounds;

3. Subdivide a curve

csharp
using Carrot.Geometry;

(Bezier left, Bezier right) = curve.Subdivide(0.5f);

4. Custom tessellation

csharp
using Carrot.Geometry;

// High-detail adaptive tessellation
var settings = BezierTessellationSettings.Adaptive(maxError: 0.1f);
Bezier curve = Bezier.Cubic(start, h1, h2, end, settings);

// Or uniform steps
var uniform = BezierTessellationSettings.Linear(steps: 20);
Vector2[] points = curve.Tessellate(uniform);

5. Build a closed loop

csharp
using Carrot.Geometry;

var factory = new BezierFactory();
factory.AddCubic(p0, h0a, h0b, p1)
       .AddCubic(p1, h1a, h1b, p2)
       .AddCubic(p2, h2a, h2b, p0);

BezierLoop loop = factory.BuildLoop();

// Point-in-loop test
bool inside = loop.Contains(new Vector2(5, 5));

// Perimeter
float perimeter = loop.Perimeter;

6. Build an open strip

csharp
using Carrot.Geometry;

BezierStrip strip = new BezierFactory()
    .AddCubic(p0, h0a, h0b, p1)
    .AddCubic(p1, h1a, h1b, p2)
    .BuildStrip();

Vector2[] polyline = strip.ExtractPolyline();
float length = strip.ArcLength;

7. Compound shapes with holes

csharp
using Carrot.Geometry;

BezierLoop outer = new BezierLoop(outerCurves);
BezierLoop hole  = new BezierLoop(holeCurves); // CCW winding

var multiLoop = new BezierMultiLoop(new[] { outer, hole });
bool inside = multiLoop.Contains(testPoint);

8. Triangulate a Bezier loop

csharp
using Carrot.Geometry;
using Carrot.Geometry.Triangulation;

BezierLoop loop = /* ... */;

// Get vertices and triangle indices
int[] indices = loop.Triangulate(out Vector2[] vertices);

9. Triangulate a raw polygon

csharp
using Carrot.Geometry.Triangulation;
using UnityEngine;

Vector2[] polygon = { new(0,0), new(10,0), new(10,10), new(0,10) };
int[] indices = EarClipTriangulator.Triangulate((ReadOnlySpan<Vector2>)polygon);

10. Polygon utilities

csharp
using Carrot.Geometry;
using UnityEngine;

Vector2[] polygon = { new(0,0), new(10,0), new(10,10), new(0,10) };

float area  = PolygonUtils.SignedArea(polygon);
bool inside = PolygonUtils.Contains(polygon, new Vector2(5, 5));
Rect bounds = PolygonUtils.ComputeBounds(polygon);
Vector2 center = PolygonUtils.Centroid(polygon);
WindingDirection winding = PolygonUtils.GetWindingDirection(polygon);

11. Convert between Carrot.Maths and Unity types

csharp
using Carrot.Geometry.Interop;
using UnityEngine;

Vector2 unityVec = someMathsPoint.ToVector2();
Carrot.Maths.Geometry.Point2f mathsPoint = unityVec.ToPoint2f();

// Bulk conversion
Vector2[] unityArray = mathsPointArray.ToVector2Array();

Tips

  • Adaptive is the default. Unless you need a predictable point count, stick with the default tessellation.
  • Tessellation is cached. The TessellatedPoints property returns a ReadOnlySpan<Vector2> over the cached array -- no allocation on access.
  • Loops auto-close. If the last curve's endpoint doesn't match the first curve's start, BezierLoop adds a closing linear segment automatically.
  • Winding matters for holes. In BezierMultiLoop, outer loops should wind CW and holes CCW (or vice versa) for correct containment testing.

Carrot