Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

readme.md

trails system

Continuous ribbon geometry that follows a moving entity — sparks, wingtip vapour, a sword arc, a skid.

A stream of sprites cannot express any of those. Spaced closely enough to read as continuous they cost a particle per centimetre, and the moment the subject accelerates the spacing opens up and the effect reads as a dotted line. A trail is one surface threaded through where the subject has been, so its continuity does not depend on how fast it is moving.

Using it

entity.find("spark").component.add("TrailRenderer", {
    lifetime  = 0.8,          -- seconds a point survives
    widthHead = 0.3,          -- ribbon width at the subject
    widthTail = 0.0,          -- and at the far end
    colorHead = { 1, 0.9, 0.4 },
    colorTail = { 1, 0.2, 0.0 },
})

Every field is live. For a trail driven from script — one that follows something that is not an entity, or is rebuilt on your own schedule — use the module:

local Trails = require("@builtin::systems.trails.trails")

local path = Trails.newPath({ lifetime = 1.5, minDistance = 0.05 })
Trails.push(path, x, y, z, getTime())
Trails.expire(path, getTime())
local geo = Trails.ribbon(path, { now = getTime(), cameraPosition = eye })

geo is flat positions / normals / uvs / colors / indices — what renderer.mesh.create takes.

What the knobs do

fieldeffect
lifetimeSeconds a point survives. This is the trail's length in time, so a faster subject leaves a longer ribbon.
minDistanceMetres travelled before another point is recorded. Below it a segment would be shorter than the ribbon is wide and fold over itself.
maxPointsBound on the ring. Reached before lifetime expires, the oldest point goes early — which is what caps a fast subject's cost.
widthHead / widthTailRibbon width at the subject and at the far end.
alphaHead / alphaTailOpacity at each end.
colorHead / colorTailRGB at each end, {r, g, b} in 0..1. An empty tail holds the head's colour.
orientation"camera" opens the ribbon toward the viewer so it holds its width from every angle; "fixed" opens it about up.
upThe axis a "fixed" ribbon opens about.
materialThe material the ribbon draws with.

Width, colour and opacity are indexed by each point's age, not by its position in the list — so the fade covers the same span of time whether the subject is crawling or sprinting, and a trail does not compress its gradient when points bunch up.

How it renders

The ribbon is world-space geometry, so it draws from its own entity at the world origin rather than as a child of the subject: a trail stays where it was laid down instead of being dragged along by the transform that laid it.

Each point contributes two vertices, offset either side of the direction of travel through that point. That direction is the central difference between its neighbours, which is what makes a corner one continuous bend rather than two mitred ends. Colour and opacity ride the vertex colour, so the gradient needs no shader of its own and any material that honours vertex colour can draw a trail.

trail_default is unlit, transparent and two-sided — a ribbon is a surface with no thickness, so it has no back to cull.

Files

trails.package/
  TrailRenderer.component/  -- the trail on an entity, authored and persisted
  trails.module/            -- the point ring and the ribbon builder
  trail_default.material/   -- unlit, transparent, two-sided