implementing water sim but not actually working

This commit is contained in:
Minjae Song
2018-12-13 04:45:09 +09:00
parent 1f1d6f1eda
commit 05a8f47006
6 changed files with 230 additions and 51 deletions

View File

@@ -33,6 +33,7 @@ import java.io.File
import java.io.IOException
import net.torvald.getcpuname.GetCpuName
import net.torvald.terrarum.modulebasegame.Ingame
import kotlin.math.absoluteValue
@@ -908,7 +909,7 @@ inline fun Double.abs() = Math.abs(this)
inline fun Double.sqr() = this * this
inline fun Double.sqrt() = Math.sqrt(this)
inline fun Float.sqrt() = FastMath.sqrt(this)
inline fun Int.abs() = if (this < 0) -this else this
inline fun Int.abs() = this.absoluteValue
fun Double.bipolarClamp(limit: Double) =
this.coerceIn(-limit, limit)

View File

@@ -1,13 +1,15 @@
package net.torvald.terrarum.blockproperties
import net.torvald.terrarum.gameworld.FluidType
/**
* Created by minjaesong on 2016-08-06.
*/
object Fluid {
val NULL = 0
val NULL = FluidType(0)
val WATER = 1
val STATIC_WATER = -1
val WATER = FluidType(1)
val STATIC_WATER = FluidType(-1)
}

View File

@@ -5,9 +5,11 @@ import com.badlogic.gdx.graphics.Color
import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.blockproperties.BlockCodex
import net.torvald.terrarum.blockproperties.Fluid
import net.torvald.terrarum.modulebasegame.gameworld.WorldSimulator
import net.torvald.terrarum.serialise.ReadLayerDataLzma
import org.dyn4j.geometry.Vector2
import kotlin.math.absoluteValue
typealias BlockAddress = Long
@@ -45,7 +47,7 @@ open class GameWorld {
val wallDamages: HashMap<BlockAddress, Float>
val terrainDamages: HashMap<BlockAddress, Float>
val fluidTypes: HashMap<BlockAddress, Int>
val fluidTypes: HashMap<BlockAddress, FluidType>
val fluidFills: HashMap<BlockAddress, Float>
//public World physWorld = new World( new Vec2(0, -Terrarum.game.gravitationalAccel) );
@@ -79,7 +81,7 @@ open class GameWorld {
wallDamages = HashMap<BlockAddress, Float>()
terrainDamages = HashMap<BlockAddress, Float>()
fluidTypes = HashMap<BlockAddress, Int>()
fluidTypes = HashMap<BlockAddress, FluidType>()
fluidFills = HashMap<BlockAddress, Float>()
// temperature layer: 2x2 is one cell
@@ -154,8 +156,8 @@ open class GameWorld {
get() = layerTerrainLowBits.data
fun getTileFromWall(x: Int, y: Int): Int? {
val wall: Int? = layerWall.getTile(x fmod width, y)
val wallDamage: Int? = getWallLowBits(x fmod width, y)
val wall: Int? = layerWall.getTile(x fmod width, y.coerceWorld().coerceWorld())
val wallDamage: Int? = getWallLowBits(x fmod width, y.coerceWorld())
return if (wall == null || wallDamage == null)
null
else
@@ -163,8 +165,8 @@ open class GameWorld {
}
fun getTileFromTerrain(x: Int, y: Int): Int? {
val terrain: Int? = layerTerrain.getTile(x fmod width, y)
val terrainDamage: Int? = getTerrainLowBits(x fmod width, y)
val terrain: Int? = layerTerrain.getTile(x fmod width, y.coerceWorld())
val terrainDamage: Int? = getTerrainLowBits(x fmod width, y.coerceWorld())
return if (terrain == null || terrainDamage == null)
null
else
@@ -172,15 +174,15 @@ open class GameWorld {
}
fun getTileFromWire(x: Int, y: Int): Int? {
return layerWire.getTile(x fmod width, y)
return layerWire.getTile(x fmod width, y.coerceWorld())
}
fun getWallLowBits(x: Int, y: Int): Int? {
return layerWallLowBits.getData(x fmod width, y)
return layerWallLowBits.getData(x fmod width, y.coerceWorld())
}
fun getTerrainLowBits(x: Int, y: Int): Int? {
return layerTerrainLowBits.getData(x fmod width, y)
return layerTerrainLowBits.getData(x fmod width, y.coerceWorld())
}
/**
@@ -192,7 +194,7 @@ open class GameWorld {
* @param combinedTilenum (tilenum * 16) + damage
*/
fun setTileWall(x: Int, y: Int, combinedTilenum: Int) {
setTileWall(x fmod width, y, (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
setTileWall(x fmod width, y.coerceWorld(), (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
}
/**
@@ -204,23 +206,23 @@ open class GameWorld {
* @param combinedTilenum (tilenum * 16) + damage
*/
fun setTileTerrain(x: Int, y: Int, combinedTilenum: Int) {
setTileTerrain(x fmod width, y, (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
setTileTerrain(x fmod width, y.coerceWorld(), (combinedTilenum / PairedMapLayer.RANGE).toByte(), combinedTilenum % PairedMapLayer.RANGE)
}
fun setTileWall(x: Int, y: Int, tile: Byte, damage: Int) {
layerWall.setTile(x fmod width, y, tile)
layerWallLowBits.setData(x fmod width, y, damage)
layerWall.setTile(x fmod width, y.coerceWorld(), tile)
layerWallLowBits.setData(x fmod width, y.coerceWorld(), damage)
wallDamages.remove(LandUtil.getBlockAddr(this, x, y))
}
fun setTileTerrain(x: Int, y: Int, tile: Byte, damage: Int) {
layerTerrain.setTile(x fmod width, y, tile)
layerTerrainLowBits.setData(x fmod width, y, damage)
layerTerrain.setTile(x fmod width, y.coerceWorld(), tile)
layerTerrainLowBits.setData(x fmod width, y.coerceWorld(), damage)
terrainDamages.remove(LandUtil.getBlockAddr(this, x, y))
}
fun setTileWire(x: Int, y: Int, tile: Byte) {
layerWire.setTile(x fmod width, y, tile)
layerWire.setTile(x fmod width, y.coerceWorld(), tile)
}
fun getTileFrom(mode: Int, x: Int, y: Int): Int? {
@@ -340,13 +342,20 @@ open class GameWorld {
fun getWallDamage(x: Int, y: Int): Float =
wallDamages[LandUtil.getBlockAddr(this, x, y)] ?: 0f
fun setFluid(x: Int, y: Int, fluidType: Int, fill: Float) {
fun setFluid(x: Int, y: Int, fluidType: FluidType, fill: Float) {
val addr = LandUtil.getBlockAddr(this, x, y)
// fluid completely drained
if (fill <= WorldSimulator.FLUID_MIN_MASS) {
fluidTypes.remove(addr)
fluidFills.remove(addr)
setTileTerrain(x, y, 0)
/**********/ fluidTypes.remove(addr)
val oldMap = fluidFills.remove(addr)
// oldMap not being null means there actually was a fluid there, so we can put AIR onto it
// otherwise, it means it was some solid and therefore we DON'T want to put AIR onto it
if (oldMap != null) {
setTileTerrain(x, y, 0)
}
}
// update the fluid amount
else {
fluidTypes[addr] = fluidType
fluidFills[addr] = fill
@@ -354,27 +363,28 @@ open class GameWorld {
}
}
fun getFluid(x: Int, y: Int): Pair<Int, Float>? {
fun getFluid(x: Int, y: Int): FluidInfo {
val addr = LandUtil.getBlockAddr(this, x, y)
val fill = fluidFills[addr]
val type = fluidTypes[addr]
return if (type == null) null else Pair(type!!, fill!!)
return if (type == null) FluidInfo(Fluid.NULL, 0f) else FluidInfo(type, fill!!)
}
data class FluidInfo(val type: FluidType, val amount: Float)
fun getTemperature(worldTileX: Int, worldTileY: Int): Float? {
return null
//return layerThermal.getValue((worldTileX fmod width) / 2, worldTileY / 2)
}
fun getAirPressure(worldTileX: Int, worldTileY: Int): Float? {
return null
//return layerFluidPressure.getValue((worldTileX fmod width) / 4, worldTileY / 8)
}
private fun Int.coerceWorld() = this.coerceIn(0, height - 1)
companion object {
@Transient val WALL = 0
@Transient val TERRAIN = 1
@@ -390,3 +400,7 @@ open class GameWorld {
infix fun Int.fmod(other: Int) = Math.floorMod(this, other)
infix fun Float.fmod(other: Float) = if (this >= 0f) this % other else (this % other) + other
inline class FluidType(val value: Int) {
infix fun sameAs(other: FluidType) = this.value.absoluteValue == other.value.absoluteValue
}

View File

@@ -598,7 +598,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
}
actorNowPlaying = newActor
WorldSimulator(actorNowPlaying, Terrarum.deltaTime)
//WorldSimulator(actorNowPlaying, Terrarum.deltaTime)
}
private fun changePossession(refid: Int) {

View File

@@ -1,17 +1,13 @@
package net.torvald.terrarum.modulebasegame.gameworld
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.roundInt
import net.torvald.terrarum.worlddrawer.BlocksDrawer
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.roundInt
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
import net.torvald.terrarum.blockproperties.BlockCodex
import net.torvald.terrarum.blockproperties.Fluid
import net.torvald.terrarum.gameworld.FluidCodex
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.Ingame
import net.torvald.terrarum.gameworld.FluidType
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
/**
@@ -25,15 +21,20 @@ object WorldSimulator {
* In tiles;
* square width/height = field * 2
*/
const val FLUID_UPDATING_SQUARE_RADIUS = 64 // larger value will have dramatic impact on performance
const val FLUID_UPDATING_SQUARE_RADIUS = 80 // larger value will have dramatic impact on performance
const private val DOUBLE_RADIUS = FLUID_UPDATING_SQUARE_RADIUS * 2
// maps are separated as old-new for obvious reason, also it'll allow concurrent modification
private val fluidMap = Array(DOUBLE_RADIUS, { FloatArray(DOUBLE_RADIUS) })
private val fluidTypeMap = Array(DOUBLE_RADIUS, { IntArray(DOUBLE_RADIUS) })
private val fluidTypeMap = Array(DOUBLE_RADIUS, { Array<FluidType>(DOUBLE_RADIUS) { Fluid.NULL } })
private val fluidNewMap = Array(DOUBLE_RADIUS, { FloatArray(DOUBLE_RADIUS) })
private val fluidNewTypeMap = Array(DOUBLE_RADIUS, { Array<FluidType>(DOUBLE_RADIUS) { Fluid.NULL } })
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
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 minFlow = 0.01f
const val maxSpeed = 1f // max units of water moved out of one block to another, per timestamp
// END OF FLUID-RELATED STUFFS
@@ -68,11 +69,160 @@ object WorldSimulator {
* TODO multithread
*/
fun moveFluids(delta: Float) {
////////////////////
// build fluidmap //
////////////////////
purgeFluidMap()
makeFluidMapFromWorld()
// before data: fluidMap/fluidTypeMap
// after data: fluidNewMap/fluidNewTypeMap
var flow = 0f
var remainingMass = 0f
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
// check solidity
if (isSolid(worldX, worldY)) continue
// check if the fluid is a same kind
//if (!isFlowable(type, worldX, worldY))) continue
// Custom push-only flow
flow = 0f
remainingMass = fluidMap[y][x]
if (remainingMass <= 0) continue
// The block below this one
if (!isSolid(worldX, worldY + 1)) { // TODO use isFlowable
flow = getStableStateB(remainingMass + fluidMap[y + 1][x]) - fluidMap[y + 1][x]
if (flow > minFlow) {
flow *= 0.5f // leads to smoother flow
}
flow.coerceIn(0f, minOf(maxSpeed, remainingMass))
fluidNewMap[y][x] -= flow
fluidNewMap[y + 1][x] += flow
remainingMass -= flow
}
if (remainingMass <= 0) continue
// Left
if (!isSolid(worldX - 1, worldY)) { // TODO use isFlowable
// Equalise the amount fo water in this block and its neighbour
flow = (fluidMap[y][x] - fluidMap[y][x - 1]) / 4f
if (flow > minFlow) {
flow *= 0.5f
}
flow.coerceIn(0f, remainingMass)
fluidNewMap[y][x] -= flow
fluidNewMap[y][x - 1] += flow
remainingMass -= flow
}
if (remainingMass <= 0) continue
// Right
if (!isSolid(worldX + 1, worldY)) { // TODO use isFlowable
// Equalise the amount fo water in this block and its neighbour
flow = (fluidMap[y][x] - fluidMap[y][x + 1]) / 4f
if (flow > minFlow) {
flow *= 0.5f
}
flow.coerceIn(0f, remainingMass)
fluidNewMap[y][x] -= flow
fluidNewMap[y][x + 1] += flow
remainingMass -= flow
}
if (remainingMass <= 0) continue
// Up; only compressed water flows upwards
if (!isSolid(worldX, worldY - 1)) { // TODO use isFlowable
flow = remainingMass - getStableStateB(remainingMass + fluidMap[y - 1][x])
if (flow > minFlow) {
flow *= 0.5f
}
flow.coerceIn(0f, minOf(maxSpeed, remainingMass))
fluidNewMap[y][x] -= flow
fluidNewMap[y - 1][x] += flow
remainingMass -= flow
}
}
}
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
}
/**
@@ -109,11 +259,22 @@ object WorldSimulator {
}
private fun purgeFluidMap() {
for (y in 1..DOUBLE_RADIUS) {
for (x in 1..DOUBLE_RADIUS) {
fluidMap[y - 1][x - 1] = 0f
fluidTypeMap[y - 1][x - 1] = Fluid.NULL
private fun makeFluidMapFromWorld() {
for (y in 0 until fluidMap.size) {
for (x in 0 until fluidMap[0].size) {
val fluidData = world.getFluid(x + updateXFrom, y + updateYFrom)
fluidMap[y][x] = fluidData.amount
fluidTypeMap[y][x] = fluidData.type
fluidNewMap[y][x] = fluidData.amount
fluidNewTypeMap[y][x] = fluidData.type
}
}
}
private fun fluidmapToWorld() {
for (y in 0 until fluidMap.size) {
for (x in 0 until fluidMap[0].size) {
world.setFluid(x + updateXFrom, y + updateYFrom, fluidNewTypeMap[y][x], fluidNewMap[y][x])
}
}
}

View File

@@ -3,6 +3,7 @@ package net.torvald.terrarum.serialise
import com.badlogic.gdx.utils.compression.Lzma
import net.torvald.terrarum.AppLoader.printdbg
import net.torvald.terrarum.gameworld.BlockAddress
import net.torvald.terrarum.gameworld.FluidType
import net.torvald.terrarum.gameworld.MapLayer
import net.torvald.terrarum.gameworld.PairedMapLayer
import net.torvald.terrarum.realestate.LandUtil
@@ -156,7 +157,7 @@ internal object ReadLayerDataLzma {
val terrainDamages = HashMap<BlockAddress, Float>()
val wallDamages = HashMap<BlockAddress, Float>()
val fluidTypes = HashMap<BlockAddress, Int>()
val fluidTypes = HashMap<BlockAddress, FluidType>()
val fluidFills = HashMap<BlockAddress, Float>()
// parse terrain damages
@@ -215,7 +216,7 @@ internal object ReadLayerDataLzma {
val spawnY: Int,
val wallDamages: HashMap<BlockAddress, Float>,
val terrainDamages: HashMap<BlockAddress, Float>,
val fluidTypes: HashMap<BlockAddress, Int>,
val fluidTypes: HashMap<BlockAddress, FluidType>,
val fluidFills: HashMap<BlockAddress, Float>
)