Appearance
kids.kapish.meshes
unity
Mesh construction utilities. MeshBuilder, BezierMeshBuilder, StrokeBuilder, and shared shaders for 2D mesh rendering.
Architecture
The package provides a builder-pattern pipeline for constructing Unity Mesh objects from 2D geometry, with a focus on Bezier shapes and stroked outlines.
Builder Hierarchy
MeshBuilder -- low-level vertex/triangle accumulator
BezierMeshBuilder -- Bezier-to-mesh pipeline (wraps MeshBuilder)
StrokeBuilder -- polyline-to-triangle-strip pipeline (wraps MeshBuilder)All three builders are reusable via Clear().
MeshBuilder
The foundation layer. Accumulates Vector3 vertices, Color per-vertex colors, up to 4 UV channels (Vector2), and triangle indices. Key features:
- 2D-first.
AddVertex(Vector2)andAddVertex(float x, float y)default Z to 0. - Automatic 32-bit indices. Switches to
IndexFormat.UInt32when vertex count exceeds 65,535. - Lazy channel initialisation. Color and UV arrays are only allocated when first written. Padding with defaults (white / zero) is handled automatically.
- Bulk addition.
AddVertices(IReadOnlyList<Vector2>)returns a base index for subsequentAddTriangles(indices, baseVertex).
BezierMeshBuilder
Converts Bezier geometry into filled triangulated meshes:
- Calls
BezierLoop.ExtractPolygon()to tessellate curves into aVector2[]polygon. - Runs
EarClipTriangulator.Triangulate()on the polygon. - Feeds vertices and indices into the internal
MeshBuilder.
Supports uniform color, per-vertex color via Func<Vector2, Color> callback, multi-loops (outer loops filled, holes skipped), and pre-triangulated geometry bypass.
StrokeBuilder
Converts polylines into triangle-strip meshes with per-vertex variable width:
- Computes left/right offset vertices using averaged normals (miter joins) at interior vertices and perpendicular normals at endpoints.
- Clamps miter extension to 4x at acute angles to prevent spikes.
- Emits a triangle strip (two triangles per segment).
Supports open strokes, closed outlines, Bezier strips (auto-tessellated), and Bezier loop outlines. For Bezier strips with control-point-level widths, widths are linearly interpolated to match the tessellated point count.
UnlitVertexColor Shader
A URP-compatible shader at Carrot/UnlitVertexColor:
- Per-vertex color multiplied by
_Colortint. - Alpha blending (
SrcAlpha OneMinusSrcAlpha). - Z-write off, backface culling off -- designed for 2D overlays.
- Configurable stencil buffer support (
_StencilRef,_StencilComp,_StencilPass). - Two passes:
Universal2D(primary) andSRPDefaultUnlit(fallback for non-2D renderers). - Falls back to
Sprites/Defaultwhen URP is not available.
Access via MeshShaders.UnlitVertexColor (lazy-loaded) or create a material with MeshShaders.CreateUnlitVertexColorMaterial().
Assembly
- Assembly name:
Carrot.Meshes - Root namespace:
Carrot.Meshes - References:
Carrot,Carrot.Geometry,Carrot.Precompiled
Key Design Decisions
- Builder pattern, not ScriptableObject. Meshes are constructed imperatively, not configured via assets. This suits procedural geometry and code-driven rendering.
- No MonoBehaviour dependency. All builders are plain C# objects. You decide when and where to build.
- Reusable builders. Call
Clear()to reset and reuse without GC pressure from new allocations. - Miter clamping. The
StrokeBuilderclamps miter extension at very sharp angles (< ~75 degrees) to prevent visual spikes, trading geometric precision for visual quality. - Shared shader access.
MeshShaderslazy-loads the shader once and caches it. Thread-safe via Unity's main-thread-only shader system.
Usage Guide
Mesh construction utilities for building Unity meshes from 2D geometry, Bezier shapes, and stroked outlines.
Setup
Add kids.kapish.meshes to your Unity project's package manifest. This also requires kids.kapish.maths.
Common Patterns
1. Build a mesh from vertices and triangles
csharp
using Carrot.Meshes.Builders;
using UnityEngine;
var builder = new MeshBuilder();
int v0 = builder.AddVertex(new Vector2(0, 0), Color.red);
int v1 = builder.AddVertex(new Vector2(10, 0), Color.green);
int v2 = builder.AddVertex(new Vector2(5, 10), Color.blue);
builder.AddTriangle(v0, v1, v2);
Mesh mesh = builder.Build("MyTriangle");2. Build a mesh from a Bezier loop
csharp
using Carrot.Geometry;
using Carrot.Meshes.Builders;
using UnityEngine;
BezierLoop loop = new BezierFactory()
.AddCubic(p0, h0a, h0b, p1)
.AddCubic(p1, h1a, h1b, p2)
.AddCubic(p2, h2a, h2b, p0)
.BuildLoop();
var bezierBuilder = new BezierMeshBuilder();
bezierBuilder.AddFilledLoop(loop, Color.cyan);
Mesh mesh = bezierBuilder.Build("FilledShape");3. Per-vertex coloring on Bezier shapes
csharp
using Carrot.Geometry;
using Carrot.Meshes.Builders;
using UnityEngine;
var bezierBuilder = new BezierMeshBuilder();
bezierBuilder.AddFilledLoop(loop, vertex =>
{
// Gradient based on Y position
float t = Mathf.InverseLerp(loop.Bounds.yMin, loop.Bounds.yMax, vertex.y);
return Color.Lerp(Color.blue, Color.red, t);
});
Mesh mesh = bezierBuilder.Build();4. Build a stroke mesh
csharp
using Carrot.Meshes.Builders;
using UnityEngine;
var strokeBuilder = new StrokeBuilder();
Vector2[] points = { new(0, 0), new(5, 10), new(10, 0) };
strokeBuilder.AddStroke(points, width: 0.5f, Color.white);
Mesh mesh = strokeBuilder.Build("StrokeMesh");5. Variable-width stroke
csharp
using Carrot.Meshes.Builders;
using UnityEngine;
var strokeBuilder = new StrokeBuilder();
Vector2[] points = { new(0, 0), new(5, 10), new(10, 0) };
float[] widths = { 0.1f, 1.0f, 0.1f }; // tapered
strokeBuilder.AddStroke(points, widths, Color.yellow);
Mesh mesh = strokeBuilder.Build();6. Outline a Bezier loop
csharp
using Carrot.Geometry;
using Carrot.Meshes.Builders;
using UnityEngine;
BezierLoop loop = /* ... */;
var strokeBuilder = new StrokeBuilder();
strokeBuilder.AddBezierOutline(loop, width: 0.3f, Color.black);
Mesh mesh = strokeBuilder.Build("Outline");7. Stroke a Bezier strip
csharp
using Carrot.Geometry;
using Carrot.Meshes.Builders;
using UnityEngine;
BezierStrip strip = /* ... */;
var strokeBuilder = new StrokeBuilder();
strokeBuilder.AddBezierStroke(strip, width: 0.5f, Color.green);
Mesh mesh = strokeBuilder.Build();8. Use the UnlitVertexColor shader
csharp
using Carrot.Meshes;
using UnityEngine;
Material material = MeshShaders.CreateUnlitVertexColorMaterial();
// Assign to a MeshRenderer, or use with Graphics.DrawMesh
GetComponent<MeshRenderer>().material = material;9. Combine multiple shapes into one mesh
csharp
using Carrot.Geometry;
using Carrot.Meshes.Builders;
using UnityEngine;
var bezierBuilder = new BezierMeshBuilder();
bezierBuilder.AddFilledLoop(shapeA, Color.red);
bezierBuilder.AddFilledLoop(shapeB, Color.blue);
bezierBuilder.AddFilledPolygon(rawPolygon, Color.green);
Mesh combined = bezierBuilder.Build("CombinedShapes");10. Reuse builders
csharp
using Carrot.Meshes.Builders;
var builder = new BezierMeshBuilder();
// Frame 1
builder.AddFilledLoop(loop, Color.red);
Mesh mesh1 = builder.Build();
// Frame 2
builder.Clear();
builder.AddFilledLoop(differentLoop, Color.blue);
Mesh mesh2 = builder.Build();Tips
- Builders are reusable. Call
Clear()between frames or uses to avoid allocating new builder instances. - Access the inner MeshBuilder. Both
BezierMeshBuilder.BuilderandStrokeBuilder.Builderexpose the underlyingMeshBuilderfor adding custom geometry alongside Bezier/stroke content. - 32-bit indices are automatic.
MeshBuilderswitches toUInt32index format when vertex count exceeds 65,535. - Stencil support. The
UnlitVertexColorshader exposes stencil buffer properties for masking effects -- configure via material properties.