From cc67f69fff25a62fcbebeac0605b70980a20cbaa Mon Sep 17 00:00:00 2001 From: Minjae Song Date: Fri, 14 Dec 2018 00:52:10 +0900 Subject: [PATCH] working very crude fluid sim --- .../torvald/terrarum/blockproperties/Block.kt | 4 +- .../terrarum/blockproperties/BlockCodex.kt | 2 +- .../torvald/terrarum/blockproperties/Fluid.kt | 5 + .../torvald/terrarum/gameworld/GameWorld.kt | 31 +++- .../modulebasegame/console/ExportMap.kt | 3 +- .../gameworld/WorldSimulator.kt | 169 +++++++++++------- .../Embarrassingly_parallel_for_dummies.txt | 24 +++ 7 files changed, 165 insertions(+), 73 deletions(-) create mode 100644 work_files/GameDesign/Embarrassingly_parallel_for_dummies.txt diff --git a/src/net/torvald/terrarum/blockproperties/Block.kt b/src/net/torvald/terrarum/blockproperties/Block.kt index e7f8820c5..aaa1f2a0a 100644 --- a/src/net/torvald/terrarum/blockproperties/Block.kt +++ b/src/net/torvald/terrarum/blockproperties/Block.kt @@ -115,7 +115,9 @@ object Block { val SUNSTONE = 257 val DAYLIGHT_CAPACITOR = 258 - val FLUID_MARKER = 4095 + + val LAVA = 4094 + val WATER = 4095 val NULL = -1 } diff --git a/src/net/torvald/terrarum/blockproperties/BlockCodex.kt b/src/net/torvald/terrarum/blockproperties/BlockCodex.kt index 7670dba08..5b4ee764d 100644 --- a/src/net/torvald/terrarum/blockproperties/BlockCodex.kt +++ b/src/net/torvald/terrarum/blockproperties/BlockCodex.kt @@ -108,7 +108,7 @@ object BlockCodex { prop.friction = intVal(record, "friction") prop.viscosity = intVal(record, "vscs") - prop.isFluid = boolVal(record, "fluid") + prop.isFluid = Fluid.isThisTileFluid(prop.id)//boolVal(record, "fluid") prop.isSolid = boolVal(record, "solid") prop.isClear = boolVal(record, "clear") prop.isWallable = boolVal(record, "wall") diff --git a/src/net/torvald/terrarum/blockproperties/Fluid.kt b/src/net/torvald/terrarum/blockproperties/Fluid.kt index aa918cf51..7009cb666 100644 --- a/src/net/torvald/terrarum/blockproperties/Fluid.kt +++ b/src/net/torvald/terrarum/blockproperties/Fluid.kt @@ -1,6 +1,7 @@ package net.torvald.terrarum.blockproperties import net.torvald.terrarum.gameworld.FluidType +import net.torvald.terrarum.gameworld.GameWorld /** * Created by minjaesong on 2016-08-06. @@ -12,4 +13,8 @@ object Fluid { val WATER = FluidType(1) val STATIC_WATER = FluidType(-1) + + fun getFluidTileFrom(type: FluidType) = GameWorld.TILES_SUPPORTED - type.abs() + private val fluidTilesRange = 4094..4095 + fun isThisTileFluid(tileid: Int) = tileid in fluidTilesRange } \ No newline at end of file diff --git a/src/net/torvald/terrarum/gameworld/GameWorld.kt b/src/net/torvald/terrarum/gameworld/GameWorld.kt index ba51ae32e..7785282b8 100644 --- a/src/net/torvald/terrarum/gameworld/GameWorld.kt +++ b/src/net/torvald/terrarum/gameworld/GameWorld.kt @@ -2,6 +2,7 @@ package net.torvald.terrarum.gameworld import com.badlogic.gdx.graphics.Color +import net.torvald.terrarum.AppLoader.printdbg import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.blockproperties.BlockCodex @@ -215,10 +216,20 @@ open class GameWorld { wallDamages.remove(LandUtil.getBlockAddr(this, x, y)) } + /** + * Warning: this function alters fluid lists: be wary of call order! + */ fun setTileTerrain(x: Int, y: Int, tile: Byte, damage: Int) { layerTerrain.setTile(x fmod width, y.coerceWorld(), tile) layerTerrainLowBits.setData(x fmod width, y.coerceWorld(), damage) - terrainDamages.remove(LandUtil.getBlockAddr(this, x, y)) + val blockAddr = LandUtil.getBlockAddr(this, x, y) + terrainDamages.remove(blockAddr) + + if (!BlockCodex[tile * PairedMapLayer.RANGE + damage].isFluid) { + fluidFills.remove(blockAddr) + fluidTypes.remove(blockAddr) + } + // fluid tiles-item should be modified so that they will also place fluid onto their respective map } fun setTileWire(x: Int, y: Int, tile: Byte) { @@ -343,6 +354,11 @@ open class GameWorld { wallDamages[LandUtil.getBlockAddr(this, x, y)] ?: 0f fun setFluid(x: Int, y: Int, fluidType: FluidType, fill: Float) { + /*if (x == 60 && y == 256) { + printdbg(this, "Setting fluid $fill at ($x,$y)") + }*/ + + val addr = LandUtil.getBlockAddr(this, x, y) // fluid completely drained if (fill <= WorldSimulator.FLUID_MIN_MASS) { @@ -357,10 +373,19 @@ open class GameWorld { } // update the fluid amount else { + //printdbg(this, "> Setting nonzero ($fill) on ($x,$y)") + + setTileTerrain(x, y, Block.WATER) // this function alters fluid list, must be called first // TODO fluidType aware fluidTypes[addr] = fluidType fluidFills[addr] = fill - setTileTerrain(x, y, Block.FLUID_MARKER) } + + + /*if (x == 60 && y == 256) { + printdbg(this, "TileTerrain: ${getTileFromTerrain(x, y)}") + printdbg(this, "fluidTypes[$addr] = ${fluidTypes[addr]} (should be ${fluidType.value})") + printdbg(this, "fluidFills[$addr] = ${fluidFills[addr]} (should be $fill)") + }*/ } fun getFluid(x: Int, y: Int): FluidInfo { @@ -390,6 +415,7 @@ open class GameWorld { @Transient val TERRAIN = 1 @Transient val WIRE = 2 + /** 4096 */ @Transient val TILES_SUPPORTED = MapLayer.RANGE * PairedMapLayer.RANGE @Transient val SIZEOF: Byte = MapLayer.SIZEOF @Transient val LAYERS: Byte = 4 // terrain, wall (layerTerrainLowBits + layerWallLowBits), wire @@ -403,4 +429,5 @@ infix fun Float.fmod(other: Float) = if (this >= 0f) this % other else (this % o inline class FluidType(val value: Int) { infix fun sameAs(other: FluidType) = this.value.absoluteValue == other.value.absoluteValue + fun abs() = this.value.absoluteValue } \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/console/ExportMap.kt b/src/net/torvald/terrarum/modulebasegame/console/ExportMap.kt index 6d9f9785f..03ff72150 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/ExportMap.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/ExportMap.kt @@ -7,7 +7,6 @@ import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.console.ConsoleCommand import net.torvald.terrarum.console.Echo import net.torvald.terrarum.console.EchoError -import net.torvald.terrarum.modulebasegame.Ingame import java.io.* import java.util.HashMap @@ -42,7 +41,7 @@ internal object ExportMap : ConsoleCommand { colorTable.put(Block.RAW_TOPAZ, Col4096(0xC70)) colorTable.put(Block.RAW_AMETHYST, Col4096(0x70C)) - colorTable.put(Block.FLUID_MARKER, Col4096(0x038)) + colorTable.put(Block.WATER, Col4096(0x038)) colorTable.put(Block.SAND, Col4096(0xDDB)) colorTable.put(Block.SAND_WHITE, Col4096(0xFFD)) diff --git a/src/net/torvald/terrarum/modulebasegame/gameworld/WorldSimulator.kt b/src/net/torvald/terrarum/modulebasegame/gameworld/WorldSimulator.kt index 7bbc71c17..0883d9a67 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameworld/WorldSimulator.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameworld/WorldSimulator.kt @@ -1,6 +1,7 @@ package net.torvald.terrarum.modulebasegame.gameworld import com.badlogic.gdx.graphics.Color +import net.torvald.terrarum.AppLoader.printdbg import net.torvald.terrarum.Terrarum import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.roundInt @@ -49,6 +50,8 @@ object WorldSimulator { private val world = (Terrarum.ingame!!.world) operator fun invoke(p: ActorHumanoid?, delta: Float) { + //printdbg(this, "============================") + if (p != null) { updateXFrom = p.hitbox.centeredX.div(FeaturesDrawer.TILE_SIZE).minus(FLUID_UPDATING_SQUARE_RADIUS).roundInt() updateYFrom = p.hitbox.centeredY.div(FeaturesDrawer.TILE_SIZE).minus(FLUID_UPDATING_SQUARE_RADIUS).roundInt() @@ -58,6 +61,8 @@ object WorldSimulator { moveFluids(delta) displaceFallables(delta) + + //printdbg(this, "============================") } /** @@ -71,7 +76,98 @@ object WorldSimulator { fun moveFluids(delta: Float) { makeFluidMapFromWorld() + //simCompression() + for (y in 1 until fluidMap.size - 1) { + for (x in 1 until fluidMap[0].size - 1) { + val worldX = x + updateXFrom + val worldY = y + updateYFrom + /*if (worldX == 60 && worldY == 256) { + printdbg(this, "tile: ${world.getTileFromTerrain(worldX, worldY)}, isSolid = ${isSolid(worldX, worldY)}") + }*/ + + if (isSolid(worldX, worldY)) continue + val remainingMass = fluidMap[y][x] + + /*if (worldX == 60 && worldY == 256) { + printdbg(this, "remainimgMass: $remainingMass at ($worldX, $worldY)") + }*/ + + if (!isSolid(worldX, worldY + 1)) { + fluidNewMap[y][x] -= remainingMass + fluidNewMap[y + 1][x] += remainingMass + } + } + } + + fluidmapToWorld() + } + + fun isFlowable(type: FluidType, worldX: Int, worldY: Int): Boolean { + val targetFluid = world.getFluid(worldX, worldY) + + // true if target's type is the same as mine, or it's NULL (air) + return (targetFluid.type sameAs type || targetFluid.type sameAs Fluid.NULL) + } + + fun isSolid(worldX: Int, worldY: Int): Boolean { + val tile = world.getTileFromTerrain(worldX, worldY) + if (tile != Block.WATER) { + // check for block properties isSolid + return BlockCodex[tile].isSolid + } + else { + // check for fluid + + // no STATIC is implement yet, just return false + return false + } + } + + /* + Explanation of get_stable_state_b (well, kind-of) : + + if x <= 1, all water goes to the lower cell + * a = 0 + * b = 1 + + if x > 1 & x < 2*MaxMass + MaxCompress, the lower cell should have MaxMass + (upper_cell/MaxMass) * MaxCompress + b = MaxMass + (a/MaxMass)*MaxCompress + a = x - b + + -> + + b = MaxMass + ((x - b)/MaxMass)*MaxCompress -> + b = MaxMass + (x*MaxCompress - b*MaxCompress)/MaxMass + b*MaxMass = MaxMass^2 + (x*MaxCompress - b*MaxCompress) + b*(MaxMass + MaxCompress) = MaxMass*MaxMass + x*MaxCompress + + * b = (MaxMass*MaxMass + x*MaxCompress)/(MaxMass + MaxCompress) + * a = x - b; + + if x >= 2 * MaxMass + MaxCompress, the lower cell should have upper+MaxCompress + + b = a + MaxCompress + a = x - b + + -> + + b = x - b + MaxCompress -> + 2b = x + MaxCompress -> + + * b = (x + MaxCompress)/2 + * a = x - b + */ + private fun getStableStateB(totalMass: Float): Float { + if (totalMass <= 1) + return 1f + else if (totalMass < 2f * FLUID_MAX_MASS + FLUID_MAX_COMP) + return (FLUID_MAX_MASS * FLUID_MAX_MASS + totalMass * FLUID_MAX_COMP) / (FLUID_MAX_MASS + FLUID_MAX_COMP) + else + return (totalMass + FLUID_MAX_COMP) / 2f + } + + private fun simCompression() { // before data: fluidMap/fluidTypeMap // after data: fluidNewMap/fluidNewTypeMap var flow = 0f @@ -156,73 +252,6 @@ object WorldSimulator { } } - - - fluidmapToWorld() - } - - fun isFlowable(type: FluidType, worldX: Int, worldY: Int): Boolean { - val targetFluid = world.getFluid(worldX, worldY) - - // true if target's type is the same as mine, or it's NULL (air) - return (targetFluid.type sameAs type || targetFluid.type sameAs Fluid.NULL) - } - - fun isSolid(worldX: Int, worldY: Int): Boolean { - val tile = world.getTileFromTerrain(worldX, worldY) - if (tile != Block.FLUID_MARKER) { - // check for block properties isSolid - return BlockCodex[tile].isSolid - } - else { - // check for fluid - - // no STATIC is implement yet, just return true - return true - } - } - - /* - Explanation of get_stable_state_b (well, kind-of) : - - if x <= 1, all water goes to the lower cell - * a = 0 - * b = 1 - - if x > 1 & x < 2*MaxMass + MaxCompress, the lower cell should have MaxMass + (upper_cell/MaxMass) * MaxCompress - b = MaxMass + (a/MaxMass)*MaxCompress - a = x - b - - -> - - b = MaxMass + ((x - b)/MaxMass)*MaxCompress -> - b = MaxMass + (x*MaxCompress - b*MaxCompress)/MaxMass - b*MaxMass = MaxMass^2 + (x*MaxCompress - b*MaxCompress) - b*(MaxMass + MaxCompress) = MaxMass*MaxMass + x*MaxCompress - - * b = (MaxMass*MaxMass + x*MaxCompress)/(MaxMass + MaxCompress) - * a = x - b; - - if x >= 2 * MaxMass + MaxCompress, the lower cell should have upper+MaxCompress - - b = a + MaxCompress - a = x - b - - -> - - b = x - b + MaxCompress -> - 2b = x + MaxCompress -> - - * b = (x + MaxCompress)/2 - * a = x - b - */ - private fun getStableStateB(totalMass: Float): Float { - if (totalMass <= 1) - return 1f - else if (totalMass < 2f * FLUID_MAX_MASS + FLUID_MAX_COMP) - return (FLUID_MAX_MASS * FLUID_MAX_MASS + totalMass * FLUID_MAX_COMP) / (FLUID_MAX_MASS + FLUID_MAX_COMP) - else - return (totalMass + FLUID_MAX_COMP) / 2f } /** @@ -260,6 +289,8 @@ object WorldSimulator { } private fun makeFluidMapFromWorld() { + //printdbg(this, "Scan area: ($updateXFrom,$updateYFrom)..(${updateXFrom + fluidMap[0].size},${updateYFrom + fluidMap.size})") + for (y in 0 until fluidMap.size) { for (x in 0 until fluidMap[0].size) { val fluidData = world.getFluid(x + updateXFrom, y + updateYFrom) @@ -267,6 +298,10 @@ object WorldSimulator { fluidTypeMap[y][x] = fluidData.type fluidNewMap[y][x] = fluidData.amount fluidNewTypeMap[y][x] = fluidData.type + + if (x + updateXFrom == 60 && y + updateYFrom == 256) { + printdbg(this, "making array amount ${fluidData.amount} for (60,256)") + } } } } diff --git a/work_files/GameDesign/Embarrassingly_parallel_for_dummies.txt b/work_files/GameDesign/Embarrassingly_parallel_for_dummies.txt new file mode 100644 index 000000000..adf24c2ba --- /dev/null +++ b/work_files/GameDesign/Embarrassingly_parallel_for_dummies.txt @@ -0,0 +1,24 @@ +Let's say you want to do some operation over a 2D array: + +10 04 06 14 +08 00 02 08 +02 16 04 12 +06 02 12 14 + +Our task: get the average of px[y][x] and px[y+1][x] + +Single-threaded + +09 02 04 11 +05 08 03 10 +04 09 08 13 +06 02 12 14 (Observation: can work with "scrachpad", but also inline mutable) + +4 threads + +09 02 04 11 .. .. .. .. .. .. .. .. .. .. .. .. These are single scrachpad but +.. .. .. .. 05 08 03 10 .. .. .. .. .. .. .. .. threads are writing to un-shared +.. .. .. .. .. .. .. .. 04 09 08 13 .. .. .. .. portion of the array +.. .. .. .. .. .. .. .. .. .. .. .. 06 02 12 14 + +(Observation: "scrachpad" is a must, making original array immutable, this multithreading works)