@aethervtt/shard-scatter 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Patrick Dias
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,3 @@
1
- # Temporary Holding Version
1
+ # @aethervtt/shard-scatter
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Part of [Shard](https://github.com/aethervtt/shard), a WebGPU-first 2D/3D game engine in TypeScript. See the repository for documentation.
@@ -0,0 +1,44 @@
1
+ import { type Entity, type World } from '@aethervtt/shard-core';
2
+ export declare const ScatterSurface: import("@aethervtt/shard-core").ComponentDef<{
3
+ readonly set: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").AssetRef<"scatter/ScatterSet"> | null, "object">;
4
+ readonly seed: import("@aethervtt/shard-core").FieldType<number, "u32">;
5
+ }>;
6
+ export declare const Prop: import("@aethervtt/shard-core").ComponentDef<{
7
+ readonly index: import("@aethervtt/shard-core").FieldType<number, "u32">;
8
+ }>;
9
+ export declare const ScatterChunk: import("@aethervtt/shard-core").ComponentDef<{
10
+ readonly surface: import("@aethervtt/shard-core").FieldType<number | null, "f64">;
11
+ readonly rule: import("@aethervtt/shard-core").FieldType<string, "object">;
12
+ readonly chunk: import("@aethervtt/shard-core").FieldType<string, "object">;
13
+ }>;
14
+ /** A prop's rule, chunk and index (the rule and chunk from its parent scatter/Chunk). */
15
+ export declare function propIdentity(world: World, prop: Entity): {
16
+ rule: string;
17
+ chunk: string;
18
+ index: number;
19
+ } | undefined;
20
+ export interface ScatterBudgetValue {
21
+ /**
22
+ * Props spawned per frame, at most (a chunk spawns over several frames when it has more). Each
23
+ * costs a few microseconds of main-thread time with its LODs and collider: 250 keeps a frame
24
+ * under a millisecond.
25
+ */
26
+ propsPerFrame: number;
27
+ /** Chunks of props that start placement per frame. */
28
+ chunksPerFrame: number;
29
+ /** Placed chunks kept after they leave range, so walking back doesn't place them again. */
30
+ cache: number;
31
+ }
32
+ export declare const ScatterBudget: import("@aethervtt/shard-core").ResourceDef<ScatterBudgetValue>;
33
+ /** Props despawned by gameplay: saved (0038), and skipped when their chunk spawns again. */
34
+ export interface RemovedProps {
35
+ /** "rule|chunk|index" keys (`removedKey`). */
36
+ props: string[];
37
+ }
38
+ export declare const Removed: import("@aethervtt/shard-core").ResourceDef<RemovedProps>;
39
+ /** The key a removed prop is recorded under. */
40
+ export declare function removedKey(rule: string, chunk: string, index: number): string;
41
+ /** Whether a prop was removed (a set per list, rebuilt when a load replaces the list). */
42
+ export declare function isRemoved(removed: RemovedProps, key: string): boolean;
43
+ /** Records a removed prop (once). */
44
+ export declare function addRemoved(removed: RemovedProps, key: string): void;
@@ -0,0 +1,98 @@
1
+ import {
2
+ ChildOf,
3
+ defineComponent,
4
+ defineResource,
5
+ defineSchema,
6
+ t
7
+ } from "@aethervtt/shard-core";
8
+ import { Mesh3d } from "@aethervtt/shard-render";
9
+ const ScatterSurface = defineComponent(
10
+ "scatter/ScatterSurface",
11
+ {
12
+ set: t.handle("scatter/ScatterSet", {
13
+ description: "The rules (*.scatter.json) to place on this mesh."
14
+ }),
15
+ seed: t.u32({ description: "Mixed into every rule: the same rules, other places." })
16
+ },
17
+ {
18
+ description: "Scatters props and foliage on this entity\u2019s mesh by a ScatterSet, as on a planet: items sit where a ray straight down (local \u2212Y) meets the mesh. For flat levels; planets use Planet.scatter and Biome.scatter.",
19
+ requires: [Mesh3d]
20
+ }
21
+ );
22
+ const Prop = defineComponent(
23
+ "scatter/Prop",
24
+ {
25
+ index: t.u32({
26
+ readonly: true,
27
+ description: "Its stable id within its chunk (a lattice cell); the parent scatter/Chunk names the rule and chunk."
28
+ })
29
+ },
30
+ {
31
+ description: "A prop a ScatterSet placed, a child of its scatter/Chunk. Rebuilt when its chunk comes back in range; despawning it records it as removed (saved), so it stays gone.",
32
+ serialize: false
33
+ }
34
+ );
35
+ const ScatterChunk = defineComponent(
36
+ "scatter/Chunk",
37
+ {
38
+ surface: t.entity({ readonly: true }),
39
+ rule: t.string({ readonly: true, description: "The rule\u2019s id (set path, biome, name)." }),
40
+ chunk: t.string({ readonly: true, description: "face/depth/x/y on a planet, x/z on a mesh." })
41
+ },
42
+ {
43
+ description: "The parent of one chunk of one rule\u2019s props (scatter/Prop): rebuilt from the scatter rules, never saved.",
44
+ serialize: false
45
+ }
46
+ );
47
+ function propIdentity(world, prop) {
48
+ const p = world.tryGet(prop, Prop);
49
+ const parent = world.tryGet(prop, ChildOf)?.parent;
50
+ if (!p || parent === void 0 || parent === null) return void 0;
51
+ const c = world.tryGet(parent, ScatterChunk);
52
+ if (!c) return void 0;
53
+ return { rule: c.rule, chunk: c.chunk, index: p.index };
54
+ }
55
+ const ScatterBudget = defineResource("scatter/Budget", {
56
+ description: "How much scatter work a frame may do, and how many placed chunks stay cached.",
57
+ init: () => ({ propsPerFrame: 250, chunksPerFrame: 16, cache: 512 })
58
+ });
59
+ const RemovedSchema = defineSchema("scatter/RemovedProps", {
60
+ props: t.list(t.string, {
61
+ description: 'Removed props as "rule|chunk|index" (chunk: face/depth/x/y, or x/z on a mesh).'
62
+ })
63
+ });
64
+ const Removed = defineResource("scatter/Removed", {
65
+ description: "Props the game removed (destroyed rocks, felled trees), by rule, chunk, and index: saved, so they stay gone however far the player travels.",
66
+ schema: RemovedSchema,
67
+ persist: true,
68
+ init: () => ({ props: [] })
69
+ });
70
+ function removedKey(rule, chunk, index) {
71
+ return `${rule}|${chunk}|${index}`;
72
+ }
73
+ const lookups = /* @__PURE__ */ new WeakMap();
74
+ function isRemoved(removed, key) {
75
+ if (removed.props.length === 0) return false;
76
+ let set = lookups.get(removed.props);
77
+ if (!set || set.size !== removed.props.length) {
78
+ set = new Set(removed.props);
79
+ lookups.set(removed.props, set);
80
+ }
81
+ return set.has(key);
82
+ }
83
+ function addRemoved(removed, key) {
84
+ if (isRemoved(removed, key)) return;
85
+ removed.props.push(key);
86
+ lookups.get(removed.props)?.add(key);
87
+ }
88
+ export {
89
+ Prop,
90
+ Removed,
91
+ ScatterBudget,
92
+ ScatterChunk,
93
+ ScatterSurface,
94
+ addRemoved,
95
+ isRemoved,
96
+ propIdentity,
97
+ removedKey
98
+ };
@@ -0,0 +1,43 @@
1
+ import { type AssetRef, type World } from '@aethervtt/shard-core';
2
+ import type { Workers } from '@aethervtt/shard-platform';
3
+ import { FoliageLayer } from '@aethervtt/shard-render';
4
+ import { type SurfaceScatter } from './runtime.js';
5
+ import { type PatchJob, type SurfaceChunk } from './surface.js';
6
+ export interface WindValue {
7
+ /** World-space direction (normalized when used). */
8
+ direction: [number, number, number];
9
+ /** 0 calm, 1 a breeze, 2+ a gale. */
10
+ strength: number;
11
+ /** Gust waves per metre. */
12
+ gustScale: number;
13
+ }
14
+ /** The wind foliage and swaying props feel (0049 replaces it with the planet's wind field). */
15
+ export declare const Wind: import("@aethervtt/shard-core").ResourceDef<WindValue>;
16
+ interface FoliageChunkState {
17
+ id: string;
18
+ chunk: SurfaceChunk;
19
+ job: PatchJob | undefined;
20
+ slot: number;
21
+ kept: number;
22
+ distance: number;
23
+ /** The patch's four corners (chunk frame, xyz each, counter-clockwise), for the overlay. */
24
+ corners: Float32Array | undefined;
25
+ }
26
+ /** A foliage rule's GPU layer and the chunks fed to it. */
27
+ export declare class FoliageRule {
28
+ readonly rule: number;
29
+ readonly layer: FoliageLayer;
30
+ readonly chunks: Map<string, FoliageChunkState>;
31
+ constructor(rule: number, layer: FoliageLayer);
32
+ }
33
+ /**
34
+ * One frame of a surface's foliage (0045): a GPU layer per foliage rule, its chunks around the
35
+ * cameras (ground patches built on the pool, props' footprints kept clear), and each chunk's
36
+ * transform for this frame's origin. Only with a renderer: headless runs have no foliage.
37
+ */
38
+ export declare function updateFoliage(world: World, ss: SurfaceScatter, frame: number, workers: Workers | undefined): void;
39
+ /** Drops a surface's foliage layers (its rules changed, or it went away). */
40
+ export declare function clearFoliage(world: World, ss: SurfaceScatter): void;
41
+ /** Writes the Wind resource into every Vegetation material scatter uses, when it changed. */
42
+ export declare function applyWind(world: World, materials: Iterable<AssetRef<'Material'> | null>): void;
43
+ export {};
@@ -0,0 +1,175 @@
1
+ import { defineResource } from "@aethervtt/shard-core";
2
+ import { FoliageLayer, FoliageLayers, Materials } from "@aethervtt/shard-render";
3
+ import { Vegetation } from "./material.js";
4
+ import { FOLIAGE } from "./rules.js";
5
+ import { finalPlacements, neighborChunks } from "./runtime.js";
6
+ import { P_RADIUS, P_X, PLACEMENT_STRIDE } from "./surface.js";
7
+ const KEEP = 1.1;
8
+ const PATCHES_PER_FRAME = 8;
9
+ const Wind = defineResource("scatter/Wind", {
10
+ description: "Wind for scatter materials (scatter/Vegetation): direction, strength and gust scale. Foliage and leaves sway with it.",
11
+ init: () => ({ direction: [1, 0, 0.35], strength: 1, gustScale: 0.05 })
12
+ });
13
+ class FoliageRule {
14
+ rule;
15
+ layer;
16
+ chunks = /* @__PURE__ */ new Map();
17
+ constructor(rule, layer) {
18
+ this.rule = rule;
19
+ this.layer = layer;
20
+ }
21
+ }
22
+ const viewers = { points: new Float64Array(24), cameras: 0, anchors: 0 };
23
+ const rows = new Float32Array(12);
24
+ const order = [];
25
+ function updateFoliage(world, ss, frame, workers) {
26
+ const layers = world.tryResource(FoliageLayers);
27
+ if (!layers) return;
28
+ const surface = ss.surface;
29
+ if (ss.foliageVersion !== surface.version) clearFoliage(world, ss);
30
+ ss.foliageVersion = surface.version;
31
+ surface.viewers(world, viewers);
32
+ let started = 0;
33
+ for (const rule of surface.rules) {
34
+ if (rule.kind !== FOLIAGE) continue;
35
+ let fr = ss.foliage.get(rule.index);
36
+ if (!fr) {
37
+ const layer = makeLayer(ss, rule.index);
38
+ if (!layer) continue;
39
+ fr = new FoliageRule(rule.index, layers.add(layer));
40
+ ss.foliage.set(rule.index, fr);
41
+ }
42
+ const f = fr;
43
+ surface.chunksNear(rule, viewers.points, 0, viewers.cameras, rule.range * KEEP, (chunk, d) => {
44
+ const id = chunk.key;
45
+ let c = f.chunks.get(id);
46
+ if (!c) {
47
+ c = { id, chunk, job: void 0, slot: -1, kept: frame, distance: d, corners: void 0 };
48
+ f.chunks.set(id, c);
49
+ }
50
+ if (c.kept !== frame) c.distance = d;
51
+ else c.distance = Math.min(c.distance, d);
52
+ c.kept = frame;
53
+ });
54
+ order.length = 0;
55
+ for (const c of f.chunks.values()) if (c.kept === frame && c.slot < 0 && !c.job) order.push(c);
56
+ order.sort((a, b) => a.distance - b.distance || (a.id < b.id ? -1 : 1));
57
+ for (const c of order) {
58
+ if (started >= PATCHES_PER_FRAME) break;
59
+ c.job = surface.startPatch(c.chunk, workers);
60
+ started++;
61
+ }
62
+ for (const c of f.chunks.values()) {
63
+ if (c.kept !== frame) {
64
+ if (c.job) c.job.cancelled = true;
65
+ if (c.slot >= 0) f.layer.removeChunk(c.slot);
66
+ f.chunks.delete(c.id);
67
+ continue;
68
+ }
69
+ if (c.job && (c.job.ready || !workers || workers.size === 0)) {
70
+ const patch = surface.finishPatch(c.job);
71
+ c.job = void 0;
72
+ const n = patch.grid;
73
+ c.corners = new Float32Array(12);
74
+ for (const [k, v] of [0, n - 1, n * n - 1, n * (n - 1)].entries())
75
+ c.corners.set(patch.positions.subarray(v * 3, v * 3 + 3), k * 3);
76
+ c.slot = f.layer.allocate();
77
+ f.layer.setChunk(c.slot, { ...patch, avoid: footprints(ss, c.chunk, frame) });
78
+ }
79
+ if (c.slot >= 0) {
80
+ surface.chunkTransform(world, c.chunk, rows);
81
+ f.layer.setTransform(c.slot, rows);
82
+ }
83
+ }
84
+ }
85
+ }
86
+ function makeLayer(ss, index) {
87
+ const rule = ss.surface.rules[index];
88
+ const meshes = [];
89
+ const lods = [];
90
+ const weights = [];
91
+ let material = null;
92
+ let prev = 0;
93
+ for (const [i, variants] of ss.variants[index].entries()) {
94
+ const w = (rule.weights[i] - prev) / variants.length;
95
+ prev = rule.weights[i];
96
+ for (const v of variants) {
97
+ if (!v.mesh) continue;
98
+ meshes.push(v.mesh);
99
+ lods.push(v.lods);
100
+ weights.push((weights.at(-1) ?? 0) + w);
101
+ material ??= v.material;
102
+ }
103
+ }
104
+ if (meshes.length === 0 || !material) return void 0;
105
+ weights[weights.length - 1] = 1;
106
+ const kind = ss.surface.kind === "planet" ? "planet" : "mesh";
107
+ return new FoliageLayer({
108
+ label: `${kind}:${rule.name}`,
109
+ meshes: meshes.slice(0, 32),
110
+ lods: lods.slice(0, 32),
111
+ weights,
112
+ material,
113
+ seed: rule.seed,
114
+ cells: ss.surface.cells[index],
115
+ range: rule.range,
116
+ shadowRange: rule.shadowRange,
117
+ scale: [rule.scaleMin, rule.scaleMax],
118
+ align: rule.align
119
+ });
120
+ }
121
+ function footprints(ss, chunk, frame) {
122
+ const rule = ss.surface.rules[chunk.rule];
123
+ if (rule.avoid.length === 0) return void 0;
124
+ const out = [];
125
+ for (const a of rule.avoid) {
126
+ for (const other of neighborChunks(ss, ss.surface.rules[a], chunk, frame)) {
127
+ const p = finalPlacements(ss, other, frame);
128
+ for (let i = 0; i < p.count; i++) {
129
+ const o = i * PLACEMENT_STRIDE;
130
+ out.push(
131
+ other.chunk.center[0] + p.data[o + P_X] - chunk.center[0],
132
+ other.chunk.center[1] + p.data[o + P_X + 1] - chunk.center[1],
133
+ other.chunk.center[2] + p.data[o + P_X + 2] - chunk.center[2],
134
+ p.data[o + P_RADIUS]
135
+ );
136
+ }
137
+ }
138
+ }
139
+ return out.length > 0 ? Float32Array.from(out) : void 0;
140
+ }
141
+ function clearFoliage(world, ss) {
142
+ const layers = world.tryResource(FoliageLayers);
143
+ for (const f of ss.foliage.values()) {
144
+ for (const c of f.chunks.values()) if (c.job) c.job.cancelled = true;
145
+ layers?.remove(f.layer);
146
+ }
147
+ ss.foliage.clear();
148
+ }
149
+ const applied = /* @__PURE__ */ new WeakMap();
150
+ const direction = [1, 0, 0];
151
+ function applyWind(world, materials) {
152
+ const wind = world.tryResource(Wind);
153
+ const store = world.tryResource(Materials);
154
+ if (!wind || !store) return;
155
+ const d = wind.direction;
156
+ const key = `${d[0]},${d[1]},${d[2]}|${wind.strength}|${wind.gustScale}`;
157
+ const l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]) || 1;
158
+ direction[0] = d[0] / l;
159
+ direction[1] = d[1] / l;
160
+ direction[2] = d[2] / l;
161
+ for (const ref of materials) {
162
+ if (!ref) continue;
163
+ const m = store.get(ref);
164
+ if (!m || m.type !== Vegetation || applied.get(m) === key) continue;
165
+ applied.set(m, key);
166
+ m.set({ windDirection: [...direction], windStrength: wind.strength, gustScale: wind.gustScale });
167
+ }
168
+ }
169
+ export {
170
+ FoliageRule,
171
+ Wind,
172
+ applyWind,
173
+ clearFoliage,
174
+ updateFoliage
175
+ };
@@ -0,0 +1,35 @@
1
+ import type { Entity, Query, World } from '@aethervtt/shard-core';
2
+ import type { HeightfieldRuntime } from '@aethervtt/shard-terrain';
3
+ import { MeshSurface } from './mesh-surface.js';
4
+ import type { SurfaceChunk } from './surface.js';
5
+ /**
6
+ * A heightfield's ground for scatter (0045 on 0071): heights and face normals from its leaf pages,
7
+ * the triangles its colliders and mesh have, so a prop stands on what a character stands on. Leaf
8
+ * pages come from the CPU cache, or are baked on the spot (the same bytes), so placement never
9
+ * depends on what has streamed in.
10
+ */
11
+ export declare class HeightfieldIndex {
12
+ readonly rt: HeightfieldRuntime;
13
+ readonly minX = 0;
14
+ readonly minZ = 0;
15
+ readonly maxX: number;
16
+ readonly maxZ: number;
17
+ readonly minY: number;
18
+ readonly maxY: number;
19
+ constructor(rt: HeightfieldRuntime);
20
+ /** The ground under (x, z): writes [height, nx, ny, nz] (up-facing) and returns true on the terrain. */
21
+ heightAt(x: number, z: number, out: Float64Array): boolean;
22
+ }
23
+ /**
24
+ * A heightfield terrain as a scatter surface (`Terrain.scatter`): the mesh surface's square
25
+ * lattice in the terrain's frame (its corner at 0, +Y up), heights from its pages, chunk roots as
26
+ * grid children of the terrain.
27
+ */
28
+ export declare class HeightfieldSurface extends MeshSurface {
29
+ readonly kind = "heightfield";
30
+ readonly rt: HeightfieldRuntime;
31
+ constructor(world: World, rt: HeightfieldRuntime, cameras: Query);
32
+ /** Takes the terrain's pages (again after a rebake). */
33
+ setTerrain(): void;
34
+ placeChunkRoot(world: World, root: Entity, chunk: SurfaceChunk): void;
35
+ }
@@ -0,0 +1,61 @@
1
+ import { mainNoise, pageHeight } from "@aethervtt/shard-terrain";
2
+ import { placeInGrid } from "@aethervtt/shard-transform";
3
+ import { MeshSurface } from "./mesh-surface.js";
4
+ class HeightfieldIndex {
5
+ rt;
6
+ minX = 0;
7
+ minZ = 0;
8
+ maxX;
9
+ maxZ;
10
+ minY;
11
+ maxY;
12
+ constructor(rt) {
13
+ this.rt = rt;
14
+ this.maxX = rt.layout.sizeX;
15
+ this.maxZ = rt.layout.sizeZ;
16
+ this.minY = rt.lo;
17
+ this.maxY = rt.hi;
18
+ }
19
+ /** The ground under (x, z): writes [height, nx, ny, nz] (up-facing) and returns true on the terrain. */
20
+ heightAt(x, z, out) {
21
+ const rt = this.rt;
22
+ const l = rt.layout;
23
+ if (!(x >= 0 && z >= 0 && x <= l.sizeX && z <= l.sizeZ)) return false;
24
+ const size = l.leafSize;
25
+ const px = Math.min(l.leavesX - 1, Math.floor(x / size));
26
+ const pz = Math.min(l.leavesZ - 1, Math.floor(z / size));
27
+ const page = rt.pages.leafNow(mainNoise(), rt.stack, px, pz);
28
+ const e = 0.05;
29
+ const h = pageHeight(rt, page, x, z);
30
+ const hx = pageHeight(rt, page, Math.min(x + e, (px + 1) * size), z);
31
+ const hz = pageHeight(rt, page, x, Math.min(z + e, (pz + 1) * size));
32
+ const dx = (hx - h) / Math.max(1e-6, Math.min(x + e, (px + 1) * size) - x);
33
+ const dz = (hz - h) / Math.max(1e-6, Math.min(z + e, (pz + 1) * size) - z);
34
+ const len = Math.sqrt(dx * dx + 1 + dz * dz);
35
+ out[0] = h;
36
+ out[1] = -dx / len;
37
+ out[2] = 1 / len;
38
+ out[3] = -dz / len;
39
+ return true;
40
+ }
41
+ }
42
+ class HeightfieldSurface extends MeshSurface {
43
+ kind = "heightfield";
44
+ rt;
45
+ constructor(world, rt, cameras) {
46
+ super(world, rt.entity, cameras);
47
+ this.rt = rt;
48
+ this.scale = 1;
49
+ }
50
+ /** Takes the terrain's pages (again after a rebake). */
51
+ setTerrain() {
52
+ this.index = new HeightfieldIndex(this.rt);
53
+ }
54
+ placeChunkRoot(world, root, chunk) {
55
+ placeInGrid(world, root, this.entity, chunk.center);
56
+ }
57
+ }
58
+ export {
59
+ HeightfieldIndex,
60
+ HeightfieldSurface
61
+ };
@@ -0,0 +1,13 @@
1
+ export { addRemoved, isRemoved, Prop, propIdentity, Removed, type RemovedProps, removedKey, ScatterBudget, type ScatterBudgetValue, ScatterChunk, ScatterSurface, } from './components.js';
2
+ export { applyWind, clearFoliage, FoliageRule, updateFoliage, Wind, type WindValue, } from './foliage.js';
3
+ export { GENERATOR_LOOKS, SCATTER_SHADERS, SWAY_WGSL, Vegetation } from './material.js';
4
+ export { HeightIndex, MeshSurface } from './mesh-surface.js';
5
+ export { resolveSurface, samplePlacements, scatterMethods } from './methods.js';
6
+ export { foliageChunksOverlay, scatterOverlay } from './overlays.js';
7
+ export { cellsPerChunk, nodeMetres, PlanetSurface, ruleDepth, writeRotation } from './planet.js';
8
+ export { type ScatterPluginOptions, scatterPlugin, updateScatter } from './plugin.js';
9
+ export { scatterSetPreview } from './preview.js';
10
+ export { type CompiledItem, type CompiledRule, cellRandom, cellSize, compileRules, FOLIAGE, jitterFor, MAX_FOLIAGE_PER_CHUNK, MAX_PROPS_PER_CHUNK, PROP, type SetSource, } from './rules.js';
11
+ export { type ChunkState, defaultMaterial, finalPlacements, type ItemVariant, neighborChunks, PLACEMENT_DELAY, type RuleStats, Scatter, ScatterState, SurfaceScatter, } from './runtime.js';
12
+ export { PROP_COLLIDERS, type PropCollider, type ScatterItemValue, type ScatterRuleValue, ScatterSet, type ScatterSetValue, } from './set.js';
13
+ export { type ChunkPlacements, type FoliagePatch, P_CELL, P_ITEM, P_QX, P_RADIUS, P_SCALE, P_SHADE, P_VARIANT, P_X, type PatchJob, PLACEMENT_STRIDE, type PlacementJob, type Surface, type SurfaceChunk, } from './surface.js';
package/dist/index.js ADDED
@@ -0,0 +1,120 @@
1
+ import {
2
+ addRemoved,
3
+ isRemoved,
4
+ Prop,
5
+ propIdentity,
6
+ Removed,
7
+ removedKey,
8
+ ScatterBudget,
9
+ ScatterChunk,
10
+ ScatterSurface
11
+ } from "./components.js";
12
+ import {
13
+ applyWind,
14
+ clearFoliage,
15
+ FoliageRule,
16
+ updateFoliage,
17
+ Wind
18
+ } from "./foliage.js";
19
+ import { GENERATOR_LOOKS, SCATTER_SHADERS, SWAY_WGSL, Vegetation } from "./material.js";
20
+ import { HeightIndex, MeshSurface } from "./mesh-surface.js";
21
+ import { resolveSurface, samplePlacements, scatterMethods } from "./methods.js";
22
+ import { foliageChunksOverlay, scatterOverlay } from "./overlays.js";
23
+ import { cellsPerChunk, nodeMetres, PlanetSurface, ruleDepth, writeRotation } from "./planet.js";
24
+ import { scatterPlugin, updateScatter } from "./plugin.js";
25
+ import { scatterSetPreview } from "./preview.js";
26
+ import {
27
+ cellRandom,
28
+ cellSize,
29
+ compileRules,
30
+ FOLIAGE,
31
+ jitterFor,
32
+ MAX_FOLIAGE_PER_CHUNK,
33
+ MAX_PROPS_PER_CHUNK,
34
+ PROP
35
+ } from "./rules.js";
36
+ import {
37
+ defaultMaterial,
38
+ finalPlacements,
39
+ neighborChunks,
40
+ PLACEMENT_DELAY,
41
+ Scatter,
42
+ ScatterState,
43
+ SurfaceScatter
44
+ } from "./runtime.js";
45
+ import {
46
+ PROP_COLLIDERS,
47
+ ScatterSet
48
+ } from "./set.js";
49
+ import {
50
+ P_CELL,
51
+ P_ITEM,
52
+ P_QX,
53
+ P_RADIUS,
54
+ P_SCALE,
55
+ P_SHADE,
56
+ P_VARIANT,
57
+ P_X,
58
+ PLACEMENT_STRIDE
59
+ } from "./surface.js";
60
+ export {
61
+ FOLIAGE,
62
+ FoliageRule,
63
+ GENERATOR_LOOKS,
64
+ HeightIndex,
65
+ MAX_FOLIAGE_PER_CHUNK,
66
+ MAX_PROPS_PER_CHUNK,
67
+ MeshSurface,
68
+ PLACEMENT_DELAY,
69
+ PLACEMENT_STRIDE,
70
+ PROP,
71
+ PROP_COLLIDERS,
72
+ P_CELL,
73
+ P_ITEM,
74
+ P_QX,
75
+ P_RADIUS,
76
+ P_SCALE,
77
+ P_SHADE,
78
+ P_VARIANT,
79
+ P_X,
80
+ PlanetSurface,
81
+ Prop,
82
+ Removed,
83
+ SCATTER_SHADERS,
84
+ SWAY_WGSL,
85
+ Scatter,
86
+ ScatterBudget,
87
+ ScatterChunk,
88
+ ScatterSet,
89
+ ScatterState,
90
+ ScatterSurface,
91
+ SurfaceScatter,
92
+ Vegetation,
93
+ Wind,
94
+ addRemoved,
95
+ applyWind,
96
+ cellRandom,
97
+ cellSize,
98
+ cellsPerChunk,
99
+ clearFoliage,
100
+ compileRules,
101
+ defaultMaterial,
102
+ finalPlacements,
103
+ foliageChunksOverlay,
104
+ isRemoved,
105
+ jitterFor,
106
+ neighborChunks,
107
+ nodeMetres,
108
+ propIdentity,
109
+ removedKey,
110
+ resolveSurface,
111
+ ruleDepth,
112
+ samplePlacements,
113
+ scatterMethods,
114
+ scatterOverlay,
115
+ scatterPlugin,
116
+ scatterSetPreview,
117
+ updateFoliage,
118
+ updateScatter,
119
+ writeRotation
120
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Vegetation and stone for scatter items (0045): the standard material, tinted by the part code
3
+ * the engine generators write in the second uv set (x: 0 bark/base to 1 leaf/tip, y: a per-vertex
4
+ * shade), and swayed by the wind (scaled by the part code, so trunks and roots stay put).
5
+ */
6
+ export declare const Vegetation: import("@aethervtt/shard-render").MaterialType;
7
+ /**
8
+ * The sway every scatter material shares: a world-space wind vector, made tangent to the item's
9
+ * up axis, moved into object space, scaled by the vertex's part code squared (roots don't move).
10
+ */
11
+ export declare const SWAY_WGSL: (u: string) => string;
12
+ export declare const SCATTER_SHADERS: Record<string, string>;
13
+ /** Default looks per engine generator: tints, roughness, sway. */
14
+ export declare const GENERATOR_LOOKS: Record<string, {
15
+ baseTint: number[];
16
+ tipTint: number[];
17
+ roughness: number;
18
+ sway: number;
19
+ doubleSided?: boolean;
20
+ }>;