Appearance
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:
| Type | Control Points | Construction |
|---|---|---|
| Linear | 2 (start, end) | Bezier.Linear(start, end) |
| Quadratic | 3 (start, handle, end) | Bezier.Quadratic(start, handle, end) |
| Cubic | 4 (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 asReadOnlySpan<Vector2>.ArcLength-- approximated from tessellated segments.Tessellate(settings)-- on-demand re-tessellation with different settings.
Composite Structures
| Type | Purpose |
|---|---|
BezierLoop | Closed loop of curves. Auto-closes with a linear segment if needed. Point-in-loop via winding number. Extracts tessellated polygon. |
BezierStrip | Open path of curves. Extracts tessellated polyline. |
BezierMultiLoop | Compound shape (outers + holes). Combined winding number containment test. |
BezierFactory | Fluent 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:
| Mode | Description |
|---|---|
LinearSteps | Fixed number of evenly-spaced points. Predictable but not curvature-aware. |
AdaptiveError | Recursive 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.
Bezierinstances 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.Containsuses 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
TessellatedPointsproperty returns aReadOnlySpan<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,
BezierLoopadds 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.