fluids to their own block layer

This commit is contained in:
minjaesong
2023-10-10 05:33:40 +09:00
parent caffdbf861
commit 26936fde09
12 changed files with 199 additions and 54 deletions

BIN
assets/mods/basegame/ores/1.tga LFS Normal file

Binary file not shown.

View File

@@ -1,5 +1,7 @@
package net.torvald.terrarum.blockproperties
import net.torvald.terrarum.gameitems.ItemID
/**
* Created by minjaesong on 2016-08-06.

View File

@@ -69,10 +69,7 @@ open class BlockLayerI16(val width: Int, val height: Int) : BlockLayer {
override fun unsafeToBytes(x: Int, y: Int): ByteArray {
val offset = BYTES_PER_BLOCK * (y * width + x)
val lsb = ptr[offset]
val msb = ptr[offset + 1]
return byteArrayOf(msb, lsb)
return byteArrayOf(ptr[offset + 1], ptr[offset + 0])
}
internal fun unsafeSetTile(x: Int, y: Int, tile: Int) {

View File

@@ -0,0 +1,128 @@
package net.torvald.terrarum.gameworld
import net.torvald.terrarum.App
import net.torvald.terrarum.blockproperties.Fluid
import net.torvald.terrarum.gameworld.WorldSimulator.FLUID_MIN_MASS
import net.torvald.terrarum.serialise.toUint
import net.torvald.unsafe.UnsafeHelper
import net.torvald.unsafe.UnsafePtr
import net.torvald.util.Float16
/**
* * Memory layout:
* * ```
* * a7 a6 a5 a4 a3 a2 a1 a0 | aF aE aD aC aB aA a9 a8 | f7 f6 f5 f4 f3 f2 f1 f0 | fF fE fD fC fB fA f9 f8 ||
* * ```
* * where a_n is a fluid number, f_n is a fluid fill
*
* Created by minjaesong on 2023-10-10.
*/
class BlockLayerI16F16(val width: Int, val height: Int) : BlockLayer {
override val bytesPerBlock = BYTES_PER_BLOCK
// for some reason, all the efforts of saving the memory space were futile.
// using unsafe pointer gets you 100 fps, whereas using directbytebuffer gets you 90
internal val ptr: UnsafePtr = UnsafeHelper.allocate(width * height * BYTES_PER_BLOCK)
val ptrDestroyed: Boolean
get() = ptr.destroyed
init {
ptr.fillWith(0)
}
/**
* @param data Byte array representation of the layer
*/
constructor(width: Int, height: Int, data: ByteArray) : this(width, height) {
TODO()
data.forEachIndexed { index, byte -> UnsafeHelper.unsafe.putByte(ptr.ptr + index, byte) }
}
/**
* Returns an iterator over stored bytes.
*
* @return an Iterator.
*/
fun bytesIterator(): Iterator<Byte> {
return object : Iterator<Byte> {
private var iteratorCount = 0L
override fun hasNext(): Boolean {
return iteratorCount < width * height * BYTES_PER_BLOCK
}
override fun next(): Byte {
iteratorCount += 1
return ptr[iteratorCount - 1]
}
}
}
internal fun unsafeGetTile(x: Int, y: Int): Pair<Int, Float> {
val offset = BYTES_PER_BLOCK * (y * width + x)
val lsb = ptr[offset]
val msb = ptr[offset + 1]
val hbits = (ptr[offset + 2].toUint() or ptr[offset + 3].toUint().shl(8)).toShort()
val fill = Float16.toFloat(hbits)
return lsb.toUint() + msb.toUint().shl(8) to fill
}
override fun unsafeToBytes(x: Int, y: Int): ByteArray {
val offset = BYTES_PER_BLOCK * (y * width + x)
return byteArrayOf(ptr[offset + 1], ptr[offset + 0], ptr[offset + 3], ptr[offset + 2])
}
internal fun unsafeSetTile(x: Int, y: Int, tile0: Int, fill: Float) {
val offset = BYTES_PER_BLOCK * (y * width + x)
val hbits = Float16.fromFloat(fill).toInt().and(0xFFFF)
val tile = if (fill < FLUID_MIN_MASS) 0 else tile0
val lsb = tile.and(0xff).toByte()
val msb = tile.ushr(8).and(0xff).toByte()
val hlsb = hbits.and(0xff).toByte()
val hmsb = hbits.ushr(8).and(0xff).toByte()
ptr[offset] = lsb
ptr[offset + 1] = msb
ptr[offset + 2] = hlsb
ptr[offset + 3] = hmsb
}
override fun unsafeSetTile(x: Int, y: Int, bytes: ByteArray) {
val offset = BYTES_PER_BLOCK * (y * width + x)
ptr[offset] = bytes[1]
ptr[offset + 1] = bytes[0]
ptr[offset + 2] = bytes[3]
ptr[offset + 3] = bytes[2]
}
/**
* @param blockOffset Offset in blocks. BlockOffset of 0x100 is equal to ```layerPtr + 0x200```
*/
/*internal fun unsafeSetTile(blockOffset: Long, tile: Int) {
val offset = BYTES_PER_BLOCK * blockOffset
val lsb = tile.and(0xff).toByte()
val msb = tile.ushr(8).and(0xff).toByte()
unsafe.putByte(layerPtr + offset, lsb)
unsafe.putByte(layerPtr + offset + 1, msb)
}*/
fun isInBound(x: Int, y: Int) = (x >= 0 && y >= 0 && x < width && y < height)
override fun dispose() {
ptr.destroy()
App.printdbg(this, "BlockLayerI16F16 with ptr ($ptr) successfully freed")
}
override fun toString(): String = ptr.toString("BlockLayerI16F16")
companion object {
@Transient val BYTES_PER_BLOCK = 4L
}
}

View File

@@ -66,11 +66,7 @@ class BlockLayerI16I8 (val width: Int, val height: Int) : BlockLayer {
override fun unsafeToBytes(x: Int, y: Int): ByteArray {
val offset = BYTES_PER_BLOCK * (y * width + x)
val lsb = ptr[offset]
val msb = ptr[offset + 1]
val placement = ptr[offset + 2]
return byteArrayOf(msb, lsb, placement)
return byteArrayOf(ptr[offset + 1], ptr[offset + 0], ptr[offset + 2])
}
internal fun unsafeSetTile(x: Int, y: Int, tile: Int, placement: Int) {

View File

@@ -84,9 +84,9 @@ open class GameWorld(
@Transient lateinit open var layerWall: BlockLayerI16
@Transient lateinit open var layerTerrain: BlockLayerI16
@Transient lateinit open var layerOres: BlockLayerI16I8 // damage to the block follows `terrainDamages`
@Transient lateinit open var layerFluids: BlockLayerI16F16
val wallDamages = HashArray<Float>()
val terrainDamages = HashArray<Float>()
val layerFluids = HashedFluidTypeAndFills() // TODO: chunk them using BlockLayerI32
@@ -142,10 +142,27 @@ open class GameWorld(
30L * WorldTime.MINUTE_SEC
)
val tileNumberToNameMap = HashArray<ItemID>()
@Transient private val forcedTileNumberToNames = hashSetOf(
Block.AIR, Block.UPDATE
)
@Transient private val forcedFluidNumberToTiles = hashSetOf(
Fluid.NULL
)
val tileNumberToNameMap = HashArray<ItemID>().also {
it[0] = Block.AIR
it[2] = Block.UPDATE
}
val fluidNumberToNameMap = HashArray<ItemID>().also {
it[0] = Fluid.NULL
}
// does not go to the savefile
@Transient val tileNameToNumberMap = HashMap<ItemID, Int>()
@Transient val tileNameToNumberMap = HashMap<ItemID, Int>().also {
it[Block.AIR] = 0
it[Block.UPDATE] = 2
}
@Transient val fluidNameToNumberMap = HashMap<ItemID, Int>().also {
it[Fluid.NULL] = 0
}
val extraFields = HashMap<String, Any?>()
@@ -202,6 +219,7 @@ open class GameWorld(
layerTerrain = BlockLayerI16(width, height)
layerWall = BlockLayerI16(width, height)
layerOres = BlockLayerI16I8(width, height)
layerFluids = BlockLayerI16F16(width, height)
// temperature layer: 2x2 is one cell
//layerThermal = MapLayerHalfFloat(width, height, averageTemperature)
@@ -223,20 +241,9 @@ open class GameWorld(
tileNameToNumberMap[it.key] = it.value.tileNumber
}
}
// AN EXCEPTIONAL TERM: tilenum 0 is always redirected to Air tile, even if the tilenum for actual Air tile is not zero
tileNumberToNameMap[0] = Block.AIR
tileNameToNumberMap[Block.AIR] = 0
tileNumberToNameMap[2] = Block.UPDATE
tileNameToNumberMap[Block.UPDATE] = 2
}
}
@Transient private val forcedTileNumberToNames = hashSetOf(
Block.AIR, Block.UPDATE
)
fun coordInWorld(x: Int, y: Int) = y in 0 until height // ROUNDWORLD implementation
fun coordInWorldStrict(x: Int, y: Int) = x in 0 until width && y in 0 until height // ROUNDWORLD implementation
@@ -258,12 +265,13 @@ open class GameWorld(
}
}
// AN EXCEPTIONAL TERM: tilenum 0 is always redirected to Air tile, even if the tilenum for actual Air tile is not zero
// force this rule to the old saves
tileNumberToNameMap[0] = Block.AIR
tileNameToNumberMap[Block.AIR] = 0
tileNumberToNameMap[2] = Block.UPDATE
tileNameToNumberMap[Block.AIR] = 0
tileNameToNumberMap[Block.UPDATE] = 2
fluidNumberToNameMap[0] = Fluid.NULL
fluidNameToNumberMap[Fluid.NULL] = 0
}
/**
@@ -274,9 +282,9 @@ open class GameWorld(
// get() = layerWire.data
fun getLayer(index: Int) = when(index) {
0 -> layerTerrain
1 -> layerWall
2 -> layerOres
TERRAIN -> layerTerrain
WALL -> layerWall
ORES -> layerOres
else -> null//throw IllegalArgumentException("Unknown layer index: $index")
}
@@ -361,7 +369,7 @@ open class GameWorld(
terrainDamages.remove(blockAddr)
if (BlockCodex[itemID].isSolid) {
layerFluids.remove(blockAddr)
layerFluids.unsafeSetTile(x, y, fluidNameToNumberMap[Fluid.NULL]!!, 0f)
}
// fluid tiles-item should be modified so that they will also place fluid onto their respective map
@@ -546,17 +554,14 @@ open class GameWorld(
return getTileFromWall(x, y)
}
else
throw IllegalArgumentException("illegal mode input: " + mode.toString())
throw IllegalArgumentException("illegal mode input: $mode")
}
fun getTileFromOre(rawX: Int, rawY: Int): OrePlacement? {
fun getTileFromOre(rawX: Int, rawY: Int): OrePlacement {
val (x, y) = coerceXY(rawX, rawY)
val (tileNum, placement) = layerOres.unsafeGetTile(x, y)
val tileName = tileNumberToNameMap[tileNum.toLong()]
if (tileName == Block.AIR)
return null
else
return OrePlacement(tileName ?: Block.UPDATE, placement)
return OrePlacement(tileName ?: Block.UPDATE, placement)
}
fun setTileOre(rawX: Int, rawY: Int, ore: ItemID, placement: Int) {
@@ -684,12 +689,14 @@ open class GameWorld(
val addr = LandUtil.getBlockAddr(this, x, y)
val fluidNumber = fluidNameToNumberMap[fluidType]!!
if (fill > WorldSimulator.FLUID_MIN_MASS) {
//setTileTerrain(x, y, fluidTypeToBlock(fluidType))
layerFluids[addr] = Fill(fluidType, fill)
layerFluids.unsafeSetTile(x, y, fluidNumber, fill)
}
else {
layerFluids.remove(addr)
layerFluids.unsafeSetTile(x, y, fluidNumber, 0f)
}
@@ -701,10 +708,11 @@ open class GameWorld(
}
fun getFluid(x: Int, y: Int): FluidInfo {
val addr = LandUtil.getBlockAddr(this, x, y)
val type = layerFluids[addr]?.item
val fill = layerFluids[addr]?.amount
return if (type == null) FluidInfo(Fluid.NULL, 0f) else FluidInfo(type, fill!!)
val (x, y) = coerceXY(x, y)
val (type, fill) = layerFluids.unsafeGetTile(x, y)
val fluidID = fluidNumberToNameMap[type.toLong()] ?: throw NullPointerException("No such fluid: $type")
return FluidInfo(fluidID, fill)
}
/*private fun fluidTypeToBlock(type: FluidType) = when (type.abs()) {
@@ -764,9 +772,9 @@ open class GameWorld(
override fun equals(other: Any?) = layerTerrain.ptr == (other as GameWorld).layerTerrain.ptr
companion object {
@Transient const val WALL = 0
@Transient const val TERRAIN = 1
@Transient const val WIRE = 2
@Transient const val WALL = 1
@Transient const val TERRAIN = 0
@Transient const val ORES = 2
@Transient val TILES_SUPPORTED = ReferencingRanges.TILES.last + 1
//@Transient val SIZEOF: Byte = 2

View File

@@ -42,7 +42,7 @@ object WorldSimulator {
const val FLUID_MAX_MASS = 1f // The normal, un-pressurized mass of a full water cell
const val FLUID_MAX_COMP = 0.02f // How much excess water a cell can store, compared to the cell above it. A tile of fluid can contain more than MaxMass water.
const val FLUID_MIN_MASS = 0.0001f //Ignore cells that are almost dry
const val FLUID_MIN_MASS = 1f / 1024f //Ignore cells that are almost dry (smaller than epsilon of float16)
const val WIRE_MIN_FLOW = 0.0001f
const val minFlow = 0.01f
const val maxSpeed = 1f // max units of water moved out of one block to another, per timestamp

View File

@@ -5,6 +5,7 @@ import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.console.Echo
import net.torvald.terrarum.gameworld.BlockLayerI16
import net.torvald.terrarum.gameworld.BlockLayerI16F16
import net.torvald.terrarum.gameworld.BlockLayerI16I8
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.langpack.Lang
@@ -147,6 +148,7 @@ object LoadSavegame {
world.layerTerrain = BlockLayerI16(world.width, world.height)
world.layerWall = BlockLayerI16(world.width, world.height)
world.layerOres = BlockLayerI16I8(world.width, world.height)
world.layerFluids = BlockLayerI16F16(world.width, world.height)
newIngame.world = world // must be set before the loadscreen, otherwise the loadscreen will try to read from the NullWorld which is already destroyed
newIngame.worldDisk = VDUtil.readDiskArchive(worldDisk.diskFile, Level.INFO)

View File

@@ -84,6 +84,12 @@ class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, par
world.setTileTerrain(x, y, terrBlock, true)
world.setTileWall(x, y, wallBlock, true)
// TODO TEST CODE
if (terrBlock == Block.DIRT) {
world.setTileOre(x, y, "ores@basegame:1", 0)
}
}
// dither shits
@@ -111,12 +117,6 @@ class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, par
world.setTileTerrain(x, y, newTile, true)
world.setTileWall(x, y, newTile, true)
// TODO TEST CODE
if (newTile == Block.DIRT) {
world.setTileOre(x, y, "ores@basegame:1", 0)
}
}
/*

View File

@@ -4,6 +4,7 @@ import net.torvald.terrarum.App
import net.torvald.terrarum.IngameInstance
import net.torvald.terrarum.ItemCodex
import net.torvald.terrarum.gameactors.Actor
import net.torvald.terrarum.gameworld.BlockLayerI16F16
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.SimpleGameWorld
import java.io.File
@@ -23,6 +24,7 @@ object ReadSimpleWorld {
world.tileNumberToNameMap.forEach { l, s ->
world.tileNameToNumberMap[s] = l.toInt()
}
world.layerFluids = BlockLayerI16F16(world.width, world.height)
ItemCodex.loadFromSave(origin, world.dynamicToStaticTable, world.dynamicItemInventory)
}

View File

@@ -247,13 +247,15 @@ class BasicDebugInfoWindow : UICanvas() {
val wallNum = it.getTileFromWall(mouseTileX, mouseTileY)
val tileNum = it.getTileFromTerrain(mouseTileX, mouseTileY)
val oreNum = it.getTileFromOre(mouseTileX, mouseTileY).item
val wires = it.getAllWiresFrom(mouseTileX, mouseTileY)
val fluid = it.getFluid(mouseTileX, mouseTileY)
val wireCount = wires.first?.size?.toString() ?: "no"
App.fontSmallNumbers.draw(batch, "$ccO$TERRAIN$ccG$tileNum", gap + 7f*(tileCursX + 3), line(tileCursY))
App.fontSmallNumbers.draw(batch, "$ccO$WALL$ccG$wallNum", gap + 7f*(tileCursX + 3), line(tileCursY + 1))
App.fontSmallNumbers.draw(batch, "$ccO$LIQUID$ccG${fluid.type.padEnd(3)}$ccO$BEAKER$ccG${fluid.amount.toIntAndFrac(2)}", gap + 7f*(tileCursX + 3), line(tileCursY + 2))
// App.fontSmallNumbers.draw(batch, "$ccO$LIQUID$ccG${fluid.type.padEnd(3)}$ccO$BEAKER$ccG${fluid.amount.toIntAndFrac(2)}", gap + 7f*(tileCursX + 3), line(tileCursY + 2))
App.fontSmallNumbers.draw(batch, "$ccO$LIQUID$ccG$oreNum", gap + 7f*(tileCursX + 3), line(tileCursY + 2))
App.fontSmallNumbers.draw(batch, "$ccO$WIRE$ccG$wireCount ${ccY}X$ccO$mouseTileX ${ccY}Y$ccO$mouseTileY", gap + 7f*(tileCursX + 3), line(tileCursY + 3))
App.fontSmallNumbers.draw(batch, "$ccR$rawR $ccG$rawG $ccB$rawB $ccW$rawA", gap + 7f*(tileCursX + 3), line(tileCursY + 4))

View File

@@ -62,6 +62,7 @@ internal object BlocksDrawer {
val WALL = GameWorld.WALL
val TERRAIN = GameWorld.TERRAIN
val ORES = GameWorld.ORES
//val WIRE = GameWorld.WIRE
val FLUID = -2
val OCCLUSION = 31337
@@ -295,6 +296,7 @@ internal object BlocksDrawer {
val thisTile: ItemID = when (mode) {
WALL -> world.getTileFromWall(x, y)
TERRAIN -> world.getTileFromTerrain(x, y)
ORES -> world.getTileFromOre(x, y).item
FLUID -> "basegame:-1" // TODO need new wire storing format //world.getFluid(x, y).type.abs()
OCCLUSION -> "placeholder_occlusion"
else -> throw IllegalArgumentException()
@@ -338,6 +340,9 @@ internal object BlocksDrawer {
// special case: fluids
else if (mode == FLUID)
tileNumberBase + connectLut47[nearbyTilesInfo]
// special case: ores
else if (mode == ORES)
tileNumberBase + world.getTileFromOre(x, y).tilePlacement
// rest of the cases: terrain and walls
else tileNumberBase + when (renderTag.maskType) {
CreateTileAtlas.RenderTag.MASK_NA -> 0