Skip to content

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) and AddVertex(float x, float y) default Z to 0.
  • Automatic 32-bit indices. Switches to IndexFormat.UInt32 when 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 subsequent AddTriangles(indices, baseVertex).

BezierMeshBuilder

Converts Bezier geometry into filled triangulated meshes:

  1. Calls BezierLoop.ExtractPolygon() to tessellate curves into a Vector2[] polygon.
  2. Runs EarClipTriangulator.Triangulate() on the polygon.
  3. 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:

  1. Computes left/right offset vertices using averaged normals (miter joins) at interior vertices and perpendicular normals at endpoints.
  2. Clamps miter extension to 4x at acute angles to prevent spikes.
  3. 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 _Color tint.
  • 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) and SRPDefaultUnlit (fallback for non-2D renderers).
  • Falls back to Sprites/Default when 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 StrokeBuilder clamps miter extension at very sharp angles (< ~75 degrees) to prevent visual spikes, trading geometric precision for visual quality.
  • Shared shader access. MeshShaders lazy-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.Builder and StrokeBuilder.Builder expose the underlying MeshBuilder for adding custom geometry alongside Bezier/stroke content.
  • 32-bit indices are automatic. MeshBuilder switches to UInt32 index format when vertex count exceeds 65,535.
  • Stencil support. The UnlitVertexColor shader exposes stencil buffer properties for masking effects -- configure via material properties.

Carrot