--- name: makepad-3d-generator description: "Source and create 3D content for Makepad games: search the CC0 model library by description, pick rigged character casts, and generate geometry procedurally with makepad-game-gen (terrain, trees, splines, interiors, level kits, implicit surfaces, meshes). Use for characters, vehicles, weapons, buildings, props, rigs, animation, terrain and level geometry requests." --- # Makepad 3D Generator Two sources, and choosing between them is most of the job. | Source | Best for | Cost | | --- | --- | --- | | **CC0 library** (`makepad-game-assets`) | hero props, vehicles, characters, anything whose silhouette must read as a specific recognisable object | a one-time download | | **Procedural** (`makepad-game-gen`) | terrain, roads, foliage, interiors, level layout, tiling detail, anything needed in bulk or varied by seed | free, deterministic, replicates as a seed | No paid generation API is involved, and none is required. When neither source fits, extend the procedural generator — do not hand-author vertex dumps into the repo. ## The library Fetch once (binaries are not in git): ```bash ./apps/arcade/download_assets.sh # core, ~1900 models, ~75 MB ./apps/arcade/download_assets.sh --packs=all # 4669 models / 47 packs, ~185 MB ./apps/arcade/download_assets.sh --list bash skills/makepad-game-director/scripts/probe_assets.sh ``` Everything is pinned and sha256-verified; a moved or tampered upstream fails loudly. Downloads are sequential with a delay — do not parallelise someone else's bandwidth. **Search by description, never filename.** `makepad-game-assets` provides `AssetIndex`, `find_model`, `find_cast`, `Filters`, `Hit`, `AssetKind`, `KitInfo`, `CastInfo`, `CATEGORIES`. Ids look like `kenney/racing/vehicle-truck-yellow` and are stable across re-downloads, because generated game code writes them. The index is ~120 ms to build, ~0.2 ms per search, ~2.1 MB heap at 4,999 entries. Query-side stemming handles inflections; on a score tie the kind the query implies wins. ## Rigged characters `find_cast` lists animated casts. **A cast is a set of characters sharing one skeleton** — any clip works on any member, so a part can be recast without touching animation code. | rig | members | clips | | --- | --- | --- | | 41 joints (KayKit) | 9 | 76–95 — block, dodge, hurt, dance, sleep, carry | | 7 joints (Kenney mini) | 22 | 25–32 — walk, run, jump, attack, sit, wave | | 6 joints (Kenney platformer) | 5 | 25 | Two rules: **one cast per scene**, and **different members within it** so a crowd is not clones. Mixing casts means an animation authored for one will not play on the other — a common and confusing failure. Rendering: `SkinnedModel`, `PoseBuffer`, `SkinnedBatch`, `SkinnedDraw` in `makepad-game-render::skin`. Skinning is CPU-side and re-uploads every frame, so skinned character count is the most expensive visual budget you control. ## Procedural generation `makepad-game-gen` modules: `mesh`, `terrain`, `tree`, `lsystem`, `spline`, `scatter`, `interior`, `kit`, `levelgen`, `implicit`, `texgen`, `cache`, `rng`. Plus `libs/rtsmap` for tiled RTS maps and `makepad-game-script::compose` (`kit_from_index`, `level_placements`, `PlacedTile`). **Everything is seeded.** `makepad-game-math` supplies deterministic transcendentals so two devices generate byte-identical meshes from the same seed — which is what lets a forest replicate as `(preset, seed, position)` instead of vertex data. Never use `std` float math or unseeded RNG in a generator. Pick by shape: - **Heightfield ground** → `terrain` + `LandKind` - **Organic branching** → `tree`, `lsystem` - **Roads, rivers, rails** → `spline` - **Fields of things** → `scatter` (with seeded scale/yaw jitter) - **Rooms and buildings** → `interior` - **Tile-based levels** → `levelgen`, `kit`, `rtsmap` - **Blobs, caves, smooth solids** → `implicit` - **Anything else** → `mesh` directly ## Rules that keep output usable **Scale to the player capsule.** Decide player height in world units first (gameplay), then obey it. A door at 1.6 m in a world with a 1.8 m player is the sort of error that is invisible in a screenshot and obvious in play. Check with `model_bounds(id)`. **Use the packed vertex layout.** `geom.GameMeshVertex` — 6 floats / 24 B (exact position, octahedral normal, f16 uv, unorm8 colour). Do not emit a fatter vertex for convenience; skinned and shadow meshes especially. **Validate what you generate.** Is the spawn clear? Is the goal reachable? Is there a navigable path? An unvalidated level generator produces unwinnable levels about as often as good ones, and only playtesting finds them. **Provide colliders.** `model_collider_parts(id)` gives library colliders. For generated geometry, produce a simplified collider — never collide against the render mesh. **Cache.** `makepad-game-gen::cache`, keyed by `(preset, seed)`. Regenerating a forest every frame is a stutter source; an uncapped cache is a leak. Bound it. ## Variety without new assets The library is shared, so default scenes look like everyone else's project. Four seeded levers, all free: 1. **Tint** — `ModelInstance::with_tint`, from `Palette` / `Spread` / `VarietyParams` 2. **Scale jitter** — ±10% 3. **Yaw jitter** — full range 4. **Member variety** — different cast members, different props from the same kit Seeded, so all four still replicate across the wire. ## Verification ```rust assert!(renderer.model_is_loaded(id), "missing {id}"); // silent primitive fallback otherwise renderer.model_bounds(id); // scale sanity renderer.model_triangles(id); // budget assert_eq!(generate(1234), generate(1234), "generator must be deterministic"); ``` Judge geometry by eye where a number cannot: the `ao_render` dev example in `makepad-game-render` software-rasterises props so baked AO and form read correctly without a `Cx` or the app. ## Required reading - `references/asset-search.md` — querying, casts, kits, ids, filters. - `references/procedural-recipes.md` — generator recipes and validation.