diff --git a/src/engine/ChunkManager.ts b/src/engine/ChunkManager.ts index 950b498..d9f6a27 100644 --- a/src/engine/ChunkManager.ts +++ b/src/engine/ChunkManager.ts @@ -46,12 +46,17 @@ export default class ChunkManager { this.removeChunk(coord.X, coord.Z) } - for (const chunkRef of update.chunksToAdd ?? []) { - const { X: cx, Z: cz } = chunkRef.coord - if (this.activeChunks.some(c => c.coord.x === cx && c.coord.z === cz)) continue - const chunk = await this.generateChunk(cx, cz) - this.activeChunks.push(chunk) - } + const toGenerate = (update.chunksToAdd ?? []).filter(ref => { + const { X: cx, Z: cz } = ref.coord + return !this.activeChunks.some(c => c.coord.x === cx && c.coord.z === cz) + }) + + if (toGenerate.length === 0) return + + const newChunks = await Promise.all( + toGenerate.map(ref => this.generateChunk(ref.coord.X, ref.coord.Z)) + ) + this.activeChunks.push(...newChunks) } private removeChunk(cx: number, cz: number): void { diff --git a/src/engine/GameEngine.ts b/src/engine/GameEngine.ts index a0f1030..09d17be 100644 --- a/src/engine/GameEngine.ts +++ b/src/engine/GameEngine.ts @@ -39,8 +39,8 @@ export default class GameEngine { this.pointerLocked = document.pointerLockElement === canvas } - private lastStreamX = 0 - private lastStreamZ = 0 + private lastStreamX = -99999 + private lastStreamZ = -99999 constructor(device: GPUDevice, context: GPUCanvasContext, format: GPUTextureFormat) { this.device = device @@ -137,7 +137,7 @@ export default class GameEngine { if (this.playerState) { const dx = this.playerState.x - this.lastStreamX const dz = this.playerState.z - this.lastStreamZ - if (dx * dx + dz * dz > 256 * 256) { + if (dx * dx + dz * dz > 128 * 128) { this.lastStreamX = this.playerState.x this.lastStreamZ = this.playerState.z this.chunkManager?.streamUpdate(this.playerState.x, this.playerState.z) diff --git a/wasm/main.go b/wasm/main.go index ec37bc6..0fae8e6 100644 --- a/wasm/main.go +++ b/wasm/main.go @@ -60,9 +60,7 @@ func goWorldUpdate(_ js.Value, args []js.Value) any { playerZ := args[1].Float() update := globalWorld.Update(playerX, playerZ) - for _, r := range update.ChunksToAdd { - globalHeightmaps[r.Coord] = r.Heightmap - } + // Clean up heightmap cache for evicted chunks for _, c := range update.ChunksToRemove { delete(globalHeightmaps, c) } diff --git a/wasm/world/config.go b/wasm/world/config.go index b166d57..7ff47a4 100644 --- a/wasm/world/config.go +++ b/wasm/world/config.go @@ -3,9 +3,9 @@ package world // Constants mirror terra-major World.cs const ( InitialRenderRadius = 2048.0 - RenderRadius = 512.0 - DistanceThreshold = 1100.0 - MaxConcurrentChunks = 3 + RenderRadius = 1536.0 + DistanceThreshold = 2560.0 + MaxConcurrentChunks = 64 ) // ChunkCoord identifies a chunk by grid position. diff --git a/wasm/world/world.go b/wasm/world/world.go index cf4db11..5373f72 100644 --- a/wasm/world/world.go +++ b/wasm/world/world.go @@ -2,6 +2,7 @@ package world import ( "math" + "sort" "github.com/maxfelker/terrain-webgpu/wasm/terrain" ) @@ -51,9 +52,10 @@ func (w *World) Update(playerX, playerZ float64) WorldUpdate { cx := playerChunkX + dx cz := playerChunkZ + dz - worldDX := float64(cx)*chunkSize - playerX - worldDZ := float64(cz)*chunkSize - playerZ - if worldDX*worldDX+worldDZ*worldDZ > RenderRadius*RenderRadius { + // Use chunk center for circular distance check + centerDX := (float64(cx)+0.5)*chunkSize - playerX + centerDZ := (float64(cz)+0.5)*chunkSize - playerZ + if centerDX*centerDX+centerDZ*centerDZ > RenderRadius*RenderRadius { continue } @@ -61,30 +63,28 @@ func (w *World) Update(playerX, playerZ float64) WorldUpdate { if w.registry.IsActive(coord) { continue } - if !w.registry.CanDispatch() { - break - } - w.registry.MarkGenerating(coord) - hm := terrain.GenerateHeightmap(cx, cz, w.cfg) - normals := terrain.ComputeNormals(hm, w.cfg.HeightmapResolution, float64(w.cfg.Dimension), float64(w.cfg.Height)) - w.registry.MarkReady(coord) w.registry.MarkActive(coord) - - toAdd = append(toAdd, ChunkGenResult{ - Coord: coord, - Heightmap: hm, - Normals: normals, - }) + toAdd = append(toAdd, ChunkGenResult{Coord: coord}) } } + // Sort toAdd by distance from player (nearest first for better UX) + sort.Slice(toAdd, func(i, j int) bool { + ci, cj := toAdd[i].Coord, toAdd[j].Coord + di := (float64(ci.X)+0.5)*chunkSize - playerX + dj := (float64(cj.X)+0.5)*chunkSize - playerX + diz := (float64(ci.Z)+0.5)*chunkSize - playerZ + djz := (float64(cj.Z)+0.5)*chunkSize - playerZ + return di*di+diz*diz < dj*dj+djz*djz + }) + // Evict chunks beyond distance threshold toRemove := make([]ChunkCoord, 0) for _, coord := range w.registry.ActiveCoords() { - worldDX := float64(coord.X)*chunkSize - playerX - worldDZ := float64(coord.Z)*chunkSize - playerZ - if worldDX*worldDX+worldDZ*worldDZ > DistanceThreshold*DistanceThreshold { + centerDX := (float64(coord.X)+0.5)*chunkSize - playerX + centerDZ := (float64(coord.Z)+0.5)*chunkSize - playerZ + if centerDX*centerDX+centerDZ*centerDZ > DistanceThreshold*DistanceThreshold { w.registry.Remove(coord) toRemove = append(toRemove, coord) }