{"_id":"@certe/atmos-clipmap-terrain","_rev":"3-e260fbb64eb457077ec93805247e177d","name":"@certe/atmos-clipmap-terrain","dist-tags":{"latest":"0.8.17"},"versions":{"0.8.14":{"name":"@certe/atmos-clipmap-terrain","version":"0.8.14","keywords":["terrain","clipmap","lod","heightmap","game-engine","atmos"],"license":"GPL-3.0-or-later","_id":"@certe/atmos-clipmap-terrain@0.8.14","maintainers":[{"name":"certesolutions","email":"certesolutions@gmail.com"}],"homepage":"https://github.com/certesolutions-cyber/atmos","bugs":{"url":"https://github.com/certesolutions-cyber/atmos/issues"},"dist":{"shasum":"54f6d9a333322cb8bf84e21a65a9c17867b02567","tarball":"https://registry.npmjs.org/@certe/atmos-clipmap-terrain/-/atmos-clipmap-terrain-0.8.14.tgz","fileCount":38,"integrity":"sha512-GcTo/uYO4j4uBiMdAZX5tJax+IqMYiSqqkuf5KGqfs9bwatb/Ys5cD9P+GuHptSbNlVJaS30U5sQSNUpXiyusw==","signatures":[{"sig":"MEUCIQCqX7eVZtJDj1VTIIe7aBmiejJe5cXNDWVjwhF8kKCeewIgTn3960UP1L00LDQq/0oT1A0viBRYKKJn7Py/xJNm1YU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":179637},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"f6a881bd1ab98fbee304e4b80766c64e47821d42","scripts":{"test":"vitest run"},"_npmUser":{"name":"certesolutions","email":"certesolutions@gmail.com"},"repository":{"url":"git+https://github.com/certesolutions-cyber/atmos.git","type":"git","directory":"packages/clipmap-terrain"},"_npmVersion":"10.8.2","description":"GPU-driven geometry clipmap terrain for the Atmos Engine","directories":{},"_nodeVersion":"20.19.4","dependencies":{"@certe/atmos-core":"^0.8.14","@certe/atmos-math":"^0.8.14","@certe/atmos-renderer":"^0.8.14"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/atmos-clipmap-terrain_0.8.14_1776666598452_0.9470228990473746","host":"s3://npm-registry-packages-npm-production"}},"0.8.16":{"name":"@certe/atmos-clipmap-terrain","version":"0.8.16","keywords":["terrain","clipmap","lod","heightmap","game-engine","atmos"],"license":"GPL-3.0-or-later","_id":"@certe/atmos-clipmap-terrain@0.8.16","maintainers":[{"name":"certesolutions","email":"certesolutions@gmail.com"}],"homepage":"https://github.com/certesolutions-cyber/atmos","bugs":{"url":"https://github.com/certesolutions-cyber/atmos/issues"},"dist":{"shasum":"149a8dd982ef20be4ead7fcc96a94bf3e436cd91","tarball":"https://registry.npmjs.org/@certe/atmos-clipmap-terrain/-/atmos-clipmap-terrain-0.8.16.tgz","fileCount":38,"integrity":"sha512-AKOuGd61WLudgdcSzlYGhhKWI47d2BSmI13iw1LgVWsBs1pxBvfMScMJL5ytQi17cILXaKovvy/NHLnCJrrrlg==","signatures":[{"sig":"MEUCICq2QgB4L/yzJaRTJMTaIkBUg6A+vmg4192OeSyg1QEQAiEA4/Ie3gDx0JQJ/QxcVwMne1IsJNZoGR7eDkP8cysVaj4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":179637},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"98ab83c5c89e585f00281118de82927cb9470a85","scripts":{"test":"vitest run"},"_npmUser":{"name":"certesolutions","email":"certesolutions@gmail.com"},"repository":{"url":"git+https://github.com/certesolutions-cyber/atmos.git","type":"git","directory":"packages/clipmap-terrain"},"_npmVersion":"10.8.2","description":"GPU-driven geometry clipmap terrain for the Atmos Engine","directories":{},"_nodeVersion":"20.19.4","dependencies":{"@certe/atmos-core":"^0.8.16","@certe/atmos-math":"^0.8.16","@certe/atmos-renderer":"^0.8.16"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/atmos-clipmap-terrain_0.8.16_1776934302012_0.6762854255969635","host":"s3://npm-registry-packages-npm-production"}},"0.8.17":{"name":"@certe/atmos-clipmap-terrain","description":"GPU-driven geometry clipmap terrain for the Atmos Engine","version":"0.8.17","repository":{"type":"git","url":"git+https://github.com/certesolutions-cyber/atmos.git","directory":"packages/clipmap-terrain"},"homepage":"https://github.com/certesolutions-cyber/atmos","license":"GPL-3.0-or-later","keywords":["terrain","clipmap","lod","heightmap","game-engine","atmos"],"type":"module","main":"dist/index.js","types":"dist/index.d.ts","dependencies":{"@certe/atmos-core":"^0.8.17","@certe/atmos-math":"^0.8.17","@certe/atmos-renderer":"^0.8.17"},"scripts":{"test":"vitest run"},"_id":"@certe/atmos-clipmap-terrain@0.8.17","gitHead":"9deb523a3de7f05de09af17083694ebede041708","bugs":{"url":"https://github.com/certesolutions-cyber/atmos/issues"},"_nodeVersion":"20.19.4","_npmVersion":"10.8.2","dist":{"integrity":"sha512-8mZtpYvPCRjgC0c6JA8slCLKmNwA4S1cKiVhOSWk2FI7k3LQW7nZmTggwx/E5b23w2FBZF2NAoc3cnUqHEyeCQ==","shasum":"f4e01f988ada028218ef068fabc9c0758df0bc80","tarball":"https://registry.npmjs.org/@certe/atmos-clipmap-terrain/-/atmos-clipmap-terrain-0.8.17.tgz","fileCount":38,"unpackedSize":179637,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIG5y+Gc+9yiEDLLTLZLDYI4+QdRtrwj6qqududzPMK8KAiEAjlD9TnGkZKa+yPm5HBUpGVCVU4y2ScnXGZFeF8GLGtM="}]},"_npmUser":{"name":"certesolutions","email":"certesolutions@gmail.com"},"directories":{},"maintainers":[{"name":"certesolutions","email":"certesolutions@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/atmos-clipmap-terrain_0.8.17_1776942737057_0.3451521096178982"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-20T06:29:58.325Z","modified":"2026-04-23T11:12:17.358Z","0.8.14":"2026-04-20T06:29:58.607Z","0.8.16":"2026-04-23T08:51:42.151Z","0.8.17":"2026-04-23T11:12:17.225Z"},"bugs":{"url":"https://github.com/certesolutions-cyber/atmos/issues"},"license":"GPL-3.0-or-later","homepage":"https://github.com/certesolutions-cyber/atmos","keywords":["terrain","clipmap","lod","heightmap","game-engine","atmos"],"repository":{"type":"git","url":"git+https://github.com/certesolutions-cyber/atmos.git","directory":"packages/clipmap-terrain"},"description":"GPU-driven geometry clipmap terrain for the Atmos Engine","maintainers":[{"name":"certesolutions","email":"certesolutions@gmail.com"}],"readme":"# @certe/atmos-clipmap-terrain\n\nGPU-driven geometry clipmap terrain for the Atmos Engine. Renders large-scale heightmap terrain with automatic level-of-detail using concentric grid rings around the camera.\n\n## How It Works\n\nA **geometry clipmap** surrounds the camera with concentric square grid rings. Each ring doubles the cell size of the previous one, providing high detail nearby and coarse detail in the distance — all from the same grid topology.\n\n```\n┌─────────────────────────────────┐\n│  Level 2 (cell = 4)            │\n│  ┌───────────────────────────┐  │\n│  │  Level 1 (cell = 2)      │  │\n│  │  ┌─────────────────────┐  │  │\n│  │  │  Level 0 (cell = 1) │  │  │\n│  │  │       [camera]      │  │  │\n│  │  └─────────────────────┘  │  │\n│  └───────────────────────────┘  │\n└─────────────────────────────────┘\n```\n\n- **Level 0**: Full grid (gridSize x gridSize), finest detail\n- **Levels 1+**: Ring grids (inner hole cut out, filled by the finer level)\n- **Outermost level**: No stitching needed (nothing beyond it)\n\n### Camera Snapping\n\nEach ring's origin snaps to a multiple of `2 * cellSize * 2^level`. This ensures the grid moves in discrete steps rather than continuously, avoiding vertex swimming. The snap-by-2 pattern guarantees that coarser rings always contain finer rings' vertex positions.\n\n### Crack-Free Ring Stitching\n\nAdjacent LOD rings have different vertex densities. Without special handling, T-junctions at ring boundaries cause visible cracks.\n\nThis package uses **boundary stitching**: each ring's outer edge uses a special triangulation where every other vertex is skipped, creating triangles that bridge 2 fine cells to 1 coarse cell:\n\n```\nOuter edge (matches coarser):  V . V . V . V    (every 2nd vertex)\n                                \\|/ \\|/ \\|/\nInner row (fine):               v v v v v v v    (every vertex)\n```\n\nThis makes the meshes watertight by construction — no morphing or blending needed.\n\n**Important**: `gridSize` must be of the form `4k + 1` (e.g. 65, 129, 257) so that even-indexed grid vertices map to even grid coordinates, which align perfectly with the coarser ring's vertex positions regardless of snap offset.\n\n### Heightmap Sampling\n\nThe vertex shader samples an R32Float heightmap texture to displace each vertex's Y position. Since `r32float` textures don't support hardware filtering, the shader performs manual bilinear interpolation. Normals are computed from central differences in the heightmap.\n\n## Quick Start\n\n### With the Editor\n\nWhen using `startEditor()`, clipmap terrain builtins are registered automatically. Just add a `ClipmapTerrain` component to a GameObject in your scene and create an init script:\n\n```typescript\n// scripts/ProceduralTerrain.ts\nimport { Component } from '@certe/atmos-core';\nimport { RenderSystem, createMaterial } from '@certe/atmos-renderer';\nimport { ClipmapTerrain, createClipmapPipeline } from '@certe/atmos-clipmap-terrain';\n\nfunction terrainHeight(x: number, z: number): number {\n  // Your height function here\n  return Math.sin(x * 0.01) * 10 + Math.cos(z * 0.01) * 10;\n}\n\nexport class ProceduralTerrain extends Component {\n  private _initialized = false;\n\n  onPlayStop(): void {\n    this._initialized = false; // Re-init after editor pause/play\n  }\n\n  onRender(): void {\n    if (this._initialized) return;\n    const rs = RenderSystem.current;\n    if (!rs) return;\n    this._initialized = true;\n\n    const device = rs.device;\n    const pipeline = createClipmapPipeline(device);\n    const material = createMaterial({\n      albedo: [0.45, 0.55, 0.35, 1],\n      roughness: 0.9,\n      metallic: 0.0,\n    });\n\n    const terrain = this.gameObject.getComponent(ClipmapTerrain)\n      ?? this.gameObject.addComponent(ClipmapTerrain);\n\n    terrain.init(device, pipeline, {\n      heightFn: terrainHeight,\n      material,\n    });\n  }\n}\n```\n\n### Without the Editor (Standalone)\n\n```typescript\nimport { registerClipmapTerrainBuiltins } from '@certe/atmos-clipmap-terrain';\n\n// Must register before deserializing scenes that contain ClipmapTerrain\nregisterClipmapTerrainBuiltins();\n```\n\n## Configuration\n\nAll settings are in `ClipmapConfig`, configurable via the editor inspector or code:\n\n| Property | Default | Description |\n|---|---|---|\n| `gridSize` | `65` | Vertices per side. Must be `4k+1` (65, 129, 257...) |\n| `cellSize` | `1` | World-space size of finest (level 0) cell |\n| `levels` | `6` | Number of LOD rings |\n| `heightmapResolution` | `1024` | Heightmap texture width/height in pixels |\n| `heightmapWorldSize` | `2048` | World-space extent the heightmap covers |\n\n### Tuning Guide\n\n**Render distance** = `gridSize * cellSize * 2^(levels-1) / 2`\n\nWith defaults (65, 1, 6): `65 * 1 * 32 / 2 = 1040` world units.\n\n| Goal | Change |\n|---|---|\n| Double render distance | `levels: 7` (cheapest — adds one ring) |\n| Quadruple render distance | `levels: 8` |\n| Finer close-up detail | `cellSize: 0.5` (halves cell size at all levels) |\n| More vertices per ring | `gridSize: 129` (4x triangles per ring) |\n| Higher heightmap detail | `heightmapResolution: 2048` |\n| Larger world | `heightmapWorldSize: 4096` |\n\n### Performance Considerations\n\n- Each ring has `~gridSize^2` vertices. Doubling `gridSize` quadruples vertex count per ring.\n- Adding a level is much cheaper than increasing `gridSize` — it adds one ring at the coarsest scale.\n- `heightmapResolution` affects only the heightmap texture size, not vertex count.\n\n## API Reference\n\n### `ClipmapTerrain` (Component)\n\nThe main component. Add to a GameObject, then call `init()`.\n\n```typescript\nconst terrain = go.addComponent(ClipmapTerrain);\nterrain.init(device, pipeline, {\n  heightFn: (x, z) => ...,  // Procedural height function\n  material,                   // Optional: shared PBR material\n  config: { levels: 8 },     // Optional: partial config overrides\n});\n```\n\n**Properties:**\n- `config: ClipmapConfig` — grid/LOD configuration\n- `castShadow: boolean` — enable shadow casting (default `true`)\n- `receiveSSAO: boolean` — enable SSAO depth pass (default `true`)\n- `material: Material | null` — get/set the shared material for all rings\n- `rings: readonly ClipmapMeshRenderer[]` — per-ring renderers (read-only)\n\n**Methods:**\n- `init(device, pipeline, options?)` — Initialize GPU resources and create ring hierarchy\n- `updateHeightmap(heightFn)` — Re-rasterize the heightmap from a new height function\n- `setHeightmapTexture(texture)` — Replace heightmap with a pre-made R32Float GPUTexture\n\n**RendererPlugin interface** (called automatically by RenderSystem):\n- `collect(vpMatrix, cameraEye, sceneBuffer)` — Snap rings to camera, write uniforms\n- `draw(pass, shadowBindGroup)` — Main PBR render pass\n- `drawShadow(pass)` — Shadow map pass\n- `drawDepth(pass)` — Depth prepass (for SSAO)\n\n### `createClipmapPipeline(device): ClipmapPipelineResources`\n\nCreates the WebGPU render pipelines (main + shadow) and bind group layouts. Call once at init time.\n\n**Bind group layout:**\n- Group 0: Object UBO + Level UBO + Heightmap texture\n- Group 1: Material UBO + Scene UBO + Albedo texture + Sampler\n- Group 2: Shadow bind group (standard engine layout)\n\n### `createFullGrid(gridSize, stitch?): ClipmapGridData`\n\nGenerate a full grid mesh for level 0.\n\n- `gridSize` — Vertices per side (must be `4k+1`)\n- `stitch` — Enable boundary stitching (default `true`)\n\n### `createRingGrid(gridSize, stitch?): ClipmapGridData`\n\nGenerate a ring grid mesh for levels 1+. Inner hole is cut out.\n\n- `gridSize` — Vertices per side (must be `4k+1`)\n- `stitch` — Enable boundary stitching (default `true`)\n\n### `ClipmapTerrainOptions`\n\n```typescript\ninterface ClipmapTerrainOptions {\n  heightFn?: HeightFn;          // (x, z) => y\n  heightmapTexture?: GPUTexture; // Pre-made R32Float (overrides heightFn)\n  material?: Material;           // Shared PBR material\n  config?: Partial<ClipmapConfig>;\n}\n```\n\n### `HeightFn`\n\n```typescript\ntype HeightFn = (x: number, z: number) => number;\n```\n\nReturns world-space Y height for a given (x, z) position. Used to rasterize the heightmap texture at init time.\n\n### `registerClipmapTerrainBuiltins()`\n\nRegisters `ClipmapTerrain` with the component registry so it can be serialized/deserialized in scenes. Called automatically by `startEditor()`. Only needed when using the engine without the editor.\n\n## Architecture\n\n```\nclipmap-terrain/src/\n├── types.ts                 # ClipmapConfig, HeightFn, uniform sizes\n├── clipmap-grid.ts          # CPU mesh generation (full grid + ring grid + stitching)\n├── clipmap-shader.ts        # WGSL shaders (vertex, PBR fragment, shadow)\n├── clipmap-pipeline.ts      # WebGPU pipeline creation + bind group layouts\n├── clipmap-mesh-renderer.ts # Per-ring Component (GPU buffers, uniforms, draw)\n├── clipmap-terrain.ts       # Main Component (ring management, camera snap, heightmap)\n├── register-builtins.ts     # Component registry integration\n└── index.ts                 # Public exports\n```\n\n### Data Flow (Per Frame)\n\n1. `ClipmapTerrain.collect()` is called by RenderSystem with camera position\n2. Each ring's origin is snapped to a grid-aligned position based on its LOD level\n3. Per-level uniforms (origin, scale, heightmap params) are written to GPU\n4. MVP + model matrices are computed and written\n5. `draw()` binds the pipeline and issues indexed draw calls for each ring\n6. The vertex shader computes world XZ from grid coords + origin, samples the heightmap for Y, computes normals from central differences\n\n### Vertex Format\n\nEach vertex is 2 floats (8 bytes): integer grid coordinates `(ix, iz)`. The vertex shader converts these to world positions: `worldPos = origin + gridCoord * scale`. This minimal format keeps CPU mesh generation fast and GPU vertex buffers small.\n\n### Shader Pipeline\n\n- **Vertex**: Grid coord → world XZ → heightmap sample → world Y + normals\n- **Fragment**: PBR Cook-Torrance with multi-light support, shadow sampling, fog\n- **Shadow vertex**: Same displacement, outputs to light-space clip position\n\n## Dependencies\n\n- `@certe/atmos-core` — Component, GameObject, Scene\n- `@certe/atmos-math` — Mat4 for MVP computation\n- `@certe/atmos-renderer` — Mesh, Material, RenderSystem, PBR shader fragments\n","readmeFilename":"README.md"}