diff --git a/game/src/main/scala/hexacraft/game/GameScene.scala b/game/src/main/scala/hexacraft/game/GameScene.scala index 9c50c6e2..87edd060 100644 --- a/game/src/main/scala/hexacraft/game/GameScene.scala +++ b/game/src/main/scala/hexacraft/game/GameScene.scala @@ -55,7 +55,8 @@ class GameScene( private val otherPlayer: ControlledPlayerEntity = new ControlledPlayerEntity(PlayerEntityModel.create("player"), new EntityBaseData(CylCoords(player.position))) - private val worldRenderer: WorldRenderer = new WorldRenderer(world, blockSpecRegistry, initialWindowSize.physicalSize) + private val worldRenderer: WorldRenderer = + new WorldRenderer(world, world.requestRenderUpdate, blockSpecRegistry, initialWindowSize.physicalSize) world.trackEvents(worldRenderer.onWorldEvent _) // worldRenderer.addPlayer(otherPlayer) diff --git a/game/src/main/scala/hexacraft/world/LightPropagator.scala b/game/src/main/scala/hexacraft/world/LightPropagator.scala index 29deda12..260ba29a 100644 --- a/game/src/main/scala/hexacraft/world/LightPropagator.scala +++ b/game/src/main/scala/hexacraft/world/LightPropagator.scala @@ -3,11 +3,11 @@ package hexacraft.world import hexacraft.math.MathUtils.oppositeSide import hexacraft.world.block.BlockState import hexacraft.world.chunk.{Chunk, LocalBlockState} -import hexacraft.world.coord.{BlockRelChunk, BlockRelWorld, NeighborOffsets} +import hexacraft.world.coord.{BlockRelChunk, BlockRelWorld, ChunkRelWorld, NeighborOffsets} import scala.collection.mutable -class LightPropagator(world: BlocksInWorld)(using CylinderSize) { +class LightPropagator(world: BlocksInWorld, requestRenderUpdate: ChunkRelWorld => Unit)(using CylinderSize) { private val chunkCache: ChunkCache = new ChunkCache(world) def initBrightnesses(chunk: Chunk): Unit = { @@ -133,7 +133,7 @@ class LightPropagator(world: BlocksInWorld)(using CylinderSize) { } } - chunksNeedingRenderUpdate.foreach(_.requestRenderUpdate()) + chunksNeedingRenderUpdate.foreach(c => requestRenderUpdate(c.coords)) propagateTorchlight(lightQueue) } @@ -167,7 +167,7 @@ class LightPropagator(world: BlocksInWorld)(using CylinderSize) { } } - chunksNeedingRenderUpdate.foreach(_.requestRenderUpdate()) + chunksNeedingRenderUpdate.foreach(c => requestRenderUpdate(c.coords)) propagateSunlight(lightQueue) } @@ -201,7 +201,7 @@ class LightPropagator(world: BlocksInWorld)(using CylinderSize) { } } - chunksNeedingRenderUpdate.foreach(_.requestRenderUpdate()) + chunksNeedingRenderUpdate.foreach(c => requestRenderUpdate(c.coords)) } private def propagateSunlight(queue: mutable.Queue[(BlockRelChunk, Chunk)]): Unit = { @@ -225,7 +225,7 @@ class LightPropagator(world: BlocksInWorld)(using CylinderSize) { } } - chunksNeedingRenderUpdate.foreach(_.requestRenderUpdate()) + chunksNeedingRenderUpdate.foreach(c => requestRenderUpdate(c.coords)) } private def propagateSunlightInChunk( diff --git a/game/src/main/scala/hexacraft/world/World.scala b/game/src/main/scala/hexacraft/world/World.scala index 7445da1d..1fa03d79 100644 --- a/game/src/main/scala/hexacraft/world/World.scala +++ b/game/src/main/scala/hexacraft/world/World.scala @@ -17,17 +17,16 @@ object World: var shouldChillChunkLoader = false enum Event: - case ChunkAdded(chunk: Chunk) + case ChunkAdded(coords: ChunkRelWorld) case ChunkRemoved(coords: ChunkRelWorld) case ChunkNeedsRenderUpdate(coords: ChunkRelWorld) - case BlockReplaced(coords: BlockRelWorld, prev: BlockState, now: BlockState) class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRepository with BlocksInWorld: given size: CylinderSize = worldInfo.worldSize private val worldGenerator = new WorldGenerator(worldInfo.gen) private val worldPlanner: WorldPlanner = WorldPlanner(this, worldInfo.gen.seed) - private val lightPropagator: LightPropagator = new LightPropagator(this) + private val lightPropagator: LightPropagator = new LightPropagator(this, this.requestRenderUpdate) val renderDistance: Double = 8 * CylinderSize.y60 @@ -40,7 +39,6 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep private val blocksToUpdate: UniqueQueue[BlockRelWorld] = new UniqueQueue private val savedChunkModCounts = mutable.Map.empty[ChunkRelWorld, Long] - private val chunkEventTrackerRevokeFns = mutable.Map.empty[ChunkRelWorld, RevokeTrackerFn] private val dispatcher = new EventDispatcher[World.Event] def trackEvents(tracker: Tracker[World.Event]): Unit = dispatcher.track(tracker) @@ -79,13 +77,17 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep def setBlock(coords: BlockRelWorld, block: BlockState): Unit = getChunk(coords.getChunkRelWorld) match - case Some(chunk) => chunk.setBlock(coords.getBlockRelChunk, block) - case None => + case Some(chunk) => + chunk.setBlock(coords.getBlockRelChunk, block) + onSetBlock(coords, block) + case None => def removeBlock(coords: BlockRelWorld): Unit = getChunk(coords.getChunkRelWorld) match - case Some(chunk) => chunk.removeBlock(coords.getBlockRelChunk) - case None => + case Some(chunk) => + chunk.removeBlock(coords.getBlockRelChunk) + onSetBlock(coords, BlockState.Air) + case None => def addEntity(entity: Entity): Unit = chunkOfEntity(entity) match @@ -112,20 +114,19 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep ensureColumnExists(coords).terrainHeight(x & 15, z & 15) def setChunk(ch: Chunk): Unit = - ensureColumnExists(ch.coords.getColumnRelWorld).setChunk(ch) + val col = ensureColumnExists(ch.coords.getColumnRelWorld) + col.setChunk(ch) - val revoke = ch.trackEvents(onChunkEvent _) - chunkEventTrackerRevokeFns += ch.coords -> revoke - - dispatcher.notify(World.Event.ChunkAdded(ch)) - - ch.requestRenderUpdate() - requestRenderUpdateForNeighborChunks(ch.coords) + dispatcher.notify(World.Event.ChunkAdded(ch.coords)) worldPlanner.decorate(ch) if ch.modCount != savedChunkModCounts.getOrElse(ch.coords, -1L) then worldProvider.saveChunkData(ch.toNbt, ch.coords) savedChunkModCounts(ch.coords) = ch.modCount + col.updateHeightmapAfterChunkUpdate(ch) + + requestRenderUpdate(ch.coords) + requestRenderUpdateForNeighborChunks(ch.coords) for block <- ch.blocks do requestBlockUpdate(BlockRelWorld.fromChunk(block.coords, ch.coords)) @@ -139,10 +140,6 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep def removeChunk(ch: ChunkRelWorld): Unit = for col <- columns.get(ch.getColumnRelWorld.value) do for removedChunk <- col.removeChunk(ch.Y) do - chunkEventTrackerRevokeFns.remove(removedChunk.coords) match - case Some(revoke) => revoke() - case None => - dispatcher.notify(World.Event.ChunkRemoved(removedChunk.coords)) if removedChunk.modCount != savedChunkModCounts.getOrElse(removedChunk.coords, -1L) then @@ -198,7 +195,7 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep for side <- 0 until 8 ch <- getChunk(coords.offset(NeighborOffsets(side))) - do ch.requestRenderUpdate() + do requestRenderUpdate(ch.coords) private def ensureColumnExists(here: ColumnRelWorld): ChunkColumn = columns.get(here.value) match @@ -217,7 +214,14 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep case Some(c) => c.lighting.getBrightness(block.getBlockRelChunk) case None => 1.0f - def onReloadedResources(): Unit = for col <- columns.values do col.onReloadedResources() + def onReloadedResources(): Unit = + for + col <- columns.values + ch <- col.allChunks + do requestRenderUpdate(ch.coords) + + def requestRenderUpdate(chunkCoords: ChunkRelWorld): Unit = + dispatcher.notify(World.Event.ChunkNeedsRenderUpdate(chunkCoords)) def unload(): Unit = blockUpdateTimer.enabled = false @@ -229,14 +233,6 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep columns.clear() chunkLoader.unload() - private def onChunkEvent(event: Chunk.Event): Unit = - event match - case Chunk.Event.ChunkNeedsRenderUpdate(coords) => - dispatcher.notify(World.Event.ChunkNeedsRenderUpdate(coords)) - case Chunk.Event.BlockReplaced(coords, prev, block) => - onSetBlock(coords, block) - dispatcher.notify(World.Event.BlockReplaced(coords, prev, block)) - private def requestBlockUpdate(coords: BlockRelWorld): Unit = blocksToUpdate.enqueue(coords) private def onSetBlock(coords: BlockRelWorld, block: BlockState): Unit = @@ -260,7 +256,7 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep for c <- getChunk(cCoords) do handleLightingOnSetBlock(c, bCoords, block) - c.requestRenderUpdate() + requestRenderUpdate(c.coords) requestBlockUpdate(BlockRelWorld.fromChunk(bCoords, c.coords)) for i <- 0 until 8 do @@ -269,7 +265,7 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRep if isInNeighborChunk(off) then for n <- getChunk(cCoords.offset(NeighborOffsets(i))) do - n.requestRenderUpdate() + requestRenderUpdate(n.coords) requestBlockUpdate(BlockRelWorld.fromChunk(c2, n.coords)) else requestBlockUpdate(BlockRelWorld.fromChunk(c2, c.coords)) diff --git a/game/src/main/scala/hexacraft/world/WorldPlanner.scala b/game/src/main/scala/hexacraft/world/WorldPlanner.scala index ee968c62..a0976fca 100644 --- a/game/src/main/scala/hexacraft/world/WorldPlanner.scala +++ b/game/src/main/scala/hexacraft/world/WorldPlanner.scala @@ -17,9 +17,9 @@ class WorldPlanner(world: BlocksInWorld, mainSeed: Long)(using CylinderSize): def onWorldEvent(event: World.Event): Unit = event match - case World.Event.ChunkAdded(chunk) => + case World.Event.ChunkAdded(coords) => for - ch <- chunk.coords.extendedNeighbors(1) + ch <- coords.extendedNeighbors(1) p <- planners do p.plan(ch) case World.Event.ChunkRemoved(_) => diff --git a/game/src/main/scala/hexacraft/world/chunk/chunk.scala b/game/src/main/scala/hexacraft/world/chunk/chunk.scala index 7208f8bd..af5efaf1 100644 --- a/game/src/main/scala/hexacraft/world/chunk/chunk.scala +++ b/game/src/main/scala/hexacraft/world/chunk/chunk.scala @@ -1,10 +1,10 @@ package hexacraft.world.chunk -import hexacraft.util.{EventDispatcher, RevokeTrackerFn, SmartArray, Tracker} import hexacraft.util.Result.{Err, Ok} -import hexacraft.world.{BlocksInWorld, CollisionDetector, CylinderSize, LightPropagator, WorldGenerator} +import hexacraft.util.SmartArray +import hexacraft.world.* import hexacraft.world.block.BlockState -import hexacraft.world.coord.{BlockRelChunk, BlockRelWorld, ChunkRelWorld} +import hexacraft.world.coord.{BlockRelChunk, ChunkRelWorld} import hexacraft.world.entity.{Entity, EntityFactory} import com.martomate.nbt.Nbt @@ -13,10 +13,6 @@ import scala.annotation.tailrec import scala.collection.mutable object Chunk: - enum Event: - case ChunkNeedsRenderUpdate(coords: ChunkRelWorld) - case BlockReplaced(coords: BlockRelWorld, prev: BlockState, now: BlockState) - def fromNbt(coords: ChunkRelWorld, loadedTag: Nbt.MapTag)(using CylinderSize): Chunk = new Chunk(coords, ChunkData.fromNBT(loadedTag)) @@ -28,9 +24,6 @@ class Chunk private (val coords: ChunkRelWorld, chunkData: ChunkData)(using Cyli private var _modCount: Long = 0L def modCount: Long = _modCount - private val dispatcher = new EventDispatcher[Chunk.Event] - def trackEvents(tracker: Tracker[Chunk.Event]): RevokeTrackerFn = dispatcher.track(tracker) - val lighting: ChunkLighting = new ChunkLighting def initLightingIfNeeded(lightPropagator: LightPropagator): Unit = if !lighting.initialized then @@ -57,13 +50,8 @@ class Chunk private (val coords: ChunkRelWorld, chunkData: ChunkData)(using Cyli chunkData.setBlock(blockCoords, block) _modCount += 1 - dispatcher.notify(Chunk.Event.BlockReplaced(BlockRelWorld.fromChunk(blockCoords, coords), before, block)) - def removeBlock(coords: BlockRelChunk): Unit = setBlock(coords, BlockState.Air) - def requestRenderUpdate(): Unit = - dispatcher.notify(Chunk.Event.ChunkNeedsRenderUpdate(coords)) - def tick(world: BlocksInWorld, collisionDetector: CollisionDetector): Unit = chunkData.optimizeStorage() diff --git a/game/src/main/scala/hexacraft/world/chunk/column.scala b/game/src/main/scala/hexacraft/world/chunk/column.scala index 9e1c9ea7..e35f09a5 100644 --- a/game/src/main/scala/hexacraft/world/chunk/column.scala +++ b/game/src/main/scala/hexacraft/world/chunk/column.scala @@ -2,7 +2,7 @@ package hexacraft.world.chunk import hexacraft.math.bits.Int12 import hexacraft.math.noise.Data2D -import hexacraft.world.{BlocksInWorld, CollisionDetector} +import hexacraft.world.{BlocksInWorld, CollisionDetector, CylinderSize} import hexacraft.world.block.{Block, BlockState} import hexacraft.world.coord.{BlockRelChunk, BlockRelWorld, ChunkRelWorld, ColumnRelWorld} @@ -82,15 +82,21 @@ class ChunkColumn private ( def allChunks: Iterable[Chunk] = chunks.values + def updateHeightmapAfterChunkUpdate(chunk: Chunk)(using CylinderSize): Unit = + val chunkCoords = chunk.coords + for + cx <- 0 until 16 + cz <- 0 until 16 + do + val blockCoords = BlockRelChunk(cx, 15, cz) + updateHeightmapAfterBlockUpdate(BlockRelWorld.fromChunk(blockCoords, chunkCoords), chunk.getBlock(blockCoords)) + def updateHeightmapAfterBlockUpdate(coords: BlockRelWorld, now: BlockState): Unit = val height = terrainHeight(coords.cx, coords.cz) - if now.blockType != Block.Air then { // a block is being added - if coords.y > height then heightMap(coords.cx)(coords.cz) = coords.y.toShort - } else { // a block is being removed - if coords.y == height then - // remove and find the next highest - + if coords.y >= height then + if now.blockType != Block.Air then heightMap(coords.cx)(coords.cz) = coords.y.toShort + else heightMap(coords.cx)(coords.cz) = LazyList .range((height - 1).toShort, Short.MinValue, -1.toShort) .map(y => @@ -101,7 +107,6 @@ class ChunkColumn private ( .takeWhile(_ != null) // stop searching if the chunk is not loaded .collectFirst({ case (y, block) if block.blockType != Block.Air => y }) .getOrElse(Short.MinValue) - } private def onChunkLoaded(chunk: Chunk): Unit = val yy = chunk.coords.Y.toInt * 16 @@ -120,7 +125,4 @@ class ChunkColumn private ( def tick(world: BlocksInWorld, collisionDetector: CollisionDetector): Unit = chunks.foreachValue(_.tick(world, collisionDetector)) - def onReloadedResources(): Unit = - chunks.foreachValue(_.requestRenderUpdate()) - def toNBT: Nbt.MapTag = ChunkColumnData(Some(heightMap)).toNBT diff --git a/game/src/main/scala/hexacraft/world/loader.scala b/game/src/main/scala/hexacraft/world/loader.scala index 82072251..d3867e48 100644 --- a/game/src/main/scala/hexacraft/world/loader.scala +++ b/game/src/main/scala/hexacraft/world/loader.scala @@ -53,7 +53,7 @@ class ChunkLoader( def onWorldEvent(event: World.Event): Unit = event match - case World.Event.ChunkAdded(chunk) => chunksLoading -= chunk.coords + case World.Event.ChunkAdded(coords) => chunksLoading -= coords case World.Event.ChunkRemoved(coords) => chunksUnloading -= coords case _ => diff --git a/game/src/main/scala/hexacraft/world/render/WorldRenderer.scala b/game/src/main/scala/hexacraft/world/render/WorldRenderer.scala index a31d6949..ab82c3e6 100644 --- a/game/src/main/scala/hexacraft/world/render/WorldRenderer.scala +++ b/game/src/main/scala/hexacraft/world/render/WorldRenderer.scala @@ -13,7 +13,12 @@ import org.joml.{Vector2ic, Vector3f} import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -class WorldRenderer(world: BlocksInWorld, blockSpecs: BlockSpecRegistry, initialFramebufferSize: Vector2ic)(using +class WorldRenderer( + world: BlocksInWorld, + requestRenderUpdate: ChunkRelWorld => Unit, + blockSpecs: BlockSpecRegistry, + initialFramebufferSize: Vector2ic +)(using CylinderSize ): private val skyShader = new SkyShader() @@ -24,7 +29,7 @@ class WorldRenderer(world: BlocksInWorld, blockSpecs: BlockSpecRegistry, initial private val chunkHandler: ChunkRenderHandler = new ChunkRenderHandler - private val lightPropagator = new LightPropagator(world) + private val lightPropagator = new LightPropagator(world, requestRenderUpdate) private val skyVao: VAO = SkyVao.create private val skyRenderer = @@ -145,8 +150,8 @@ class WorldRenderer(world: BlocksInWorld, blockSpecs: BlockSpecRegistry, initial def onWorldEvent(event: World.Event): Unit = event match - case World.Event.ChunkAdded(chunk) => - chunksToRender.add(chunk.coords) + case World.Event.ChunkAdded(coords) => + chunksToRender.add(coords) case World.Event.ChunkRemoved(coords) => chunksToRender.remove(coords) chunkHandler.clearChunkRenderData(coords)