Log inGet started

population

A hardware-instanced population at rest: one mesh drawn at every transform of a baked placement, as ONE draw call per variant at any instance count.

The asset is the recipe — which .mesh and material each variant draws with, plus the instance matrices as bytes. Its instantiate method is what turns that recipe back into a live draw, so a scene records ONE entity for a population of a million instances instead of a million entities.

Layout

<name>.population/
  population.json  { version, total, variants: [ { mesh, material | materialKey, count, offset } ] }
  transforms.bin   total × 64 B — little-endian f32, column-major mat4 per instance

offset counts INSTANCES, not bytes: variant k's matrices start at offset × 64 bytes of transforms.bin and run for count × 64. Each matrix is column-major, which puts its translation at floats 13/14/15 of the block — the layout the engine reads a transform buffer as array<mat4x4<f32>>. The matrices are world-space: a population draws where the recipe placed it.

mesh and material are asset references ({ "__ref": "<guid>" }), so the geometry and material a population draws travel with it in the dependency graph. A variant drawing with a material the renderer's registry holds but no asset backs names it in materialKey instead.

Putting one in a scene

local pop = asset.resolve("forest", "population")
pop:instantiate(nil, { position = { 0, 0, 0 } })     -- one entity, one draw call

That spawns an entity carrying the Population component, which owns the live GPU buffers and draw registrations for as long as the entity lives — a reload replaces them rather than stacking a second set. The same contract is what Asset { source = <population> } drives, which is how a baked procgen bundle carries a population: a root record plus one child holding the reference.

Building one

proc's population.write sink persists a Population value:

local pop = proc.registry.get("instances.draw").eval({}, { instances = iset }, {}).population
proc.registry.get("population.write").eval({}, { population = pop }, { name = "forest" })

Or through asset.create directly, with each variant's matrices as a flat count × 16 column-major float array:

asset.create("population", "forest", {
    variants = { { mesh = meshGuid, material = materialGuid, transforms = flat } },
})

Reading and drawing one by hand

local recipe = pop:spec()        -- { version, total, variants }
local live   = pop:draw()        -- allocate buffers + register draws
print(live:count(), live:drawCalls())
live:destroy()                   -- drop the registrations, free the buffers

The value :draw() returns OWNS those resources. Hold it and release it — a registration nobody holds can be neither enumerated nor dropped afterwards.

  • asset-type
  • reference