working very crude fluid sim

This commit is contained in:
Minjae Song
2018-12-14 00:52:10 +09:00
parent 05a8f47006
commit 16e4067d89
7 changed files with 165 additions and 73 deletions

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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))

View File

@@ -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)")
}
}
}
}

View File

@@ -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)