@aethervtt/shard-terrain 0.1.0 → 0.2.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.
Files changed (76) hide show
  1. package/dist/biomes.d.ts +1 -0
  2. package/dist/biomes.js +3 -0
  3. package/dist/chunk.d.ts +0 -44
  4. package/dist/chunk.js +2 -127
  5. package/dist/colliders.d.ts +16 -1
  6. package/dist/colliders.js +39 -24
  7. package/dist/components.d.ts +5 -0
  8. package/dist/components.js +7 -2
  9. package/dist/cube-sphere.d.ts +50 -0
  10. package/dist/cube-sphere.js +224 -0
  11. package/dist/grid-mesh.d.ts +49 -0
  12. package/dist/grid-mesh.js +208 -0
  13. package/dist/heightfield/bake.d.ts +120 -0
  14. package/dist/heightfield/bake.js +494 -0
  15. package/dist/heightfield/colliders.d.ts +39 -0
  16. package/dist/heightfield/colliders.js +152 -0
  17. package/dist/heightfield/component.d.ts +14 -0
  18. package/dist/heightfield/component.js +59 -0
  19. package/dist/heightfield/deflate.d.ts +11 -0
  20. package/dist/heightfield/deflate.js +484 -0
  21. package/dist/heightfield/heightmap.d.ts +27 -0
  22. package/dist/heightfield/heightmap.js +160 -0
  23. package/dist/heightfield/kernel.d.ts +277 -0
  24. package/dist/heightfield/kernel.js +1033 -0
  25. package/dist/heightfield/material.d.ts +13 -0
  26. package/dist/heightfield/material.js +394 -0
  27. package/dist/heightfield/methods.d.ts +104 -0
  28. package/dist/heightfield/methods.js +218 -0
  29. package/dist/heightfield/pack.d.ts +59 -0
  30. package/dist/heightfield/pack.js +127 -0
  31. package/dist/heightfield/pages.d.ts +91 -0
  32. package/dist/heightfield/pages.js +244 -0
  33. package/dist/heightfield/project.d.ts +40 -0
  34. package/dist/heightfield/project.js +55 -0
  35. package/dist/heightfield/queries.d.ts +48 -0
  36. package/dist/heightfield/queries.js +150 -0
  37. package/dist/heightfield/render.d.ts +108 -0
  38. package/dist/heightfield/render.js +727 -0
  39. package/dist/heightfield/root-grid.d.ts +38 -0
  40. package/dist/heightfield/root-grid.js +109 -0
  41. package/dist/heightfield/runtime.d.ts +105 -0
  42. package/dist/heightfield/runtime.js +312 -0
  43. package/dist/heightfield/source-asset.d.ts +39 -0
  44. package/dist/heightfield/source-asset.js +372 -0
  45. package/dist/heightfield/source.d.ts +151 -0
  46. package/dist/heightfield/source.js +402 -0
  47. package/dist/heightfield/stack.d.ts +29 -0
  48. package/dist/heightfield/stack.js +158 -0
  49. package/dist/heightfield/system.d.ts +22 -0
  50. package/dist/heightfield/system.js +165 -0
  51. package/dist/heightfield/testing.d.ts +317 -0
  52. package/dist/heightfield/testing.js +441 -0
  53. package/dist/heightfield/worker.js +47 -0
  54. package/dist/heights.d.ts +8 -3
  55. package/dist/heights.js +8 -4
  56. package/dist/index.d.ts +24 -7
  57. package/dist/index.js +158 -8
  58. package/dist/lod.d.ts +27 -0
  59. package/dist/lod.js +23 -1
  60. package/dist/material.js +2 -1
  61. package/dist/methods.js +64 -16
  62. package/dist/overlays.d.ts +2 -0
  63. package/dist/overlays.js +48 -6
  64. package/dist/planet.d.ts +2 -7
  65. package/dist/planet.js +8 -18
  66. package/dist/plugin.d.ts +10 -3
  67. package/dist/plugin.js +48 -8
  68. package/dist/quadtree.d.ts +61 -39
  69. package/dist/quadtree.js +44 -208
  70. package/dist/render.d.ts +7 -1
  71. package/dist/render.js +8 -6
  72. package/dist/test-planet.d.ts +39 -0
  73. package/dist/test-planet.js +40 -4
  74. package/dist/testing.d.ts +2 -0
  75. package/dist/testing.js +42 -2
  76. package/package.json +16 -16
@@ -0,0 +1,160 @@
1
+ import { AssetStore, defineAssetType, defineImporter } from "@aethervtt/shard-assets";
2
+ import { defineResource, defineSchema, ShardError, t } from "@aethervtt/shard-core";
3
+ import { inflate } from "./kernel.js";
4
+ const Heightmaps = defineResource("terrain/Heightmaps", {
5
+ description: "Loaded heightmaps (16-bit PNG, .r16, .r32) by guid.",
6
+ init: () => new AssetStore("Heightmap")
7
+ });
8
+ const HeightmapAssetType = defineAssetType("Heightmap", {
9
+ store: Heightmaps,
10
+ load: (artifact) => {
11
+ const info = artifact.json;
12
+ const bytes = artifact.bytes ?? new Uint8Array(0);
13
+ const n = info.width * info.height;
14
+ const data = new Float32Array(n);
15
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
16
+ if (info.bits === 16) for (let i = 0; i < n; i++) data[i] = view.getUint16(i * 2, true) / 65535;
17
+ else for (let i = 0; i < n; i++) data[i] = view.getFloat32(i * 4, true);
18
+ return { width: info.width, height: info.height, data, version: 0 };
19
+ },
20
+ update: (existing, next) => {
21
+ const e = existing;
22
+ e.width = next.width;
23
+ e.height = next.height;
24
+ e.data = next.data;
25
+ e.version++;
26
+ }
27
+ });
28
+ const HeightmapSettings = defineSchema(
29
+ "terrain/HeightmapSettings",
30
+ {
31
+ width: t.u32({ description: "Raw files (.r16, .r32): samples per row." }),
32
+ height: t.u32({ description: "Raw files (.r16, .r32): rows." })
33
+ },
34
+ {
35
+ description: "A heightmap: a 16-bit grayscale PNG (dimensions from the file), or raw little-endian samples (.r16 unsigned 16-bit, .r32 float, 0\u20131) with width and height here."
36
+ }
37
+ );
38
+ function format(path, message) {
39
+ return new ShardError("terrain/heightmap-format", `${path}: ${message}`, {
40
+ path,
41
+ hint: 'Heightmaps are 16-bit grayscale PNGs, or .r16/.r32 raw files with "width" and "height" in their .meta settings. An 8-bit PNG is a texture, not a heightmap.'
42
+ });
43
+ }
44
+ function paeth(a, b, c) {
45
+ const p = a + b - c;
46
+ const pa = Math.abs(p - a);
47
+ const pb = Math.abs(p - b);
48
+ const pc = Math.abs(p - c);
49
+ return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
50
+ }
51
+ function decodeHeightPng(path, png) {
52
+ const view = new DataView(png.buffer, png.byteOffset, png.byteLength);
53
+ if (png.length < 8 || view.getUint32(0) !== 2303741511) throw format(path, "not a PNG file");
54
+ let offset = 8;
55
+ let width = 0;
56
+ let height = 0;
57
+ let depth = 0;
58
+ let colorType = -1;
59
+ let interlace = 0;
60
+ const idat = [];
61
+ while (offset + 8 <= png.length) {
62
+ const length = view.getUint32(offset);
63
+ const type = String.fromCharCode(...png.subarray(offset + 4, offset + 8));
64
+ const data = png.subarray(offset + 8, offset + 8 + length);
65
+ if (type === "IHDR") {
66
+ width = view.getUint32(offset + 8);
67
+ height = view.getUint32(offset + 12);
68
+ depth = data[8];
69
+ colorType = data[9];
70
+ interlace = data[12];
71
+ } else if (type === "IDAT") idat.push(data);
72
+ else if (type === "IEND") break;
73
+ offset += 12 + length;
74
+ }
75
+ if (colorType !== 0 || depth !== 16) {
76
+ throw format(
77
+ path,
78
+ `a ${depth}-bit ${colorType === 0 ? "grayscale" : "color"} PNG isn't 16-bit grayscale`
79
+ );
80
+ }
81
+ if (interlace !== 0)
82
+ throw format(path, "interlaced PNGs aren\u2019t read; save it without interlacing");
83
+ const joined = new Uint8Array(idat.reduce((n, p) => n + p.length, 0));
84
+ let o = 0;
85
+ for (const p of idat) {
86
+ joined.set(p, o);
87
+ o += p.length;
88
+ }
89
+ const rowBytes = width * 2;
90
+ const raw = inflate(joined.subarray(2), (rowBytes + 1) * height);
91
+ const samples = new Uint16Array(width * height);
92
+ const prev = new Uint8Array(rowBytes);
93
+ const row = new Uint8Array(rowBytes);
94
+ for (let y = 0; y < height; y++) {
95
+ const filter = raw[y * (rowBytes + 1)];
96
+ const src = y * (rowBytes + 1) + 1;
97
+ for (let x = 0; x < rowBytes; x++) {
98
+ const v = raw[src + x];
99
+ const a = x >= 2 ? row[x - 2] : 0;
100
+ const b = prev[x];
101
+ const c = x >= 2 ? prev[x - 2] : 0;
102
+ const r = filter === 0 ? v : filter === 1 ? v + a : filter === 2 ? v + b : filter === 3 ? v + (a + b >> 1) : filter === 4 ? v + paeth(a, b, c) : -1;
103
+ if (r < 0) throw format(path, `row ${y} has an invalid filter (${filter})`);
104
+ row[x] = r & 255;
105
+ }
106
+ for (let x = 0; x < width; x++) samples[y * width + x] = row[x * 2] << 8 | row[x * 2 + 1];
107
+ prev.set(row);
108
+ }
109
+ return { width, height, bits: 16, samples };
110
+ }
111
+ const HeightmapImporter = defineImporter({
112
+ name: "heightmap",
113
+ version: 1,
114
+ extensions: [".r16", ".r32", ".height.png"],
115
+ settings: HeightmapSettings,
116
+ async import(source, ctx) {
117
+ const path = source.path;
118
+ let info;
119
+ let bytes;
120
+ if (path.endsWith(".r16") || path.endsWith(".r32")) {
121
+ const bits = path.endsWith(".r16") ? 16 : 32;
122
+ const width = Number(ctx.settings.width ?? 0);
123
+ const height = Number(ctx.settings.height ?? 0);
124
+ if (!(width > 0 && height > 0))
125
+ throw format(path, "a raw heightmap needs width and height in its .meta settings");
126
+ const expected = width * height * (bits / 8);
127
+ if (source.bytes.length !== expected) {
128
+ throw format(
129
+ path,
130
+ `${source.bytes.length} bytes, but ${width} \xD7 ${height} ${bits}-bit samples are ${expected}`
131
+ );
132
+ }
133
+ info = { width, height, bits };
134
+ bytes = source.bytes.slice();
135
+ } else {
136
+ const png = decodeHeightPng(path, source.bytes);
137
+ info = { width: png.width, height: png.height, bits: 16 };
138
+ bytes = new Uint8Array(png.samples.length * 2);
139
+ const view = new DataView(bytes.buffer);
140
+ for (let i = 0; i < png.samples.length; i++) view.setUint16(i * 2, png.samples[i], true);
141
+ }
142
+ return {
143
+ assets: [
144
+ {
145
+ label: "",
146
+ type: "Heightmap",
147
+ bytes,
148
+ json: { ...info },
149
+ info: { width: info.width, height: info.height, bits: info.bits }
150
+ }
151
+ ]
152
+ };
153
+ }
154
+ });
155
+ export {
156
+ HeightmapAssetType,
157
+ HeightmapImporter,
158
+ Heightmaps,
159
+ decodeHeightPng
160
+ };
@@ -0,0 +1,277 @@
1
+ // Types for kernel.js (plain JavaScript so worker threads can import it without a loader).
2
+
3
+ export { deflate, inflate } from './deflate.js'
4
+
5
+ export declare const PAGE: number
6
+ export declare const SIDE: number
7
+ export declare const LEAF_SIDE: number
8
+ export declare const BLOCK: number
9
+ export declare const LATTICE: number
10
+ export declare const BLOCK_LEVELS: number
11
+ export declare const BAKE_VERSION: number
12
+
13
+ export declare const BLEND_ADD: 0
14
+ export declare const BLEND_MAX: 1
15
+ export declare const BLEND_MIN: 2
16
+ export declare const BLEND_REPLACE: 3
17
+ export declare const MODE_FLATTEN: 0
18
+ export declare const MODE_RAISE: 1
19
+ export declare const MODE_CARVE: 2
20
+ export declare const LAYER_NOISE: 0
21
+ export declare const LAYER_IMAGE: 1
22
+ export declare const LAYER_SPLINE: 2
23
+
24
+ /** The noise kernel a bake samples with: this thread's instance and its functions. */
25
+ export interface NoiseAccess {
26
+ readonly state: unknown
27
+ computeOrigins(terms: unknown, origin: ArrayLike<number>, out: Int32Array): Int32Array
28
+ evalProgram(
29
+ state: unknown,
30
+ program: unknown,
31
+ seed: number,
32
+ origins: Int32Array,
33
+ pts: ArrayLike<number>,
34
+ stride: number,
35
+ count: number,
36
+ out: Float32Array,
37
+ ): void
38
+ }
39
+
40
+ /** A compiled noise program as the kernel reads it (`NoiseGraph.programFor`). */
41
+ export interface StackProgram {
42
+ readonly terms: unknown
43
+ readonly zeroOrigins: Int32Array
44
+ readonly [key: string]: unknown
45
+ }
46
+
47
+ /** A heightmap: values 0–1, row by row, top row first. */
48
+ export interface StackMap {
49
+ readonly width: number
50
+ readonly height: number
51
+ readonly data: Float32Array
52
+ }
53
+
54
+ /** A rotated rectangle: center, half width and depth, and its rotation's cosine and sine. */
55
+ export interface StackRect {
56
+ readonly cx: number
57
+ readonly cz: number
58
+ readonly hw: number
59
+ readonly hd: number
60
+ readonly cos: number
61
+ readonly sin: number
62
+ }
63
+
64
+ /** A spline ready for distance queries (buildSpline). */
65
+ export interface StackSpline {
66
+ readonly pts: Float64Array
67
+ readonly count: number
68
+ readonly width: number
69
+ readonly falloff: number
70
+ readonly reach: number
71
+ readonly cell: number
72
+ readonly x0: number
73
+ readonly z0: number
74
+ readonly cols: number
75
+ readonly rows: number
76
+ readonly start: Int32Array
77
+ readonly segs: Int32Array
78
+ readonly minX: number
79
+ readonly minZ: number
80
+ readonly maxX: number
81
+ readonly maxZ: number
82
+ }
83
+
84
+ export type StackHeightLayer =
85
+ | {
86
+ readonly kind: 0
87
+ readonly program: StackProgram
88
+ readonly scale: number
89
+ readonly offset: number
90
+ readonly blend: number
91
+ readonly rect: StackRect | null
92
+ readonly falloff: number
93
+ }
94
+ | {
95
+ readonly kind: 1
96
+ readonly map: StackMap
97
+ readonly rect: StackRect
98
+ readonly lo: number
99
+ readonly hi: number
100
+ readonly blend: number
101
+ readonly falloff: number
102
+ }
103
+ | {
104
+ readonly kind: 2
105
+ readonly spline: StackSpline
106
+ readonly mode: number
107
+ readonly offset: number
108
+ readonly falloff: number
109
+ }
110
+
111
+ export interface StackPaint {
112
+ /** Material layer index. */
113
+ readonly layer: number
114
+ readonly height: readonly [number, number] | null
115
+ readonly slope: readonly [number, number] | null
116
+ readonly noise: { readonly program: StackProgram; readonly above: number } | null
117
+ readonly mask: { readonly map: StackMap; readonly rect: StackRect } | null
118
+ readonly spline: StackSpline | null
119
+ /** Edge softness: metres for height, masks and splines, degrees for slope. */
120
+ readonly blend: number
121
+ }
122
+
123
+ /** A terrain source compiled for the kernel (heightfield/stack.ts): structured-cloneable. */
124
+ export interface Stack {
125
+ readonly spacing: number
126
+ readonly seed: number
127
+ readonly lo: number
128
+ readonly hi: number
129
+ readonly layerCount: number
130
+ readonly height: readonly StackHeightLayer[]
131
+ readonly paint: readonly StackPaint[]
132
+ }
133
+
134
+ /** What bakeBlock needs of the terrain's grid (heightfield/source.ts TerrainLayout). */
135
+ export interface KernelLayout {
136
+ readonly depth: number
137
+ readonly spacing: number
138
+ readonly leavesX: number
139
+ readonly leavesZ: number
140
+ /** Leaf samples per paint sample. */
141
+ readonly paintStep: number
142
+ /** Paint cells per page side. */
143
+ readonly cells: number
144
+ /** Leaves per block side (BLOCK; smaller in tests), and log2 of it. */
145
+ readonly block: number
146
+ readonly blockLevels: number
147
+ }
148
+
149
+ /** A decoded page: u16 heights (LEAF_SIDE² for leaves, SIDE² for parents), normals, control. */
150
+ export interface DecodedPage {
151
+ readonly heights: Uint16Array
152
+ /** Parents: x then z planes, SIDE² bytes each. Leaves: null (their heights make them). */
153
+ readonly normals: Uint8Array | null
154
+ /** (cells + 1)² texels of four bytes: heaviest layer, second, its share, hole bit. */
155
+ readonly control: Uint8Array
156
+ }
157
+
158
+ /** A baked page: its level above the leaves, node coordinates at that level, and deflated bytes. */
159
+ export interface BakedPage {
160
+ level: number
161
+ x: number
162
+ z: number
163
+ data: Uint8Array
164
+ /** Quantized height range over the node (its whole subtree). */
165
+ min: number
166
+ max: number
167
+ /** Exact geometric error (m): the largest gap between its subtree's samples and its surface. */
168
+ error: number
169
+ }
170
+
171
+ export interface BlockResult {
172
+ pages: BakedPage[]
173
+ /** Errors of the levels above the block's own, over its samples (level BLOCK_LEVELS + 1 first). */
174
+ above: number[]
175
+ /** Quantized height range of the block. */
176
+ range: [number, number]
177
+ /** Samples clipped by heightRange. */
178
+ clipped: number
179
+ lowest: number
180
+ highest: number
181
+ ms: number
182
+ }
183
+
184
+ export declare function windowWeight(x: number, lo: number, hi: number, blend: number): number
185
+ export declare function sampleMap(map: StackMap, u: number, v: number): number
186
+ export declare function buildSpline(
187
+ pts: ArrayLike<number>,
188
+ width: number,
189
+ falloff: number,
190
+ reach: number,
191
+ ): StackSpline
192
+ export declare function nearestOnSpline(
193
+ sp: StackSpline,
194
+ x: number,
195
+ z: number,
196
+ out: Float64Array,
197
+ ): number
198
+ export declare function evalHeights(
199
+ noise: NoiseAccess,
200
+ stack: Stack,
201
+ gi0: number,
202
+ gj0: number,
203
+ step: number,
204
+ nx: number,
205
+ nz: number,
206
+ upTo: number,
207
+ out: Float64Array,
208
+ ): void
209
+ export declare function evalHeightPoints(
210
+ noise: NoiseAccess,
211
+ stack: Stack,
212
+ xz: ArrayLike<number>,
213
+ count: number,
214
+ upTo: number,
215
+ out: Float64Array,
216
+ ): void
217
+ export declare function evalControl(
218
+ noise: NoiseAccess,
219
+ stack: Stack,
220
+ gi0: number,
221
+ gj0: number,
222
+ k: number,
223
+ nx: number,
224
+ nz: number,
225
+ hq: Float64Array,
226
+ hx: number,
227
+ h0: number,
228
+ out: Uint8Array,
229
+ ): Uint8Array
230
+ export declare function quantize(h: number, lo: number, hi: number): number
231
+ export declare function dequantize(q: number, lo: number, hi: number): number
232
+ export declare function normalByte(v: number): number
233
+ export declare function byteNormal(b: number): number
234
+ export declare function leafNormals(
235
+ heights: Uint16Array,
236
+ lo: number,
237
+ hi: number,
238
+ spacing: number,
239
+ out: Uint8Array,
240
+ ): Uint8Array
241
+ export declare function pageBytes(leaf: boolean, cells: number): number
242
+ export declare function encodePage(page: DecodedPage, leaf: boolean, cells: number): Uint8Array
243
+ export declare function decodePage(raw: Uint8Array, leaf: boolean, cells: number): DecodedPage
244
+ export declare function readPage(packed: Uint8Array, leaf: boolean, cells: number): DecodedPage
245
+ export declare function pageTexels(
246
+ page: DecodedPage,
247
+ leaf: boolean,
248
+ lo: number,
249
+ hi: number,
250
+ spacing: number,
251
+ texels: Uint8Array,
252
+ ): Uint8Array
253
+ export declare function bakeBlock(
254
+ noise: NoiseAccess,
255
+ stack: Stack,
256
+ layout: KernelLayout,
257
+ bx: number,
258
+ bz: number,
259
+ ): BlockResult
260
+ export declare function bakeLeaf(
261
+ noise: NoiseAccess,
262
+ stack: Stack,
263
+ layout: KernelLayout,
264
+ x: number,
265
+ z: number,
266
+ ): { page: DecodedPage; min: number; max: number }
267
+ export declare function parentPage(
268
+ children: readonly (DecodedPage | null)[],
269
+ cells: number,
270
+ normalAt: (i: number, j: number, out: Float64Array) => void,
271
+ ): DecodedPage
272
+ export declare function pageNormal(
273
+ page: DecodedPage,
274
+ i: number,
275
+ j: number,
276
+ out: Float64Array,
277
+ ): Float64Array