@certe/atmos-terrain 0.1.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/LICENCE +674 -0
- package/README.md +155 -0
- package/dist/chunk-key.d.ts +10 -0
- package/dist/chunk-key.d.ts.map +1 -0
- package/dist/chunk-key.js +27 -0
- package/dist/chunk-key.js.map +1 -0
- package/dist/chunk.d.ts +36 -0
- package/dist/chunk.d.ts.map +1 -0
- package/dist/chunk.js +128 -0
- package/dist/chunk.js.map +1 -0
- package/dist/density-field.d.ts +34 -0
- package/dist/density-field.d.ts.map +1 -0
- package/dist/density-field.js +68 -0
- package/dist/density-field.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/lod-chunk.d.ts +20 -0
- package/dist/lod-chunk.d.ts.map +1 -0
- package/dist/lod-chunk.js +256 -0
- package/dist/lod-chunk.js.map +1 -0
- package/dist/lod-extract.d.ts +21 -0
- package/dist/lod-extract.d.ts.map +1 -0
- package/dist/lod-extract.js +178 -0
- package/dist/lod-extract.js.map +1 -0
- package/dist/marching-cubes-tables.d.ts +28 -0
- package/dist/marching-cubes-tables.d.ts.map +1 -0
- package/dist/marching-cubes-tables.js +344 -0
- package/dist/marching-cubes-tables.js.map +1 -0
- package/dist/marching-cubes.d.ts +17 -0
- package/dist/marching-cubes.d.ts.map +1 -0
- package/dist/marching-cubes.js +200 -0
- package/dist/marching-cubes.js.map +1 -0
- package/dist/register-builtins.d.ts +2 -0
- package/dist/register-builtins.d.ts.map +1 -0
- package/dist/register-builtins.js +34 -0
- package/dist/register-builtins.js.map +1 -0
- package/dist/terrain-editor.d.ts +15 -0
- package/dist/terrain-editor.d.ts.map +1 -0
- package/dist/terrain-editor.js +85 -0
- package/dist/terrain-editor.js.map +1 -0
- package/dist/terrain-normals.d.ts +13 -0
- package/dist/terrain-normals.d.ts.map +1 -0
- package/dist/terrain-normals.js +96 -0
- package/dist/terrain-normals.js.map +1 -0
- package/dist/terrain-volume.d.ts +42 -0
- package/dist/terrain-volume.d.ts.map +1 -0
- package/dist/terrain-volume.js +146 -0
- package/dist/terrain-volume.js.map +1 -0
- package/dist/terrain-world.d.ts +67 -0
- package/dist/terrain-world.d.ts.map +1 -0
- package/dist/terrain-world.js +344 -0
- package/dist/terrain-world.js.map +1 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/package.json +29 -0
- package/src/index.ts +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# 🏔️ @certe/atmos-terrain
|
|
2
|
+
|
|
3
|
+
Voxel-based terrain system for the Atmos Engine. Provides density field primitives, marching cubes surface extraction, multi-level LOD streaming, and optional splat texturing with 3-layer blending.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🔑 Key Concepts
|
|
8
|
+
|
|
9
|
+
- **Density Field** — A function `(x, y, z) → number` where negative = solid, positive = air, surface at `isoLevel`
|
|
10
|
+
- **Marching Cubes** — Polygonizes the density field into triangle meshes per chunk
|
|
11
|
+
- **LOD Streaming** — `TerrainWorld` streams chunks around a camera with 3 LOD levels
|
|
12
|
+
- **Splat Texturing** — Blend 3 terrain textures based on surface normal and height
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🚀 Quick Start
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { TerrainWorld, noiseTerrain, registerTerrainBuiltins } from '@certe/atmos-terrain';
|
|
20
|
+
import { perlinNoise3D } from '@certe/atmos-math';
|
|
21
|
+
|
|
22
|
+
registerTerrainBuiltins();
|
|
23
|
+
|
|
24
|
+
const terrain = gameObject.addComponent(TerrainWorld);
|
|
25
|
+
terrain.setDensityFn(noiseTerrain(perlinNoise3D, 32, 0));
|
|
26
|
+
terrain.init(device, pipelineResources, scene);
|
|
27
|
+
terrain.cameraTarget = cameraGameObject;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 📖 API Overview
|
|
33
|
+
|
|
34
|
+
### Density Primitives
|
|
35
|
+
|
|
36
|
+
Build terrain shapes with CSG composition:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { sphereDensity, planeDensity, boxDensity,
|
|
40
|
+
unionDensity, subtractDensity, noiseTerrain } from '@certe/atmos-terrain';
|
|
41
|
+
|
|
42
|
+
const ground = planeDensity(0);
|
|
43
|
+
const hill = sphereDensity(10, 0, 10, 8);
|
|
44
|
+
const cave = sphereDensity(10, -2, 10, 4);
|
|
45
|
+
|
|
46
|
+
const density = subtractDensity(unionDensity(ground, hill), cave);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Function | Description |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `sphereDensity(cx, cy, cz, r)` | SDF sphere |
|
|
52
|
+
| `planeDensity(height)` | Half-space below Y |
|
|
53
|
+
| `boxDensity(cx, cy, cz, hx, hy, hz)` | SDF box |
|
|
54
|
+
| `unionDensity(a, b)` | CSG union |
|
|
55
|
+
| `intersectDensity(a, b)` | CSG intersection |
|
|
56
|
+
| `subtractDensity(a, b)` | CSG subtraction |
|
|
57
|
+
| `noiseTerrain(noiseFn, amp, baseY)` | Noise-based heightmap |
|
|
58
|
+
|
|
59
|
+
### TerrainWorld (Infinite Streaming)
|
|
60
|
+
|
|
61
|
+
Streams chunks around a focus point with 3 LOD levels:
|
|
62
|
+
|
|
63
|
+
| Property | Description |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `loadRadius` / `unloadRadius` | Chunk-distance thresholds |
|
|
66
|
+
| `maxBuildsPerFrame` / `buildBudgetMs` | Amortized build limits |
|
|
67
|
+
| `cameraTarget` | GameObject to track for streaming |
|
|
68
|
+
| `config` | `TerrainConfig` (chunkSize, voxelSize, smoothNormals) |
|
|
69
|
+
| `lodConfig` | `LODConfig` with distance thresholds |
|
|
70
|
+
|
|
71
|
+
| Method | Description |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `setDensityFn(fn)` | Set the terrain density function |
|
|
74
|
+
| `init(device, pipeline, scene)` | Initialize GPU context |
|
|
75
|
+
| `setSplatMaterials(pipeline, textures, weightFn)` | Enable 3-layer splat blending |
|
|
76
|
+
| `edit(op)` | Apply brush edit (sphere/cube) |
|
|
77
|
+
|
|
78
|
+
LOD levels: **LOD 0** (step=1, near) → **LOD 1** (step=2, medium) → **LOD 2** (step=4, far)
|
|
79
|
+
|
|
80
|
+
### TerrainVolume (Bounded Grid)
|
|
81
|
+
|
|
82
|
+
Fixed N×M×P chunk grid for smaller terrains:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const volume = gameObject.addComponent(TerrainVolume);
|
|
86
|
+
volume.chunksX = 4; volume.chunksY = 2; volume.chunksZ = 4;
|
|
87
|
+
volume.setDensityFn(myDensity);
|
|
88
|
+
volume.init(device, pipelineResources, scene);
|
|
89
|
+
volume.build();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Brush Editing
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
terrain.edit({
|
|
96
|
+
shape: 'sphere',
|
|
97
|
+
x: hitPoint[0], y: hitPoint[1], z: hitPoint[2],
|
|
98
|
+
radius: 3,
|
|
99
|
+
strength: -0.5, // negative = add, positive = remove
|
|
100
|
+
falloff: 1,
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Splat Texturing
|
|
105
|
+
|
|
106
|
+
Blend 3 textures based on surface normal and world height:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
terrain.setSplatMaterials(terrainPipeline, [grassTex, rockTex, snowTex],
|
|
110
|
+
(nx, ny, nz, worldY) => {
|
|
111
|
+
const slope = 1 - ny;
|
|
112
|
+
const snow = worldY > 20 ? 1 : 0;
|
|
113
|
+
return [1 - slope - snow, slope]; // [grass, rock], snow = remainder
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 🧠 Implementation Details
|
|
121
|
+
|
|
122
|
+
- **Vertex format**: 8 floats (pos+normal+uv) standard, 10 floats (+ weights) for splat
|
|
123
|
+
- **Skirt geometry**: Chunks extend 1 voxel past boundaries to hide LOD seams
|
|
124
|
+
- **Pooled buffers**: Scratch vertex/index/density arrays reused across builds
|
|
125
|
+
- **Deferred removal**: Old chunks kept 1 frame for smooth transitions
|
|
126
|
+
- **Chunk keys**: 30-bit packed coordinates (10 bits per axis)
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 📁 Structure
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
packages/terrain/src/
|
|
134
|
+
index.ts # Public API
|
|
135
|
+
types.ts # TerrainConfig, DensityFn, ChunkCoord, etc.
|
|
136
|
+
density-field.ts # Density primitives + CSG
|
|
137
|
+
marching-cubes.ts # Full-resolution surface extraction
|
|
138
|
+
lod-extract.ts # LOD-stepped marching cubes
|
|
139
|
+
terrain-normals.ts # Gradient + triangle normal computation
|
|
140
|
+
chunk.ts # TerrainChunk (density grid + mesh)
|
|
141
|
+
chunk-key.ts # Coordinate packing utilities
|
|
142
|
+
lod-chunk.ts # buildLODMesh / buildLODSplatMesh
|
|
143
|
+
terrain-world.ts # TerrainWorld streaming component
|
|
144
|
+
terrain-volume.ts # TerrainVolume bounded grid component
|
|
145
|
+
terrain-editor.ts # Brush edit system
|
|
146
|
+
register-builtins.ts # Component registry integration
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 🔗 Dependencies
|
|
152
|
+
|
|
153
|
+
- `@certe/atmos-core` — Component, GameObject, Scene
|
|
154
|
+
- `@certe/atmos-math` — Noise functions for terrain generation
|
|
155
|
+
- `@certe/atmos-renderer` — Mesh, Material, terrain pipeline, textures
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bit-packs chunk coordinates into a single number for use as a Map key.
|
|
3
|
+
* Supports coordinates in the range [-511, 512] per axis (10 bits each).
|
|
4
|
+
*/
|
|
5
|
+
export declare function chunkKey(cx: number, cy: number, cz: number): number;
|
|
6
|
+
/** Unpack a chunk key back into [cx, cy, cz]. */
|
|
7
|
+
export declare function fromChunkKey(key: number): [number, number, number];
|
|
8
|
+
/** Convert world position to chunk coordinates. */
|
|
9
|
+
export declare function worldToChunk(wx: number, wy: number, wz: number, chunkWorldSize: number): [number, number, number];
|
|
10
|
+
//# sourceMappingURL=chunk-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-key.d.ts","sourceRoot":"","sources":["../src/chunk-key.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAMnE;AAED,iDAAiD;AACjD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAKlE;AAED,mDAAmD;AACnD,wBAAgB,YAAY,CAC1B,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,cAAc,EAAE,MAAM,GACrB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAM1B"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bit-packs chunk coordinates into a single number for use as a Map key.
|
|
3
|
+
* Supports coordinates in the range [-511, 512] per axis (10 bits each).
|
|
4
|
+
*/
|
|
5
|
+
export function chunkKey(cx, cy, cz) {
|
|
6
|
+
// Offset to make values positive, then mask to 10 bits
|
|
7
|
+
const x = (cx + 512) & 0x3FF;
|
|
8
|
+
const y = (cy + 512) & 0x3FF;
|
|
9
|
+
const z = (cz + 512) & 0x3FF;
|
|
10
|
+
return (x << 20) | (y << 10) | z;
|
|
11
|
+
}
|
|
12
|
+
/** Unpack a chunk key back into [cx, cy, cz]. */
|
|
13
|
+
export function fromChunkKey(key) {
|
|
14
|
+
const x = ((key >>> 20) & 0x3FF) - 512;
|
|
15
|
+
const y = ((key >>> 10) & 0x3FF) - 512;
|
|
16
|
+
const z = (key & 0x3FF) - 512;
|
|
17
|
+
return [x, y, z];
|
|
18
|
+
}
|
|
19
|
+
/** Convert world position to chunk coordinates. */
|
|
20
|
+
export function worldToChunk(wx, wy, wz, chunkWorldSize) {
|
|
21
|
+
return [
|
|
22
|
+
Math.floor(wx / chunkWorldSize),
|
|
23
|
+
Math.floor(wy / chunkWorldSize),
|
|
24
|
+
Math.floor(wz / chunkWorldSize),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=chunk-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-key.js","sourceRoot":"","sources":["../src/chunk-key.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAU,EAAE,EAAU,EAAE,EAAU;IACzD,uDAAuD;IACvD,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC;IAC7B,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC;IAC7B,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC;IAC7B,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC;IACvC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC;IACvC,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC;IAC9B,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACnB,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,YAAY,CAC1B,EAAU,EAAE,EAAU,EAAE,EAAU,EAClC,cAAsB;IAEtB,OAAO;QACL,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,cAAc,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,cAAc,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,cAAc,CAAC;KAChC,CAAC;AACJ,CAAC"}
|
package/dist/chunk.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Mesh } from '@certe/atmos-renderer';
|
|
2
|
+
import type { DensityFn, TerrainConfig, MeshData } from './types.js';
|
|
3
|
+
import { ChunkState } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* A single terrain chunk: owns a density grid, scratch buffers, and a GPU mesh.
|
|
6
|
+
*/
|
|
7
|
+
export declare class TerrainChunk {
|
|
8
|
+
readonly cx: number;
|
|
9
|
+
readonly cy: number;
|
|
10
|
+
readonly cz: number;
|
|
11
|
+
readonly densityGrid: Float32Array;
|
|
12
|
+
state: ChunkState;
|
|
13
|
+
mesh: Mesh | null;
|
|
14
|
+
lastMeshData: MeshData | null;
|
|
15
|
+
lodLevel: number;
|
|
16
|
+
skirtFaces: number;
|
|
17
|
+
private readonly _size;
|
|
18
|
+
private readonly _gridLen;
|
|
19
|
+
private _scratchVerts;
|
|
20
|
+
private _scratchIdx;
|
|
21
|
+
constructor(cx: number, cy: number, cz: number, size: number);
|
|
22
|
+
/** Fill the density grid by sampling the density function. */
|
|
23
|
+
sampleDensity(fn: DensityFn, voxelSize: number): void;
|
|
24
|
+
/**
|
|
25
|
+
* Run marching cubes + normals + UVs and create a GPU mesh.
|
|
26
|
+
* Returns the Mesh, or null if the chunk produced no geometry.
|
|
27
|
+
*/
|
|
28
|
+
buildMesh(device: GPUDevice, config: TerrainConfig, densityFn?: DensityFn): Mesh | null;
|
|
29
|
+
/** Destroy the GPU mesh, keeping density data intact. */
|
|
30
|
+
destroyMesh(): void;
|
|
31
|
+
/** Release CPU-side scratch buffers (keeps GPU mesh alive). */
|
|
32
|
+
destroyCPU(): void;
|
|
33
|
+
/** Full cleanup: GPU + CPU resources. */
|
|
34
|
+
destroy(): void;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=chunk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk.d.ts","sourceRoot":"","sources":["../src/chunk.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAIlD,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAcxC;;GAEG;AACH,qBAAa,YAAY;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC;IAEnC,KAAK,aAAoB;IACzB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAQ;IACzB,YAAY,EAAE,QAAQ,GAAG,IAAI,CAAQ;IACrC,QAAQ,SAAK;IACb,UAAU,SAAK;IAEf,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,WAAW,CAA4B;gBAEnC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAS5D,8DAA8D;IAC9D,aAAa,CACX,EAAE,EAAE,SAAS,EACb,SAAS,EAAE,MAAM,GAChB,IAAI;IAoBP;;;OAGG;IACH,SAAS,CACP,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,aAAa,EACrB,SAAS,CAAC,EAAE,SAAS,GACpB,IAAI,GAAG,IAAI;IAmEd,yDAAyD;IACzD,WAAW,IAAI,IAAI;IAQnB,+DAA+D;IAC/D,UAAU,IAAI,IAAI;IAMlB,yCAAyC;IACzC,OAAO,IAAI,IAAI;CAIhB"}
|
package/dist/chunk.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createMesh } from '@certe/atmos-renderer';
|
|
2
|
+
import { computeBoundingSphere, VERTEX_STRIDE_FLOATS } from '@certe/atmos-renderer';
|
|
3
|
+
import { extractSurface, computeTriplanarUVs } from './marching-cubes.js';
|
|
4
|
+
import { computeGradientNormals, computeTriangleNormals } from './terrain-normals.js';
|
|
5
|
+
import { ChunkState } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Maximum triangles per chunk (worst case: ~5 triangles per voxel).
|
|
8
|
+
* Used to pre-allocate scratch buffers.
|
|
9
|
+
*/
|
|
10
|
+
function maxTriangles(size) {
|
|
11
|
+
return size * size * size * 5;
|
|
12
|
+
}
|
|
13
|
+
function maxVertices(size) {
|
|
14
|
+
return maxTriangles(size) * 3;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A single terrain chunk: owns a density grid, scratch buffers, and a GPU mesh.
|
|
18
|
+
*/
|
|
19
|
+
export class TerrainChunk {
|
|
20
|
+
cx;
|
|
21
|
+
cy;
|
|
22
|
+
cz;
|
|
23
|
+
densityGrid;
|
|
24
|
+
state = ChunkState.Empty;
|
|
25
|
+
mesh = null;
|
|
26
|
+
lastMeshData = null;
|
|
27
|
+
lodLevel = 0;
|
|
28
|
+
skirtFaces = 0;
|
|
29
|
+
_size;
|
|
30
|
+
_gridLen;
|
|
31
|
+
_scratchVerts = null;
|
|
32
|
+
_scratchIdx = null;
|
|
33
|
+
constructor(cx, cy, cz, size) {
|
|
34
|
+
this.cx = cx;
|
|
35
|
+
this.cy = cy;
|
|
36
|
+
this.cz = cz;
|
|
37
|
+
this._size = size;
|
|
38
|
+
this._gridLen = (size + 1) ** 3;
|
|
39
|
+
this.densityGrid = new Float32Array(this._gridLen);
|
|
40
|
+
}
|
|
41
|
+
/** Fill the density grid by sampling the density function. */
|
|
42
|
+
sampleDensity(fn, voxelSize) {
|
|
43
|
+
const s = this._size;
|
|
44
|
+
const s1 = s + 1;
|
|
45
|
+
const originX = this.cx * s * voxelSize;
|
|
46
|
+
const originY = this.cy * s * voxelSize;
|
|
47
|
+
const originZ = this.cz * s * voxelSize;
|
|
48
|
+
for (let z = 0; z <= s; z++) {
|
|
49
|
+
for (let y = 0; y <= s; y++) {
|
|
50
|
+
for (let x = 0; x <= s; x++) {
|
|
51
|
+
const wx = originX + x * voxelSize;
|
|
52
|
+
const wy = originY + y * voxelSize;
|
|
53
|
+
const wz = originZ + z * voxelSize;
|
|
54
|
+
this.densityGrid[z * s1 * s1 + y * s1 + x] = fn(wx, wy, wz);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
this.state = ChunkState.Sampled;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Run marching cubes + normals + UVs and create a GPU mesh.
|
|
62
|
+
* Returns the Mesh, or null if the chunk produced no geometry.
|
|
63
|
+
*/
|
|
64
|
+
buildMesh(device, config, densityFn) {
|
|
65
|
+
const s = this._size;
|
|
66
|
+
// Lazy-allocate scratch buffers
|
|
67
|
+
if (!this._scratchVerts) {
|
|
68
|
+
this._scratchVerts = new Float32Array(maxVertices(s) * VERTEX_STRIDE_FLOATS);
|
|
69
|
+
}
|
|
70
|
+
if (!this._scratchIdx) {
|
|
71
|
+
this._scratchIdx = new Uint32Array(maxTriangles(s) * 3);
|
|
72
|
+
}
|
|
73
|
+
const meshData = extractSurface(this.densityGrid, s, config.voxelSize, config.isoLevel, this._scratchVerts, this._scratchIdx);
|
|
74
|
+
if (meshData.vertexCount === 0 || meshData.indexCount === 0) {
|
|
75
|
+
this.destroyMesh();
|
|
76
|
+
this.lastMeshData = null;
|
|
77
|
+
this.state = ChunkState.Meshed;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
// Compute normals
|
|
81
|
+
if (config.smoothNormals && densityFn) {
|
|
82
|
+
const voxelSize = config.voxelSize;
|
|
83
|
+
const originX = this.cx * s * voxelSize;
|
|
84
|
+
const originY = this.cy * s * voxelSize;
|
|
85
|
+
const originZ = this.cz * s * voxelSize;
|
|
86
|
+
// Offset density function to chunk-local coords → world coords
|
|
87
|
+
const worldDensity = (x, y, z) => densityFn(x + originX, y + originY, z + originZ);
|
|
88
|
+
computeGradientNormals(meshData.vertices, meshData.vertexCount, worldDensity, config.normalEpsilon);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
computeTriangleNormals(meshData.vertices, meshData.indices, meshData.vertexCount, meshData.indexCount);
|
|
92
|
+
}
|
|
93
|
+
// Compute triplanar UVs after normals are ready
|
|
94
|
+
computeTriplanarUVs(meshData.vertices, meshData.vertexCount);
|
|
95
|
+
// Trim to actual size for GPU upload
|
|
96
|
+
const trimmedVerts = meshData.vertices.subarray(0, meshData.vertexCount * VERTEX_STRIDE_FLOATS);
|
|
97
|
+
const trimmedIdx = meshData.indices.subarray(0, meshData.indexCount);
|
|
98
|
+
// Detach old mesh reference (caller is responsible for destroying old
|
|
99
|
+
// GPU buffers via MeshRenderer.destroyMesh() after submit completes).
|
|
100
|
+
this.mesh = null;
|
|
101
|
+
const gpuMesh = createMesh(device, trimmedVerts, trimmedIdx, VERTEX_STRIDE_FLOATS);
|
|
102
|
+
gpuMesh.bounds = computeBoundingSphere(trimmedVerts, VERTEX_STRIDE_FLOATS);
|
|
103
|
+
this.mesh = gpuMesh;
|
|
104
|
+
this.lastMeshData = meshData;
|
|
105
|
+
this.state = ChunkState.Meshed;
|
|
106
|
+
return gpuMesh;
|
|
107
|
+
}
|
|
108
|
+
/** Destroy the GPU mesh, keeping density data intact. */
|
|
109
|
+
destroyMesh() {
|
|
110
|
+
if (this.mesh) {
|
|
111
|
+
this.mesh.vertexBuffer.destroy();
|
|
112
|
+
this.mesh.indexBuffer.destroy();
|
|
113
|
+
this.mesh = null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Release CPU-side scratch buffers (keeps GPU mesh alive). */
|
|
117
|
+
destroyCPU() {
|
|
118
|
+
this._scratchVerts = null;
|
|
119
|
+
this._scratchIdx = null;
|
|
120
|
+
this.lastMeshData = null;
|
|
121
|
+
}
|
|
122
|
+
/** Full cleanup: GPU + CPU resources. */
|
|
123
|
+
destroy() {
|
|
124
|
+
this.destroyMesh();
|
|
125
|
+
this.destroyCPU();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=chunk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk.js","sourceRoot":"","sources":["../src/chunk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEtF,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC;;;GAGG;AACH,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,YAAY;IACd,EAAE,CAAS;IACX,EAAE,CAAS;IACX,EAAE,CAAS;IACX,WAAW,CAAe;IAEnC,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;IACzB,IAAI,GAAgB,IAAI,CAAC;IACzB,YAAY,GAAoB,IAAI,CAAC;IACrC,QAAQ,GAAG,CAAC,CAAC;IACb,UAAU,GAAG,CAAC,CAAC;IAEE,KAAK,CAAS;IACd,QAAQ,CAAS;IAC1B,aAAa,GAAwB,IAAI,CAAC;IAC1C,WAAW,GAAuB,IAAI,CAAC;IAE/C,YAAY,EAAU,EAAE,EAAU,EAAE,EAAU,EAAE,IAAY;QAC1D,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,WAAW,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED,8DAA8D;IAC9D,aAAa,CACX,EAAa,EACb,SAAiB;QAEjB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QACrB,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;QAExC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC5B,MAAM,EAAE,GAAG,OAAO,GAAG,CAAC,GAAG,SAAS,CAAC;oBACnC,MAAM,EAAE,GAAG,OAAO,GAAG,CAAC,GAAG,SAAS,CAAC;oBACnC,MAAM,EAAE,GAAG,OAAO,GAAG,CAAC,GAAG,SAAS,CAAC;oBACnC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC9D,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC;IAClC,CAAC;IAED;;;OAGG;IACH,SAAS,CACP,MAAiB,EACjB,MAAqB,EACrB,SAAqB;QAErB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QAErB,gCAAgC;QAChC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,IAAI,CAAC,aAAa,GAAG,IAAI,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC;QAC/E,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,CAAC;QAED,MAAM,QAAQ,GAAG,cAAc,CAC7B,IAAI,CAAC,WAAW,EAChB,CAAC,EACD,MAAM,CAAC,SAAS,EAChB,MAAM,CAAC,QAAQ,EACf,IAAI,CAAC,aAAa,EAClB,IAAI,CAAC,WAAW,CACjB,CAAC;QAEF,IAAI,QAAQ,CAAC,WAAW,KAAK,CAAC,IAAI,QAAQ,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;YAC5D,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC;YAC/B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,kBAAkB;QAClB,IAAI,MAAM,CAAC,aAAa,IAAI,SAAS,EAAE,CAAC;YACtC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;YACnC,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;YACxC,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;YACxC,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;YACxC,+DAA+D;YAC/D,MAAM,YAAY,GAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAC1C,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC;YACnD,sBAAsB,CACpB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,WAAW,EACvC,YAAY,EAAE,MAAM,CAAC,aAAa,CACnC,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,sBAAsB,CACpB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,EACnC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,UAAU,CAC1C,CAAC;QACJ,CAAC;QAED,gDAAgD;QAChD,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;QAE7D,qCAAqC;QACrC,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,WAAW,GAAG,oBAAoB,CAAC,CAAC;QAChG,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAErE,sEAAsE;QACtE,sEAAsE;QACtE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QAEjB,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,oBAAoB,CAAC,CAAC;QACnF,OAAO,CAAC,MAAM,GAAG,qBAAqB,CAAC,YAAY,EAAE,oBAAoB,CAAC,CAAC;QAC3E,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC;QACpB,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC;QAE/B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,yDAAyD;IACzD,WAAW;QACT,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACnB,CAAC;IACH,CAAC;IAED,+DAA+D;IAC/D,UAAU;QACR,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,yCAAyC;IACzC,OAAO;QACL,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DensityFn, DensityWithWeightsFn } from './types.js';
|
|
2
|
+
/** Adapt a simple DensityFn to DensityWithWeightsFn (no weights). */
|
|
3
|
+
export declare function adaptDensityFn(fn: DensityFn): DensityWithWeightsFn;
|
|
4
|
+
/**
|
|
5
|
+
* Signed distance field for a sphere.
|
|
6
|
+
* Negative inside, positive outside (solid inside).
|
|
7
|
+
* Returns a DensityFn where negative = solid.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sphereDensity(cx: number, cy: number, cz: number, radius: number): DensityFn;
|
|
10
|
+
/**
|
|
11
|
+
* Half-space density: solid below the plane at y = height.
|
|
12
|
+
* Returns negative (solid) when y < height, positive (air) when y > height.
|
|
13
|
+
*/
|
|
14
|
+
export declare function planeDensity(height: number): DensityFn;
|
|
15
|
+
/**
|
|
16
|
+
* Axis-aligned box density. Solid inside the box.
|
|
17
|
+
* @param cx,cy,cz Center of the box
|
|
18
|
+
* @param hx,hy,hz Half-extents
|
|
19
|
+
*/
|
|
20
|
+
export declare function boxDensity(cx: number, cy: number, cz: number, hx: number, hy: number, hz: number): DensityFn;
|
|
21
|
+
/** Union: solid where either A or B is solid (min of two SDFs). */
|
|
22
|
+
export declare function unionDensity(a: DensityFn, b: DensityFn): DensityFn;
|
|
23
|
+
/** Intersection: solid where both A and B are solid (max of two SDFs). */
|
|
24
|
+
export declare function intersectDensity(a: DensityFn, b: DensityFn): DensityFn;
|
|
25
|
+
/** Subtraction: solid where A is solid but B is not (A minus B). */
|
|
26
|
+
export declare function subtractDensity(a: DensityFn, b: DensityFn): DensityFn;
|
|
27
|
+
/**
|
|
28
|
+
* Noise-based terrain: creates a heightmap-style density using an external noise function.
|
|
29
|
+
* @param noiseFn 2D noise function (x, z) => value in [-1, 1]
|
|
30
|
+
* @param amplitude Height amplitude of the noise
|
|
31
|
+
* @param baseHeight Base terrain height (y)
|
|
32
|
+
*/
|
|
33
|
+
export declare function noiseTerrain(noiseFn: (x: number, z: number) => number, amplitude: number, baseHeight: number): DensityFn;
|
|
34
|
+
//# sourceMappingURL=density-field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"density-field.d.ts","sourceRoot":"","sources":["../src/density-field.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAElE,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,EAAE,EAAE,SAAS,GAAG,oBAAoB,CAElE;AAKD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,MAAM,EAAE,MAAM,GACb,SAAS,CAOX;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GACjC,SAAS,CAYX;AAID,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAElE;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAEtE;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,EACzC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,SAAS,CAKX"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Adapt a simple DensityFn to DensityWithWeightsFn (no weights). */
|
|
2
|
+
export function adaptDensityFn(fn) {
|
|
3
|
+
return (x, y, z) => ({ density: fn(x, y, z) });
|
|
4
|
+
}
|
|
5
|
+
// --- Primitives ---
|
|
6
|
+
// Convention: positive = air, negative = solid, surface at 0
|
|
7
|
+
/**
|
|
8
|
+
* Signed distance field for a sphere.
|
|
9
|
+
* Negative inside, positive outside (solid inside).
|
|
10
|
+
* Returns a DensityFn where negative = solid.
|
|
11
|
+
*/
|
|
12
|
+
export function sphereDensity(cx, cy, cz, radius) {
|
|
13
|
+
return (x, y, z) => {
|
|
14
|
+
const dx = x - cx;
|
|
15
|
+
const dy = y - cy;
|
|
16
|
+
const dz = z - cz;
|
|
17
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz) - radius;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Half-space density: solid below the plane at y = height.
|
|
22
|
+
* Returns negative (solid) when y < height, positive (air) when y > height.
|
|
23
|
+
*/
|
|
24
|
+
export function planeDensity(height) {
|
|
25
|
+
return (_x, y, _z) => y - height;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Axis-aligned box density. Solid inside the box.
|
|
29
|
+
* @param cx,cy,cz Center of the box
|
|
30
|
+
* @param hx,hy,hz Half-extents
|
|
31
|
+
*/
|
|
32
|
+
export function boxDensity(cx, cy, cz, hx, hy, hz) {
|
|
33
|
+
return (x, y, z) => {
|
|
34
|
+
const dx = Math.abs(x - cx) - hx;
|
|
35
|
+
const dy = Math.abs(y - cy) - hy;
|
|
36
|
+
const dz = Math.abs(z - cz) - hz;
|
|
37
|
+
// Exact SDF for an AABB
|
|
38
|
+
const outsideDist = Math.sqrt(Math.max(dx, 0) ** 2 + Math.max(dy, 0) ** 2 + Math.max(dz, 0) ** 2);
|
|
39
|
+
const insideDist = Math.min(Math.max(dx, dy, dz), 0);
|
|
40
|
+
return outsideDist + insideDist;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// --- CSG Combinators ---
|
|
44
|
+
/** Union: solid where either A or B is solid (min of two SDFs). */
|
|
45
|
+
export function unionDensity(a, b) {
|
|
46
|
+
return (x, y, z) => Math.min(a(x, y, z), b(x, y, z));
|
|
47
|
+
}
|
|
48
|
+
/** Intersection: solid where both A and B are solid (max of two SDFs). */
|
|
49
|
+
export function intersectDensity(a, b) {
|
|
50
|
+
return (x, y, z) => Math.max(a(x, y, z), b(x, y, z));
|
|
51
|
+
}
|
|
52
|
+
/** Subtraction: solid where A is solid but B is not (A minus B). */
|
|
53
|
+
export function subtractDensity(a, b) {
|
|
54
|
+
return (x, y, z) => Math.max(a(x, y, z), -b(x, y, z));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Noise-based terrain: creates a heightmap-style density using an external noise function.
|
|
58
|
+
* @param noiseFn 2D noise function (x, z) => value in [-1, 1]
|
|
59
|
+
* @param amplitude Height amplitude of the noise
|
|
60
|
+
* @param baseHeight Base terrain height (y)
|
|
61
|
+
*/
|
|
62
|
+
export function noiseTerrain(noiseFn, amplitude, baseHeight) {
|
|
63
|
+
return (x, y, z) => {
|
|
64
|
+
const terrainHeight = baseHeight + noiseFn(x, z) * amplitude;
|
|
65
|
+
return y - terrainHeight;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=density-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"density-field.js","sourceRoot":"","sources":["../src/density-field.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type { DensityFn, DensityWithWeightsFn, DensitySample, SplatWeightFn, SplatTextures } from './types.js';
|
|
2
|
+
export type { ChunkCoord, TerrainConfig, MeshData, TerrainEdit, LODConfig } from './types.js';
|
|
3
|
+
export { BrushShape, ChunkState, DEFAULT_TERRAIN_CONFIG, DEFAULT_LOD_CONFIG } from './types.js';
|
|
4
|
+
export { chunkKey, fromChunkKey, worldToChunk } from './chunk-key.js';
|
|
5
|
+
export { extractSurface, computeTriplanarUVs } from './marching-cubes.js';
|
|
6
|
+
export { computeGradientNormals, computeTriangleNormals } from './terrain-normals.js';
|
|
7
|
+
export { adaptDensityFn, sphereDensity, planeDensity, boxDensity, unionDensity, intersectDensity, subtractDensity, noiseTerrain, } from './density-field.js';
|
|
8
|
+
export { TerrainChunk } from './chunk.js';
|
|
9
|
+
export { applyEdit } from './terrain-editor.js';
|
|
10
|
+
export { extractSurfaceLOD } from './lod-extract.js';
|
|
11
|
+
export { buildLODMesh, buildLODSplatMesh } from './lod-chunk.js';
|
|
12
|
+
export { TerrainVolume } from './terrain-volume.js';
|
|
13
|
+
export { TerrainWorld } from './terrain-world.js';
|
|
14
|
+
export { registerTerrainBuiltins } from './register-builtins.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,SAAS,EAAE,oBAAoB,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC/G,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9F,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGhG,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGtE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG1E,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGtF,OAAO,EACL,cAAc,EACd,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { BrushShape, ChunkState, DEFAULT_TERRAIN_CONFIG, DEFAULT_LOD_CONFIG } from './types.js';
|
|
2
|
+
// Chunk key utilities
|
|
3
|
+
export { chunkKey, fromChunkKey, worldToChunk } from './chunk-key.js';
|
|
4
|
+
// Marching cubes
|
|
5
|
+
export { extractSurface, computeTriplanarUVs } from './marching-cubes.js';
|
|
6
|
+
// Normals
|
|
7
|
+
export { computeGradientNormals, computeTriangleNormals } from './terrain-normals.js';
|
|
8
|
+
// Density primitives & CSG
|
|
9
|
+
export { adaptDensityFn, sphereDensity, planeDensity, boxDensity, unionDensity, intersectDensity, subtractDensity, noiseTerrain, } from './density-field.js';
|
|
10
|
+
// Chunk
|
|
11
|
+
export { TerrainChunk } from './chunk.js';
|
|
12
|
+
// Editor (brush system)
|
|
13
|
+
export { applyEdit } from './terrain-editor.js';
|
|
14
|
+
// LOD
|
|
15
|
+
export { extractSurfaceLOD } from './lod-extract.js';
|
|
16
|
+
export { buildLODMesh, buildLODSplatMesh } from './lod-chunk.js';
|
|
17
|
+
// Components
|
|
18
|
+
export { TerrainVolume } from './terrain-volume.js';
|
|
19
|
+
export { TerrainWorld } from './terrain-world.js';
|
|
20
|
+
// Registration
|
|
21
|
+
export { registerTerrainBuiltins } from './register-builtins.js';
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhG,sBAAsB;AACtB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEtE,iBAAiB;AACjB,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1E,UAAU;AACV,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEtF,2BAA2B;AAC3B,OAAO,EACL,cAAc,EACd,aAAa,EACb,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B,QAAQ;AACR,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,wBAAwB;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,MAAM;AACN,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEjE,aAAa;AACb,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,eAAe;AACf,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Mesh } from '@certe/atmos-renderer';
|
|
2
|
+
import type { TerrainChunk } from './chunk.js';
|
|
3
|
+
import type { DensityFn, TerrainConfig, SplatWeightFn } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Build a mesh at the given LOD level with grid overlap
|
|
6
|
+
* to hide cracks at LOD transitions.
|
|
7
|
+
*
|
|
8
|
+
* When `skirtFaces` is non-zero and `densityFn` is provided, the MC grid
|
|
9
|
+
* is extended by 1 cell (step voxels) past each boundary. The resulting
|
|
10
|
+
* mesh naturally overlaps with neighbors and the depth buffer hides seams.
|
|
11
|
+
* No extra skirt geometry — just real terrain surface past the boundary.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildLODMesh(chunk: TerrainChunk, device: GPUDevice, config: TerrainConfig, lodLevel: number, skirtFaces: number, densityFn?: DensityFn): Mesh | null;
|
|
14
|
+
/**
|
|
15
|
+
* Build a terrain mesh with splat weights (10-float stride).
|
|
16
|
+
* Runs the normal 8-float mesh build, then repacks into 10-float stride
|
|
17
|
+
* adding splat weights computed from surface normal + world height.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildLODSplatMesh(chunk: TerrainChunk, device: GPUDevice, config: TerrainConfig, lodLevel: number, skirtFaces: number, densityFn: DensityFn, weightFn: SplatWeightFn): Mesh | null;
|
|
20
|
+
//# sourceMappingURL=lod-chunk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lod-chunk.d.ts","sourceRoot":"","sources":["../src/lod-chunk.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAIlD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAkG1E;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,SAAS,GACpB,IAAI,GAAG,IAAI,CA0Gb;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,aAAa,GACtB,IAAI,GAAG,IAAI,CAwGb"}
|