/** * A {@link Mesh} drawn many times from one copy of its geometry. * * Ordinary meshes pay for every repeat: a forest of five thousand trees is * five thousand geometries on the GPU, each re-uploaded when it changes. An * `InstancedMesh` uploads the geometry **once** and stamps out copies from a * small per-instance record, so cost scales with the number of instances * rather than with `instances × vertices`, and the whole thing draws in a * single call. * * Every `Mesh` setting works unchanged — `model` + `material` from an OBJ, * raw `vertices`/`uvs`/`indices`, `lit`, `cullBackFaces`, `rightHanded`, * `tint`, `textureRepeat`, the lot. What an instanced mesh adds is the * instance buffer: * * - a **transform** per instance, always present; * - a **colour** per instance, when `instanceColors` is declared, multiplied * into the mesh tint; * - an opaque **`vec4`** per instance, when `instanceData` is declared. The * built-in lit shading reads its `rgb` as emissive; a custom * {@link Mesh#shader} may read it as a wind phase, an atlas offset, a * random seed — whatever the shader wants. * * The mesh's own position, rotation and scale keep their ordinary meaning and * act as the **group** transform: moving an `InstancedMesh` moves every * instance with one uniform write and re-uploads nothing. * * Requires a GPU backend (`renderer.supportsInstancing`). Under the Canvas * renderer the instances are drawn one at a time through the ordinary CPU * mesh path — correct, but without any of the benefit. * * ### When a plain {@link Mesh} each is the better answer * * One `InstancedMesh` is **one geometry and one material**, and four things * move from per-object to per-group: * * - **Depth sorting.** The set has a single sort key — its own `pos` — so it * orders against the rest of the scene as one object. Opaque instances still * resolve against each other per pixel through the depth buffer; blended * ones cannot be sorted among themselves at all. * - **Ground shadows.** {@link Mesh#castGroundShadow} becomes one instanced * shadow draw covering the whole set, not a blob computed per object. * - **Removal.** {@link InstancedMesh#removeInstance} swaps the last instance * into the hole, so any index a caller was holding now refers to a different * object. * - **Colour.** {@link Mesh#tint} applies to the set; per-instance colour has * to be declared up front with `instanceColors` and set through * {@link InstancedMesh#setInstanceColor}. * * So the question is not how many there are, but whether the game addresses * them **individually**. Scenery — trees, rocks, grass, debris — instances * cleanly, because nothing ever asks about one of them. Collision-tested props * are fine too: the positions are yours either way and instancing only changes * how they are drawn. Collectibles and enemies usually are not, because * removing them one at a time makes the index swap the caller's problem, and * at small counts a pooled `Mesh` each is less code and no slower. * @augments Mesh * @category Rendering * @example * const forest = new me.InstancedMesh(0, 0, { * model: "tree", * material: "tree", * width: 64, * lit: true, * instanceCount: 5000, // pre-allocate * instanceColors: true, * instanceData: true, * }); * * const placement = new me.Matrix3d(); // one scratch — zero allocation * for (let i = 0; i < forest.instanceCount; i++) { * placement.identity().translate(x, y, z).scale(s); * forest.setInstance(i, placement); * forest.setInstanceColor(i, autumnTint); * forest.setInstanceData(i, windPhase, seed, 0, 0); * } * * // draw only the nearest 1200 — no re-upload, just a smaller draw * forest.visibleInstanceCount = 1200; * app.world.addChild(forest, 10); */ /** * Everything {@link InstancedMesh} takes: the instancing settings, plus every * {@link MeshSettings} — they are handed to {@link Mesh} whole, so the type * says so by intersection rather than by copying the list. * @typedef {object} InstancedMeshOwnSettings * @property {number} [instanceCount=0] - number of instances to pre-allocate. Instances start at the group origin (identity transform) until placed; `addInstance` grows past this. * @property {boolean} [instanceColors=false] - give each instance its own colour (16 bytes per instance), multiplied into the mesh tint * @property {boolean} [instanceData=false] - give each instance an opaque `vec4` (16 bytes per instance). Read as emissive by the built-in lit shading, or as anything at all by a custom mesh shader. */ /** * @typedef {InstancedMeshOwnSettings & import("./mesh.js").MeshSettings} InstancedMeshSettings */ export default class InstancedMesh extends Mesh { /** * @param {number} x - the x coordinate of the group origin * @param {number} y - the y coordinate of the group origin * @param {InstancedMeshSettings} settings - every {@link Mesh} setting, plus the instancing ones */ constructor(x: number, y: number, settings: InstancedMeshSettings); /** * The packed instance records, `instanceLayout.floats` per instance. * Editable in place for bulk updates — announce those with * {@link InstancedMesh#needsInstanceUpdate} or * {@link InstancedMesh#markInstancesDirty}, since writing here bypasses * the setters that track the dirty range. * @type {Float32Array} */ instanceBuffer: Float32Array; set instanceCount(count: number); /** * How many instances this mesh holds. * * Growing pre-allocates the new records as identity transforms (an * unplaced instance sits at the group origin rather than collapsing onto * a zero matrix); shrinking keeps the allocation, so a count that * oscillates does not thrash it. * @type {number} */ get instanceCount(): number; set visibleInstanceCount(count: number); /** * How many instances are actually drawn, counted from the first. * * The cheap culling and level-of-detail knob: sort the instances by * distance once, then draw fewer of them by moving this single number — * no re-upload, no rebuild. Defaults to every instance (`-1`). * @type {number} * @example * forest.visibleInstanceCount = playerIsIndoors ? 0 : 1200; */ get visibleInstanceCount(): number; /** * Append an instance, growing the buffer as needed. * @param {Matrix3d} [transform] - where this instance sits, relative to the group. Defaults to the group origin. * @param {object} [options] - optional per-instance slots * @param {Color} [options.color] - instance colour (requires `instanceColors`) * @param {number[]} [options.data] - four numbers for the custom slot (requires `instanceData`) * @returns {number} the new instance's index */ addInstance(transform?: Matrix3d, options?: { color?: any; data?: number[] | undefined; }): number; /** * Remove an instance by moving the last one into its place. * * Swapping is what keeps this O(1) instead of shifting every record after * the hole — but it means **indices are not stable across a removal**: the * instance that was last now answers to `index`. Callers holding indices * must re-read them, or avoid removal in favour of * {@link InstancedMesh#visibleInstanceCount}. * @param {number} index - the instance to remove */ removeInstance(index: number): void; /** * Place one instance. * @param {number} index - the instance to place * @param {Matrix3d} transform - where it sits, relative to the group */ setInstance(index: number, transform: Matrix3d): void; /** * Read one instance's transform back. * @param {number} index - the instance to read * @param {Matrix3d} [out] - matrix to write into; a new one is allocated when omitted * @returns {Matrix3d|undefined} the transform, or `undefined` for an out-of-range index */ getInstance(index: number, out?: Matrix3d): Matrix3d | undefined; /** * Set one instance's colour, multiplied into the mesh tint. * @param {number} index - the instance to colour * @param {Color} color - the instance colour */ setInstanceColor(index: number, color: Color): void; /** * Set one instance's custom `vec4`. * @param {number} index - the instance to write * @param {number} x - first component (emissive red, under the built-in lit shading) * @param {number} y - second component * @param {number} z - third component * @param {number} [w=0] - fourth component */ setInstanceData(index: number, x: number, y: number, z: number, w?: number): void; /** * Announce that a span of instances was edited directly through * {@link InstancedMesh#instanceBuffer}, so the next draw re-uploads it. * The per-instance setters do this themselves. * @param {number} first - first instance changed * @param {number} count - how many */ markInstancesDirty(first: number, count: number): void; /** * Announce that the whole instance buffer was edited in place. The * instancing counterpart of {@link Mesh#needsUpdate} — and, like it, a * signal rather than a state, so it is write-only. * @type {boolean} * @example * forest.instanceBuffer.set(myRecords); * forest.needsInstanceUpdate = true; */ set needsInstanceUpdate(value: any); /** * Draw the instanced mesh. Under a GPU backend this is one instanced * draw call; the Canvas renderer has no instancing, so each instance is * drawn through the ordinary mesh path instead. * @param {CanvasRenderer|WebGLRenderer} renderer - a renderer instance * @param {Camera2d} [viewport] - the camera rendering this frame */ draw(renderer: CanvasRenderer | WebGLRenderer, viewport?: Camera2d): void; } /** * Everything {@link InstancedMesh} takes: the instancing settings, plus every * {@link MeshSettings} — they are handed to {@link Mesh} whole, so the type * says so by intersection rather than by copying the list. */ export type InstancedMeshOwnSettings = { /** * - number of instances to pre-allocate. Instances start at the group origin (identity transform) until placed; `addInstance` grows past this. */ instanceCount?: number; /** * - give each instance its own colour (16 bytes per instance), multiplied into the mesh tint */ instanceColors?: boolean; /** * - give each instance an opaque `vec4` (16 bytes per instance). Read as emissive by the built-in lit shading, or as anything at all by a custom mesh shader. */ instanceData?: boolean; }; export type InstancedMeshSettings = InstancedMeshOwnSettings & import("./mesh.js").MeshSettings; import Mesh from "./mesh.js"; import { Matrix3d } from "../math/matrix3d.ts"; //# sourceMappingURL=instanced_mesh.d.ts.map