@aethervtt/shard-procgen 0.0.1

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.
@@ -0,0 +1,122 @@
1
+ import {
2
+ defineAssetSchema,
3
+ defineImportDependency,
4
+ defineImporter
5
+ } from "@aethervtt/shard-assets";
6
+ import { defineSchema, isPlainObject, ShardError } from "@aethervtt/shard-core";
7
+ import { codeHashOf, findGenerator, requestOf, requireGenerator } from "./generator.js";
8
+ import { paramHandles, runJob } from "./runtime.js";
9
+ import { genFileSchema } from "./schema.js";
10
+ const NoSettings = defineSchema(
11
+ "procgen/NoSettings",
12
+ {},
13
+ { description: "None: the generator, seed, and params are the file." }
14
+ );
15
+ function generatorHash(gen) {
16
+ return `${gen.version}:${codeHashOf(gen)}`;
17
+ }
18
+ const generatorDependency = defineImportDependency("generator:", (id) => {
19
+ const gen = findGenerator(id.slice("generator:".length));
20
+ return gen ? generatorHash(gen) : void 0;
21
+ });
22
+ const generatorSchema = defineAssetSchema("gen.schema.json", genFileSchema);
23
+ function parseGenFile(text, path) {
24
+ let json;
25
+ try {
26
+ json = JSON.parse(text);
27
+ } catch (cause) {
28
+ throw new ShardError("procgen/bad-file", `${path} isn't valid JSON`, { path: "", cause });
29
+ }
30
+ if (!isPlainObject(json)) {
31
+ throw new ShardError("procgen/bad-file", `${path} must be a JSON object`, { path: "" });
32
+ }
33
+ for (const key of Object.keys(json)) {
34
+ if (!["$schema", "generator", "seed", "params"].includes(key)) {
35
+ throw new ShardError("procgen/bad-file", `Unknown field "${key}"`, {
36
+ path: `/${key}`,
37
+ hint: 'A generator file has "generator", "seed", and "params".'
38
+ });
39
+ }
40
+ }
41
+ if (typeof json.generator !== "string" || json.generator === "") {
42
+ throw new ShardError("procgen/bad-file", '"generator" must name a generator', {
43
+ path: "/generator",
44
+ hint: 'e.g. "star-explorer/Rock" (.agents/generators.md lists them).'
45
+ });
46
+ }
47
+ const seed = json.seed ?? 0;
48
+ if (typeof seed !== "number" || !Number.isInteger(seed) || seed < 0 || seed > 4294967295) {
49
+ throw new ShardError("procgen/bad-params", '"seed" must be an integer from 0 to 4294967295', {
50
+ path: "/seed"
51
+ });
52
+ }
53
+ const params = json.params ?? {};
54
+ if (!isPlainObject(params)) {
55
+ throw new ShardError("procgen/bad-params", '"params" must be an object', { path: "/params" });
56
+ }
57
+ return { generator: json.generator, seed, params };
58
+ }
59
+ const GeneratorImporter = defineImporter({
60
+ name: "procgen",
61
+ version: 1,
62
+ extensions: [".gen.json"],
63
+ settings: NoSettings,
64
+ async import(source, ctx) {
65
+ const file = parseGenFile(source.text(), source.path);
66
+ let gen;
67
+ try {
68
+ gen = requireGenerator(file.generator);
69
+ } catch (err) {
70
+ throw new ShardError("procgen/unknown-generator", err.message, {
71
+ path: "/generator",
72
+ hint: err.hint
73
+ });
74
+ }
75
+ let request;
76
+ try {
77
+ request = requestOf(gen, file.params, file.seed);
78
+ } catch (err) {
79
+ const e = err;
80
+ throw new ShardError(e.code, e.message, {
81
+ path: e.path === "/seed" ? "/seed" : `/params${e.path ?? ""}`,
82
+ hint: e.hint,
83
+ details: e.details
84
+ });
85
+ }
86
+ ctx.depend(`generator:${gen.name}`, generatorHash(gen));
87
+ const deps = [];
88
+ for (const ref of paramHandles(gen, request.params)) {
89
+ if (!ref.path) continue;
90
+ const asset = await ctx.asset(`/${ref.path}`);
91
+ deps.push({ guid: asset.guid, path: ref.path, type: asset.type, artifact: asset.artifact });
92
+ }
93
+ const result = await runJob({ ...request, deps, chain: [] });
94
+ for (const w of result.warnings) ctx.warn(w.message, w.path);
95
+ const assets = result.assets.map((a) => {
96
+ const out = { label: a.label, type: a.type };
97
+ if (a.bytes) out.bytes = a.bytes;
98
+ if (a.json !== void 0) out.json = a.json;
99
+ const info = { ...a.info ?? {} };
100
+ if (a.label === "") {
101
+ info.generator = gen.name;
102
+ info.seed = request.seed;
103
+ info.ms = Math.round(result.ms * 10) / 10;
104
+ if (result.children.length > 0) info.generated = result.children.length;
105
+ }
106
+ out.info = info;
107
+ return out;
108
+ });
109
+ assets.push({
110
+ label: "Generator",
111
+ type: "Generator",
112
+ json: { generator: gen.name, seed: request.seed, params: request.params }
113
+ });
114
+ return { assets };
115
+ }
116
+ });
117
+ export {
118
+ GeneratorImporter,
119
+ generatorDependency,
120
+ generatorSchema,
121
+ parseGenFile
122
+ };
@@ -0,0 +1,14 @@
1
+ export { findNondeterminism, type NondeterministicCall } from './check.js';
2
+ export { type ContextState, createContext, type GenContext, guardNondeterminism, type LoadedDependency, type NoiseApi, } from './context.js';
3
+ export { allGenerators, canonicalParams, codeHashOf, defineGenerator, type Fragment, type FragmentEntity, findGenerator, type Generator, type GeneratorOptions, type GenParams, type GenRequest, guidOf, hashString, identityOf, type MeshResult, type OutputAsset, type OutputSpec, parseProceduralRef, proceduralPath, type RunResult, requestOf, requireGenerator, setGeneratorCodeHashes, type TextureData, } from './generator.js';
4
+ export { GeneratorImporter, type GenFile, parseGenFile } from './importer.js';
5
+ export { Generated, GeneratorInstance, instanceRequest } from './instance.js';
6
+ export { executeJob, type GenJob, type GenResult, type JobDependency, jobError, } from './job.js';
7
+ export { type MeshBuilderApi, meshBuilder } from './mesh-builder.js';
8
+ export { previewOutput, procgenMethods, type RunSummary, resolveGenerator, runGenerator, } from './methods.js';
9
+ export { encodeOutput, type GenOutputAsset, toJson } from './outputs.js';
10
+ export { type ProcgenPluginOptions, procgenPlugin } from './plugin.js';
11
+ export { decodeRecord, encodeRecord, type OutputRecord } from './record.js';
12
+ export { type CacheResult, configureProcgenHost, DataAssetType, GeneratedData, GeneratorAssetType, type GeneratorBinding, GeneratorBindings, generate, mainThreadTurn, type ProcgenHost, type ProcgenOptions, ProcgenResource, ProcgenRuntime, type ProcgenStats, paramHandles, procgen, procgenHost, procgenMainThreadMs, runJob, warmGeneratorWorkers, } from './runtime.js';
13
+ export { genFileSchema } from './schema.js';
14
+ export { contactSheet, drawLabel, parseSeeds } from './sheet.js';
package/dist/index.js ADDED
@@ -0,0 +1,109 @@
1
+ import { findNondeterminism } from "./check.js";
2
+ import {
3
+ createContext,
4
+ guardNondeterminism
5
+ } from "./context.js";
6
+ import {
7
+ allGenerators,
8
+ canonicalParams,
9
+ codeHashOf,
10
+ defineGenerator,
11
+ findGenerator,
12
+ guidOf,
13
+ hashString,
14
+ identityOf,
15
+ parseProceduralRef,
16
+ proceduralPath,
17
+ requestOf,
18
+ requireGenerator,
19
+ setGeneratorCodeHashes
20
+ } from "./generator.js";
21
+ import { GeneratorImporter, parseGenFile } from "./importer.js";
22
+ import { Generated, GeneratorInstance, instanceRequest } from "./instance.js";
23
+ import {
24
+ executeJob,
25
+ jobError
26
+ } from "./job.js";
27
+ import { meshBuilder } from "./mesh-builder.js";
28
+ import {
29
+ previewOutput,
30
+ procgenMethods,
31
+ resolveGenerator,
32
+ runGenerator
33
+ } from "./methods.js";
34
+ import { encodeOutput, toJson } from "./outputs.js";
35
+ import { procgenPlugin } from "./plugin.js";
36
+ import { decodeRecord, encodeRecord } from "./record.js";
37
+ import {
38
+ configureProcgenHost,
39
+ DataAssetType,
40
+ GeneratedData,
41
+ GeneratorAssetType,
42
+ GeneratorBindings,
43
+ generate,
44
+ mainThreadTurn,
45
+ ProcgenResource,
46
+ ProcgenRuntime,
47
+ paramHandles,
48
+ procgen,
49
+ procgenHost,
50
+ procgenMainThreadMs,
51
+ runJob,
52
+ warmGeneratorWorkers
53
+ } from "./runtime.js";
54
+ import { genFileSchema } from "./schema.js";
55
+ import { contactSheet, drawLabel, parseSeeds } from "./sheet.js";
56
+ export {
57
+ DataAssetType,
58
+ Generated,
59
+ GeneratedData,
60
+ GeneratorAssetType,
61
+ GeneratorBindings,
62
+ GeneratorImporter,
63
+ GeneratorInstance,
64
+ ProcgenResource,
65
+ ProcgenRuntime,
66
+ allGenerators,
67
+ canonicalParams,
68
+ codeHashOf,
69
+ configureProcgenHost,
70
+ contactSheet,
71
+ createContext,
72
+ decodeRecord,
73
+ defineGenerator,
74
+ drawLabel,
75
+ encodeOutput,
76
+ encodeRecord,
77
+ executeJob,
78
+ findGenerator,
79
+ findNondeterminism,
80
+ genFileSchema,
81
+ generate,
82
+ guardNondeterminism,
83
+ guidOf,
84
+ hashString,
85
+ identityOf,
86
+ instanceRequest,
87
+ jobError,
88
+ mainThreadTurn,
89
+ meshBuilder,
90
+ paramHandles,
91
+ parseGenFile,
92
+ parseProceduralRef,
93
+ parseSeeds,
94
+ previewOutput,
95
+ proceduralPath,
96
+ procgen,
97
+ procgenHost,
98
+ procgenMainThreadMs,
99
+ procgenMethods,
100
+ procgenPlugin,
101
+ requestOf,
102
+ requireGenerator,
103
+ resolveGenerator,
104
+ runGenerator,
105
+ runJob,
106
+ setGeneratorCodeHashes,
107
+ toJson,
108
+ warmGeneratorWorkers
109
+ };
@@ -0,0 +1,28 @@
1
+ import { type AssetEntry } from '@aethervtt/shard-assets';
2
+ import { type AssetRef, type JsonValue, type World } from '@aethervtt/shard-core';
3
+ import { type Generator, type GenRequest } from './generator.js';
4
+ /**
5
+ * Spawns an `entities` generator's fragment as this entity's children (and merges the fragment
6
+ * root's components into it), like a PrefabInstance. Changing `seed` or `params` regenerates; the
7
+ * children whose path is unchanged keep their entity.
8
+ */
9
+ export declare const GeneratorInstance: import("@aethervtt/shard-core").ComponentDef<{
10
+ readonly generator: import("@aethervtt/shard-core").FieldType<AssetRef<"Generator"> | null, "object">;
11
+ readonly seed: import("@aethervtt/shard-core").FieldType<number, "u32">;
12
+ readonly params: import("@aethervtt/shard-core").FieldType<JsonValue, "object">;
13
+ readonly overrides: import("@aethervtt/shard-core").FieldType<JsonValue, "object">;
14
+ }>;
15
+ /** On every entity a GeneratorInstance spawned. Such entities are never written to scene files. */
16
+ export declare const Generated: import("@aethervtt/shard-core").TagDef;
17
+ /** The request an instance's value makes, or undefined while its generator file loads. */
18
+ export declare function instanceRequest(world: World, value: {
19
+ generator?: unknown;
20
+ seed?: unknown;
21
+ params?: unknown;
22
+ }): {
23
+ request: GenRequest;
24
+ gen: Generator;
25
+ } | {
26
+ pending: AssetEntry;
27
+ } | undefined;
28
+ export declare const generatorInstanceKind: import("@aethervtt/shard-scene").InstanceKind;
@@ -0,0 +1,147 @@
1
+ import { assetServer } from "@aethervtt/shard-assets";
2
+ import {
3
+ defineComponent,
4
+ defineTag,
5
+ isPlainObject,
6
+ pointer,
7
+ ShardError,
8
+ t
9
+ } from "@aethervtt/shard-core";
10
+ import { LogResource } from "@aethervtt/shard-runtime";
11
+ import { defineInstanceKind } from "@aethervtt/shard-scene";
12
+ import { findGenerator, requestOf } from "./generator.js";
13
+ import { GeneratorBindings, procgen } from "./runtime.js";
14
+ const GeneratorInstance = defineComponent(
15
+ "procgen/GeneratorInstance",
16
+ {
17
+ generator: t.handle("Generator", {
18
+ description: `The generator, by name ({ "path": "star-explorer/StarSystem" }), or a generator file's #Generator ({ "path": "generators/sol.gen.json#Generator" }), whose seed and params are defaults.`
19
+ }),
20
+ seed: t.u32({
21
+ description: "The seed. 0 uses the generator file's seed (0 for a generator by name)."
22
+ }),
23
+ params: t.json({
24
+ default: {},
25
+ description: "Params, checked against the generator's schema; missing ones take the file's or the defaults."
26
+ }),
27
+ overrides: t.json({
28
+ default: {},
29
+ description: "Changes to generated entities by path relative to this entity, as in scene/PrefabInstance. Saves write the children's runtime changes here."
30
+ })
31
+ },
32
+ {
33
+ description: "Spawns an entities generator's output as children (procgen/Generated). Patching seed or params regenerates on the pool; children at unchanged paths keep their entity ids. Saves write only this component."
34
+ }
35
+ );
36
+ const Generated = defineTag("procgen/Generated", {
37
+ description: "Spawned by a GeneratorInstance; rebuilt from the generator, never saved.",
38
+ serialize: false
39
+ });
40
+ function instanceRequest(world, value) {
41
+ const ref = value.generator;
42
+ if (!ref) return void 0;
43
+ const server = assetServer(world);
44
+ const entry = server.entry(ref);
45
+ if (!entry) {
46
+ throw new ShardError(
47
+ "procgen/unknown-generator",
48
+ `No generator ${JSON.stringify(ref.path ?? ref.guid)}`,
49
+ { hint: 'Name a generator ("star-explorer/StarSystem") or a *.gen.json#Generator.' }
50
+ );
51
+ }
52
+ if (entry.state !== "loaded") {
53
+ if (entry.state === "unloaded") server.request(entry.guid);
54
+ return { pending: entry };
55
+ }
56
+ const binding = world.resource(GeneratorBindings).get(entry);
57
+ const gen = binding && findGenerator(binding.generator);
58
+ if (!gen) {
59
+ throw new ShardError("procgen/unknown-generator", `No generator "${binding?.generator}"`, {
60
+ hint: "Define it with project.generator in scripts/."
61
+ });
62
+ }
63
+ if (gen.output !== "entities") {
64
+ throw new ShardError(
65
+ "procgen/output-mismatch",
66
+ `GeneratorInstance needs an entities generator; ${gen.name} makes a ${gen.outputType}`,
67
+ {
68
+ hint: 'Use its output as a handle instead (a mesh: render/Mesh3d { "mesh": { "path": "procedural:\u2026" } }).'
69
+ }
70
+ );
71
+ }
72
+ const params = {
73
+ ...binding.params ?? {},
74
+ ...isPlainObject(value.params) ? value.params : {}
75
+ };
76
+ const seed = value.seed || binding.seed || 0;
77
+ return { request: requestOf(gen, params, seed), gen };
78
+ }
79
+ const reported = /* @__PURE__ */ new WeakMap();
80
+ const generatorInstanceKind = defineInstanceKind({
81
+ def: GeneratorInstance,
82
+ mark: Generated,
83
+ keepIds: true,
84
+ nested: true,
85
+ source(world, value, entity) {
86
+ let seen = reported.get(world);
87
+ if (!seen) {
88
+ seen = /* @__PURE__ */ new Map();
89
+ reported.set(world, seen);
90
+ }
91
+ try {
92
+ const made = instanceRequest(world, value);
93
+ if (!made) return void 0;
94
+ if ("pending" in made) return made.pending;
95
+ seen.delete(entity);
96
+ return procgen(world).entryFor(made.request);
97
+ } catch (err) {
98
+ const message = err.message;
99
+ if (seen.get(entity) !== message) {
100
+ seen.set(entity, message);
101
+ world.tryResource(LogResource)?.error(err);
102
+ }
103
+ return void 0;
104
+ }
105
+ },
106
+ validate(world, json, base, errors, options) {
107
+ if (!options.catalog) return;
108
+ const ref = json.generator;
109
+ if (!isPlainObject(ref)) return;
110
+ const entry = assetServer(world).entry(ref);
111
+ if (!entry) return;
112
+ const binding = entry.state === "loaded" ? world.resource(GeneratorBindings).get(entry) : void 0;
113
+ const gen = binding && findGenerator(binding.generator);
114
+ if (!gen) return;
115
+ if (gen.output !== "entities") {
116
+ errors.push(
117
+ new ShardError(
118
+ "procgen/output-mismatch",
119
+ `${gen.name} makes a ${gen.outputType}, not entities`,
120
+ {
121
+ path: pointer(base, "generator"),
122
+ hint: "GeneratorInstance spawns entities generators; put other outputs in a handle field (procedural:\u2026)."
123
+ }
124
+ )
125
+ );
126
+ return;
127
+ }
128
+ const params = {
129
+ ...binding.params ?? {},
130
+ ...isPlainObject(json.params) ? json.params : {}
131
+ };
132
+ for (const e of gen.params.validate(params)) {
133
+ errors.push(
134
+ new ShardError("procgen/bad-params", `${gen.name}: ${e.message}`, {
135
+ path: `${pointer(base, "params")}${e.path ?? ""}`,
136
+ hint: e.hint ?? `${gen.name}'s params are in .agents/generators.md.`
137
+ })
138
+ );
139
+ }
140
+ }
141
+ });
142
+ export {
143
+ Generated,
144
+ GeneratorInstance,
145
+ generatorInstanceKind,
146
+ instanceRequest
147
+ };
package/dist/job.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ import { type Artifact } from '@aethervtt/shard-assets';
2
+ import { type JsonValue } from '@aethervtt/shard-core';
3
+ import { type GenRequest } from './generator.js';
4
+ import { type GenOutputAsset } from './outputs.js';
5
+ /** An asset a job's params point at: its artifact (workers rebuild it) or the loaded object. */
6
+ export interface JobDependency {
7
+ guid: string | undefined;
8
+ path: string | undefined;
9
+ type: string;
10
+ artifact?: Artifact;
11
+ object?: unknown;
12
+ }
13
+ /** One generator run, as posted to a worker: all of it is structured-clonable. */
14
+ export interface GenJob extends GenRequest {
15
+ deps: JobDependency[];
16
+ /** Identities of the generators that asked for this one (cycle checks). */
17
+ chain: string[];
18
+ /** The noise kernel module, so a worker samples without fetching the .wasm. */
19
+ noise?: WebAssembly.Module;
20
+ noiseSimd?: boolean;
21
+ /** Guard the clock and Math.random for the whole run (workers), not just its first turn. */
22
+ guardAll?: boolean;
23
+ /** Check entities' components here (default true). Workers leave it to the main thread. */
24
+ checkComponents?: boolean;
25
+ }
26
+ export interface GenResult {
27
+ assets: GenOutputAsset[];
28
+ /** Outputs this one referenced through `ctx.generate`. */
29
+ children: GenRequest[];
30
+ warnings: {
31
+ message: string;
32
+ path?: string;
33
+ }[];
34
+ /** Time in `run` and encoding, in ms. */
35
+ ms: number;
36
+ }
37
+ /**
38
+ * Runs a generator job and encodes its output: the same code inline and on a worker. Throws
39
+ * `procgen/*` errors; anything else the generator throws becomes `procgen/generator-failed`.
40
+ */
41
+ export declare function executeJob(job: GenJob): Promise<GenResult>;
42
+ /** Plain-object errors cross the worker boundary; this keeps code, hint, and path. */
43
+ export declare function jobError(err: unknown): {
44
+ code: string;
45
+ message: string;
46
+ hint?: string;
47
+ path?: string;
48
+ };
49
+ export type { GenOutputAsset, JsonValue };
50
+ /** The identity of a job's request (for logs and stats). */
51
+ export declare function jobIdentity(job: GenJob): string;
package/dist/job.js ADDED
@@ -0,0 +1,93 @@
1
+ import { findAssetType } from "@aethervtt/shard-assets";
2
+ import { ShardError } from "@aethervtt/shard-core";
3
+ import { useNoiseKernel } from "@aethervtt/shard-noise";
4
+ import {
5
+ createContext,
6
+ guardNondeterminism
7
+ } from "./context.js";
8
+ import { identityOf, requireGenerator } from "./generator.js";
9
+ import { encodeOutput } from "./outputs.js";
10
+ function wrap(err, job) {
11
+ if (err instanceof ShardError && err.code.startsWith("procgen/")) return err;
12
+ const message = err?.message ?? String(err);
13
+ return new ShardError(
14
+ "procgen/generator-failed",
15
+ `${job.generator} failed (seed ${job.seed}, params ${JSON.stringify(job.params)}): ${message}`,
16
+ {
17
+ hint: "The error is in the generator; procgen.run with the same seed and params reproduces it.",
18
+ cause: err
19
+ }
20
+ );
21
+ }
22
+ async function loadDependencies(job) {
23
+ const out = [];
24
+ for (const d of job.deps) {
25
+ if (d.object !== void 0) {
26
+ out.push({ guid: d.guid, path: d.path, type: d.type, object: d.object });
27
+ continue;
28
+ }
29
+ const type = findAssetType(d.type);
30
+ if (!type || !d.artifact) {
31
+ throw new ShardError(
32
+ "procgen/dependency-failed",
33
+ `${job.generator} needs ${d.path ?? d.guid}, a ${d.type}, which can't be loaded here`,
34
+ { hint: "Add the plugin that defines the asset type to the project." }
35
+ );
36
+ }
37
+ const object = await type.load(d.artifact, {
38
+ guid: d.guid ?? "",
39
+ path: d.path ?? "",
40
+ resolve: () => void 0
41
+ });
42
+ out.push({ guid: d.guid, path: d.path, type: d.type, object });
43
+ }
44
+ return out;
45
+ }
46
+ async function executeJob(job) {
47
+ const gen = requireGenerator(job.generator);
48
+ if (job.noise) useNoiseKernel(job.noise, job.noiseSimd ?? true);
49
+ const deps = await loadDependencies(job);
50
+ const state = { children: [], warnings: [] };
51
+ const params = gen.params.deserialize(job.params);
52
+ const ctx = createContext(gen, job, deps, job.chain, state);
53
+ const start = performance.now();
54
+ let restore = guardNondeterminism(gen.name);
55
+ try {
56
+ let result = gen.run(ctx, params);
57
+ if (!job.guardAll) {
58
+ restore();
59
+ restore = void 0;
60
+ }
61
+ if (result instanceof Promise) result = await result;
62
+ const assets = encodeOutput(gen, result, { checkComponents: job.checkComponents });
63
+ restore?.();
64
+ restore = void 0;
65
+ return {
66
+ assets,
67
+ children: state.children,
68
+ warnings: state.warnings,
69
+ ms: performance.now() - start
70
+ };
71
+ } catch (err) {
72
+ throw wrap(err, job);
73
+ } finally {
74
+ restore?.();
75
+ }
76
+ }
77
+ function jobError(err) {
78
+ const e = err;
79
+ return {
80
+ code: e?.code ?? "procgen/generator-failed",
81
+ message: e?.message ?? String(err),
82
+ ...e?.hint ? { hint: e.hint } : {},
83
+ ...e?.path ? { path: e.path } : {}
84
+ };
85
+ }
86
+ function jobIdentity(job) {
87
+ return identityOf(job);
88
+ }
89
+ export {
90
+ executeJob,
91
+ jobError,
92
+ jobIdentity
93
+ };
@@ -0,0 +1,27 @@
1
+ import { type MeshData } from '@aethervtt/shard-mesh';
2
+ import type { MeshResult } from './generator.js';
3
+ /**
4
+ * Mesh building inside a generator. Deterministic across hosts: only +, *, and sqrt (no trig),
5
+ * so the same inputs give the same bytes in Node, Chrome, and WebKit.
6
+ */
7
+ export interface MeshBuilderApi {
8
+ /**
9
+ * A unit icosphere (radius 1) subdivided `detail` times: 12 · 4^detail vertices (well, 10 · 4^d
10
+ * + 2), shared between faces, with normals.
11
+ */
12
+ icosphere(detail: number): MeshData;
13
+ /** One of the built-in primitives (the `procedural:` meshes) as data you can change. */
14
+ primitive(name: 'box' | 'cube' | 'sphere' | 'plane' | 'cylinder' | 'cone' | 'capsule' | 'torus', params?: Record<string, number>): MeshData;
15
+ /** Recomputes smooth normals from the triangles (area-weighted). */
16
+ computeNormals(mesh: MeshData): MeshData;
17
+ /**
18
+ * Finishes a mesh: optional normals and tangents, and levels of detail as fractions of the
19
+ * triangle count (`lods: [0.5, 0.2]`), made by vertex clustering.
20
+ */
21
+ finish(mesh: MeshData, options?: {
22
+ normals?: boolean;
23
+ tangents?: boolean;
24
+ lods?: readonly number[];
25
+ }): MeshResult;
26
+ }
27
+ export declare const meshBuilder: MeshBuilderApi;