Skip to content

Developer notes — kids.kapish.splines.meshes

unity

Role

The "extrude a cross-section along a spline" core, factored out so paths, walls and rivers share one sweeper and differ only by StripProfile. Depends on kids.kapish.splines (the Strip substrate) and kids.kapish.meshes (the MeshBuilder accumulator) — base splines stays mesh-free.

StripMeshBuilder

  • Resample: the dense Strip is resampled to rings at segmentLength arc spacing (Center/Left/Right/Up/Tangent).
  • Place: a StripProfilePoint sits at LerpUnclamped(Left, Right, Across) + Up * Up — Across rides the (terrain-conformed) edges, Up rides the frame.
  • Stitch: adjacent rings × adjacent profile points → two triangles. Winding is aligned to a per-quad outward reference (edge-midpoint minus cross-section centroid, mapped to world), so the surface is never inside-out regardless of how the profile was wound. flipFaces inverts it.
  • Caps: closed profiles fan-triangulate the section at each end (outward = ∓tangent). Fan assumes a convex section — fine for the v1 wall presets; concave sections want ear-clipping (kids.kapish.maths' EarClipTriangulator) later.
  • Normals: flat per-face (crisp edges). Smooth/welded normals (for hedges, rounded copings) are a later option.

Future

Paths' PathMeshBuilder can migrate onto this (its skirts/chunking layered on top) once it's worth a [SerializeReference] profile migration. Rivers/fences are just more profiles.


Usage Guide

You rarely use this directly — a primitive component (e.g. WallMeshStrip) drives it. To build a sweeper of your own:

csharp
using Carrot.Splines;        // Strip, StripCalculator, StripSettings
using Carrot.Splines.Meshes; // StripProfile, StripMeshBuilder
using UnityEngine.Splines;

// 1. Author a profile (closed = a tube you can cap).
sealed class MyProfile : StripProfile
{
    public override bool Closed => true;
    public override void GetCrossSection(System.Collections.Generic.List<StripProfilePoint> into)
    {
        into.Add(new StripProfilePoint(0f, 0f, 0f));
        into.Add(new StripProfilePoint(1f, 0f, 1f));
        into.Add(new StripProfilePoint(1f, 2f, 1f));
        into.Add(new StripProfilePoint(0f, 2f, 0f));
    }
}

// 2. Strip from the spline, then sweep.
var container = GetComponent<SplineContainer>();
Strip[] strips = StripCalculator.ComputeAll(container, new StripSettings { Width = 0.4f, ConformToTerrain = true });
foreach (Strip s in strips)
{
    // Returns one mesh per chunk (chunkLength arc; 0 = a single mesh).
    foreach (Mesh m in StripMeshBuilder.Build(s, new MyProfile(), segmentLength: 1f, capEnds: true, transform.worldToLocalMatrix, chunkLength: 25f))
    {
        // ... assign each chunk to its own MeshFilter
    }
}

If faces render inside-out, pass flipFaces: true — the profile's outward sense is reversed.

Carrot