fluid worldgen

This commit is contained in:
minjaesong
2024-09-10 02:13:13 +09:00
parent b3467d538c
commit 29c1a69222
12 changed files with 344 additions and 40 deletions

Binary file not shown.

View File

@@ -323,11 +323,12 @@ class Cvec {
/** Returns the color encoded as hex string with the format RRGGBBAA. */
override fun toString(): String {
var value = Integer
/*var value = Integer
.toHexString((255 * r).toInt() shl 24 or ((255 * g).toInt() shl 16) or ((255 * b).toInt() shl 8) or (255 * a).toInt())
while (value.length < 8)
value = "0$value"
return value
return value*/
return "($r,$g,$b,$a)"
}
/** Sets the RGB Cvec components using the specified Hue-Saturation-Value. Note that HSV components are voluntary not clamped

View File

@@ -53,6 +53,7 @@ internal class UnsafeCvecArray(val width: Int, val height: Int) {
target.g = array.getFloat(a + 1)
target.b = array.getFloat(a + 2)
target.a = array.getFloat(a + 3)
checkNaN(target)
}
/**
* `getAndSet(cvec, x, y, func)` is equivalent to
@@ -80,6 +81,7 @@ internal class UnsafeCvecArray(val width: Int, val height: Int) {
// fun setA(x: Int, y: Int, value: Float) { array.setFloat(toAddr(x, y) + 3, value) }
// operator fun set(i: Long, value: Float) = array.setFloat(i, value)
fun setVec(x: Int, y: Int, value: Cvec) {
checkNaN(value)
val a = toAddr(x, y)
array.setFloat(a + 0, value.r)
array.setFloat(a + 1, value.g)
@@ -87,6 +89,8 @@ internal class UnsafeCvecArray(val width: Int, val height: Int) {
array.setFloat(a + 3, value.a)
}
fun setScalar(x: Int, y: Int, value: Float) {
checkNaN(value)
val a = toAddr(x, y)
array.setFloat(a + 0, value)
@@ -103,6 +107,7 @@ internal class UnsafeCvecArray(val width: Int, val height: Int) {
// operators
fun max(x: Int, y: Int, other: Cvec) {
checkNaN(other)
val a = toAddr(x, y)
array.setFloat(a + 0, kotlin.math.max(array.getFloat(a + 0), other.r))
array.setFloat(a + 1, kotlin.math.max(array.getFloat(a + 1), other.g))
@@ -110,6 +115,7 @@ internal class UnsafeCvecArray(val width: Int, val height: Int) {
array.setFloat(a + 3, kotlin.math.max(array.getFloat(a + 3), other.a))
}
fun mul(x: Int, y: Int, scalar: Float) {
checkNaN(scalar)
val a = toAddr(x, y)
array.setFloat(a + 0, (array.getFloat(a + 0) * scalar))
array.setFloat(a + 1, (array.getFloat(a + 1) * scalar))
@@ -118,6 +124,7 @@ internal class UnsafeCvecArray(val width: Int, val height: Int) {
}
fun mulAndAssign(x: Int, y: Int, scalar: Float) {
checkNaN(scalar)
val addr = toAddr(x, y)
array.setFloat(addr + 0, (array.getFloat(addr + 0) * scalar))
array.setFloat(addr + 1, (array.getFloat(addr + 1) * scalar))
@@ -141,6 +148,12 @@ internal class UnsafeCvecArray(val width: Int, val height: Int) {
fun destroy() = this.array.destroy()
private inline fun checkNaN(vec: Cvec) {
// if (vec.r.isNaN() || vec.g.isNaN() || vec.b.isNaN() || vec.a.isNaN()) throw Error("Vector contains NaN (${vec.r},${vec.g},${vec.b},${vec.a})")
}
private inline fun checkNaN(scalar: Float) {
// if (scalar.isNaN()) throw Error("Scalar value is NaN ($scalar)")
}
}

View File

@@ -11,5 +11,6 @@ object Fluid {
val NULL = Block.AIR
val WATER = "fluid@basegame:1"
val LAVA = "fluid@basegame:2"
val CRUDE_OIL = "fluid@basegame:3"
}

View File

@@ -874,7 +874,7 @@ open class GameWorld(
if (fluidID == Block.NULL || fluidID == Block.NOT_GENERATED)
fluidID = Fluid.NULL
return FluidInfo(fluidID, fill.let { if (it.isNaN()) 0f else it }) // hex FFFFFFFF (magic number for ungenerated tiles) is interpreted as Float.NaN
return FluidInfo(fluidID, if (fill.isNaN()) 0f else fill) // hex FFFFFFFF (magic number for ungenerated tiles) is interpreted as Float.NaN
}
/*private fun fluidTypeToBlock(type: FluidType) = when (type.abs()) {

View File

@@ -61,7 +61,7 @@ class ItemBottomlessLavaBucket(originalID: ItemID) : GameItem(originalID) {
override fun startPrimaryUse(actor: ActorWithBody, delta: Float): Long {
val mx = Terrarum.mouseTileX; val my =Terrarum.mouseTileY
if (!BlockCodex[INGAME.world.getTileFromTerrain(mx, my)].isSolid) {
INGAME.world.setFluid(mx, my, Fluid.LAVA, 1f)
INGAME.world.setFluid(mx, my, Fluid.CRUDE_OIL, 1f)
return 0L
}
else {

View File

@@ -0,0 +1,266 @@
package net.torvald.terrarum.modulebasegame.worldgenerator
import com.sudoplay.joise.Joise
import com.sudoplay.joise.module.*
import net.torvald.terrarum.BlockCodex
import net.torvald.terrarum.INGAME
import net.torvald.terrarum.LoadScreenBase
import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.blockproperties.Fluid
import net.torvald.terrarum.gameitems.isFluid
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.realestate.LandUtil.CHUNK_H
import net.torvald.terrarum.realestate.LandUtil.CHUNK_W
import kotlin.math.cos
import kotlin.math.sin
/**
* Created by minjaesong on 2024-09-10.
*/
class Aquagen(world: GameWorld, isFinal: Boolean, val groundScalingCached: ModuleCache, seed: Long, params: Any) : Gen(world, isFinal, seed, params) {
private val FLUID_FILL = 1.2f
override fun getDone(loadscreen: LoadScreenBase?) {
loadscreen?.let {
it.stageValue += 1
it.progress.set(0L)
}
Worldgen.threadExecutor.renew()
submitJob(loadscreen)
Worldgen.threadExecutor.join()
}
override fun draw(xStart: Int, yStart: Int, noises: List<Joise>, soff: Double) {
if (INGAME.worldGenVer != null && INGAME.worldGenVer!! <= 0x0000_000004_000003) return
for (x in xStart until xStart + CHUNK_W) {
val st = (x.toDouble() / world.width) * TWO_PI
for (y in yStart until yStart + CHUNK_H) {
val sx = sin(st) * soff + soff // plus sampleOffset to make only
val sz = cos(st) * soff + soff // positive points are to be sampled
val sy = Worldgen.getSY(y)
// DEBUG NOTE: it is the OFFSET FROM THE IDEAL VALUE (observed land height - (HEIGHT * DIVISOR)) that must be constant
// get the actual noise values
// the size of the two lists are guaranteed to be identical as they all derive from the same `ores`
val noiseValues = noises.map { it.get(sx, sy, sz) }
val lavaVal = noiseValues[noiseValues.lastIndex - 2]
val lava = (lavaVal >= 0.5)
val waterVal = noiseValues[noiseValues.lastIndex - 1]
val waterShell = (waterVal >= 0.32)
val water = (waterVal >= 0.5)
val oilVal = noiseValues[noiseValues.lastIndex]
val oilShell = (oilVal >= 0.38)
val oil = (oilVal >= 0.5)
val backingTile = world.getTileFromWall(x, y)
val outFluid = if (water) Fluid.WATER
else if (oil) Fluid.CRUDE_OIL
else if (lava) Fluid.LAVA
else if (waterShell) backingTile
else if (oilShell) backingTile
else null
outFluid?.let {
if (outFluid.isFluid()) {
world.setTileTerrain(x, y, Block.AIR, true)
world.setFluid(x, y, outFluid, FLUID_FILL)
}
else {
world.setTileTerrain(x, y, outFluid, true)
}
}
}
}
}
override fun getGenerator(seed: Long, params: Any?): List<Joise> {
return listOf(
Joise(generateSeaOfLava(seed)),
Joise(generateAquifer(seed, groundScalingCached)),
Joise(generateCrudeOil(seed, groundScalingCached)),
)
}
private fun generateSeaOfLava(seed: Long): Module {
val params = params as TerragenParams
val lavaPipe = ModuleScaleDomain().also {
it.setSource(ModuleFractal().also {
it.setType(ModuleFractal.FractalType.RIDGEMULTI)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(1)
it.setFrequency(params.lavaShapeFreg) // adjust the "density" of the caves
it.seed = seed shake "LattiaOnLavaa"
})
it.setScaleY(1.0 / 6.0)
}
val lavaPerturbFractal = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.FBM)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(6)
it.setFrequency(params.lavaShapeFreg * 3.0 / 4.0)
it.seed = seed shake "FloorIsLava"
}
val lavaPerturbScale = ModuleScaleOffset().also {
it.setSource(lavaPerturbFractal)
it.setScale(23.0)
it.setOffset(0.0)
}
val lavaPerturb = ModuleTranslateDomain().also {
it.setSource(lavaPipe)
it.setAxisXSource(lavaPerturbScale)
}
val lavaSelect = ModuleSelect().also {
it.setLowSource(1.0)
it.setHighSource(0.0)
it.setControlSource(lavaPerturb)
it.setThreshold(lavaGrad)
it.setFalloff(0.0)
}
return lavaSelect
}
private fun generateAquifer(seed: Long, groundScalingCached: Module): Module {
val params = params as TerragenParams
val waterPocket = ModuleScaleDomain().also {
it.setSource(ModuleFractal().also {
it.setType(ModuleFractal.FractalType.BILLOW)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.SIMPLEX)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(4)
it.setFrequency(params.rockBandCutoffFreq / params.featureSize)
it.seed = seed shake "WaterPocket"
})
it.setScaleX(0.5)
it.setScaleZ(0.5)
it.setScaleY(0.8)
}
val terrainBool = ModuleSelect().also {
it.setLowSource(0.0)
it.setHighSource(1.0)
it.setControlSource(groundScalingCached)
it.setThreshold(0.5)
it.setFalloff(0.1)
}
val aquifer = ModuleCombiner().also {
it.setType(ModuleCombiner.CombinerType.MULT)
it.setSource(0, waterPocket)
it.setSource(1, terrainBool)
it.setSource(2, aquiferGrad)
}
return aquifer
}
private fun generateCrudeOil(seed: Long, groundScalingCached: Module): Module {
val params = params as TerragenParams
val oilPocket = ModuleScaleDomain().also {
it.setSource(ModuleFractal().also {
it.setType(ModuleFractal.FractalType.BILLOW)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.SIMPLEX)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(4)
it.setFrequency(params.rockBandCutoffFreq / params.featureSize)
it.seed = seed shake "CrudeOil"
})
it.setScaleX(0.16)
it.setScaleZ(0.16)
it.setScaleY(1.4)
}
crudeOilGradStart = TerrarumModuleCacheY().also {
it.setSource(ModuleClamp().also {
it.setSource(ModuleScaleOffset().also {
it.setSource(groundScalingCached)
it.setOffset(-8.0)
})
it.setRange(0.0, 1.0)
})
}
crudeOilGrad = TerrarumModuleCacheY().also {
it.setSource(ModuleCombiner().also {
it.setType(ModuleCombiner.CombinerType.ADD)
it.setSource(0, crudeOilGradStart)
it.setSource(1, crudeOilGradEnd)
it.setSource(2, ModuleConstant().also { it.setConstant(-1.0) })
})
}
val oilLayer = ModuleCombiner().also {
it.setType(ModuleCombiner.CombinerType.MULT)
it.setSource(0, oilPocket)
it.setSource(1, crudeOilGrad)
}
return oilLayer
}
companion object {
// val = sqrt((y-H+L) / L); where H=5300 (world height-100), L=620;
// 100 is the height of the "base lava sheet", 600 is the height of the "transitional layer"
// in this setup, the entire lava layer never exceeds 8 chunks (720 tiles) in height
val lavaGrad = TerrarumModuleCacheY().also {
it.setSource(TerrarumModuleLavaFloorGrad().also {
it.setH(5300.0)
it.setL(620.0)
})
}
val aquiferGrad = TerrarumModuleCacheY().also {
it.setSource(TerrarumModuleCaveLayerClosureGrad().also {
it.setH(4300.0)
it.setL(620.0)
})
}
lateinit var crudeOilGradStart: TerrarumModuleCacheY
lateinit var crudeOilGrad: TerrarumModuleCacheY
val crudeOilGradEnd = TerrarumModuleCacheY().also {
it.setSource(TerrarumModuleCaveLayerClosureGrad().also {
it.setH(4800.0)
it.setL(620.0)
})
}
val caveTerminalClosureGrad = TerrarumModuleCacheY().also {
it.setSource(TerrarumModuleCaveLayerClosureGrad().also {
it.setH(17.2)
it.setL(3.0)
})
}
val aquiferTerminalClosureGrad = TerrarumModuleCacheY().also {
it.setSource(TerrarumModuleCaveLayerClosureGrad().also {
it.setH(21.0)
it.setL(8.0)
})
}
}
}

View File

@@ -2,6 +2,7 @@ package net.torvald.terrarum.modulebasegame.worldgenerator
import com.sudoplay.joise.Joise
import com.sudoplay.joise.module.*
import net.torvald.terrarum.INGAME
import net.torvald.terrarum.LoadScreenBase
import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.gameworld.GameWorld
@@ -99,11 +100,19 @@ class Cavegen(world: GameWorld, isFinal: Boolean, val highlandLowlandSelectCache
it.setOffset(0.0)
}
val cavePerturb = ModuleTranslateDomain().also {
val cavePerturb0 = ModuleTranslateDomain().also {
it.setSource(caveShapeAttenuate)
it.setAxisXSource(cavePerturbScale)
}
val cavePerturb = ModuleCombiner().also { // 0: rock, 1: air
it.setType(ModuleCombiner.CombinerType.MULT)
it.setSource(0, cavePerturb0)
// basically disabling terminal closure for the world generated from the old version
if (INGAME.worldGenVer != null && INGAME.worldGenVer!! <= 0x0000_000004_000003)
it.setSource(1, caveTerminalClosureGrad)
}
val caveSelect = ModuleSelect().also {
it.setLowSource(1.0)
it.setHighSource(0.0)
@@ -161,4 +170,13 @@ class Cavegen(world: GameWorld, isFinal: Boolean, val highlandLowlandSelectCache
Joise(caveScaling)
)
}
companion object {
val caveTerminalClosureGrad = TerrarumModuleCacheY().also {
it.setSource(TerrarumModuleCaveLayerClosureGrad().also {
it.setH(17.2)
it.setL(3.0)
})
}
}
}

View File

@@ -16,7 +16,7 @@ import kotlin.math.sin
/**
* Created by minjaesong on 2019-07-23.
*/
class Terragen(world: GameWorld, isFinal: Boolean , val highlandLowlandSelectCache: ModuleCache, seed: Long, params: Any) : Gen(world, isFinal, seed, params) {
class Terragen(world: GameWorld, isFinal: Boolean, val groundScalingCached: ModuleCache, seed: Long, params: Any) : Gen(world, isFinal, seed, params) {
private val dirtStoneDitherSize = 3 // actual dither size will be double of this value
private val stoneSlateDitherSize = 4
@@ -151,24 +151,12 @@ class Terragen(world: GameWorld, isFinal: Boolean , val highlandLowlandSelectCac
// they should be treated properly when you actually generate the world out of the noisemap
// for the visualisation, no treatment will be done in this demo app.
val groundClamp = ModuleClamp().also {
it.setRange(0.0, 100.0)
it.setSource(highlandLowlandSelectCache)
}
val groundScaling = ModuleScaleDomain().also {
it.setScaleX(1.0 / params.featureSize) // adjust this value to change features size
it.setScaleY(1.0 / params.featureSize)
it.setScaleZ(1.0 / params.featureSize)
it.setSource(groundClamp)
}
val marblerng = HQRNG(seed) // this must be here: every slice must get identical series of random numbers
return listOf(
Joise(groundScaling),
Joise(groundScalingCached),
Joise(generateRockLayer(groundScaling, seed, params, (0..7).map {
Joise(generateRockLayer(groundScalingCached, seed, params, (0..7).map {
thicknesses[it] + marblerng.nextTriangularBal() * 0.006 to (1.04 * params.terragenTiers[3] * terragenYscaling) + it * 0.18 + marblerng.nextTriangularBal() * 0.09
})),
)

View File

@@ -59,12 +59,14 @@ object Worldgen {
params = genParams
highlandLowlandSelectCache = getHighlandLowlandSelectCache(params.terragenParams, params.seed)
caveAttenuateBiasScaledCache = getCaveAttenuateBiasScaled(highlandLowlandSelectCache, params.terragenParams)
caveAttenuateBiasScaledForOresCache = getCaveAttenuateBiasScaled(highlandLowlandSelectCache, params.terragenParams)
groundScalingCached = getGroundScalingCached(highlandLowlandSelectCache, params.terragenParams)
biomeMap = HashMap()
}
internal lateinit var groundScalingCached: ModuleCache
internal lateinit var highlandLowlandSelectCache: ModuleCache
internal lateinit var caveAttenuateBiasScaledCache: ModuleCache
internal lateinit var caveAttenuateBiasScaledForOresCache: ModuleCache
internal lateinit var biomeMap: HashMap<BlockAddress, Byte>
@@ -86,11 +88,12 @@ object Worldgen {
else { work: Work -> (work.tags union tags).isNotEmpty() }
return listOf(
Work(Lang["MENU_IO_WORLDGEN_RETICULATING_SPLINES"], Terragen(world, false, highlandLowlandSelectCache, params.seed, params.terragenParams), listOf("TERRAIN")), // also generates marble veins
Work(Lang["MENU_IO_WORLDGEN_GROWING_MINERALS"], Oregen(world, false, caveAttenuateBiasScaledCache, params.seed, oreRegistry), listOf("ORES")),
Work(Lang["MENU_IO_WORLDGEN_RETICULATING_SPLINES"], Terragen(world, false, groundScalingCached, params.seed, params.terragenParams), listOf("TERRAIN")), // also generates marble veins
Work(Lang["MENU_IO_WORLDGEN_CARVING_EARTH"], Cavegen(world, false, highlandLowlandSelectCache, params.seed, params.terragenParams), listOf("TERRAIN", "CAVE")),
Work(Lang["MENU_IO_WORLDGEN_FLOODING_UNDERGROUND"], Aquagen(world, false, groundScalingCached, params.seed, params.terragenParams), listOf("WATER")),
Work(Lang["MENU_IO_WORLDGEN_GROWING_MINERALS"], Oregen(world, false, caveAttenuateBiasScaledForOresCache, params.seed, oreRegistry), listOf("ORES")),
Work(Lang["MENU_IO_WORLDGEN_POSITIONING_ROCKS"], OregenAutotiling(world, false, params.seed, oreTilingModes), listOf("ORES")),
// TODO generate gemstones
Work(Lang["MENU_IO_WORLDGEN_CARVING_EARTH"], Cavegen(world, false, highlandLowlandSelectCache, params.seed, params.terragenParams), listOf("TERRAIN", "CAVE")),
Work(Lang["MENU_IO_WORLDGEN_PAINTING_GREEN"], Biomegen(world, false, params.seed, params.biomegenParams, biomeMap), listOf("BIOME")),
Work(Lang["MENU_IO_WORLDGEN_PAINTING_GREEN"], Treegen(world, true, params.seed, params.terragenParams, params.treegenParams, biomeMap), listOf("TREES")),
).filter(tagFilter)
@@ -454,6 +457,24 @@ object Worldgen {
}
}
private fun getGroundScalingCached(highlandLowlandSelectCache: ModuleCache, params: TerragenParams): ModuleCache {
val groundClamp = ModuleClamp().also {
it.setRange(0.0, 100.0)
it.setSource(highlandLowlandSelectCache)
}
val groundScaling = ModuleScaleDomain().also {
it.setScaleX(1.0 / params.featureSize) // adjust this value to change features size
it.setScaleY(1.0 / params.featureSize)
it.setScaleZ(1.0 / params.featureSize)
it.setSource(groundClamp)
}
return ModuleCache().also {
it.setSource(groundScaling)
}
}
}
abstract class Gen(val world: GameWorld, val isFinal: Boolean, val seed: Long, val params: Any? = null) {

View File

@@ -744,7 +744,7 @@ internal class TerragenTest(val params: TerragenParams) : NoiseMaker {
it.setFalloff(0.0)
}
val caveBlockageFractal0 = ModuleFractal().also {
val caveBlockageFractal = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.RIDGEMULTI)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
@@ -753,11 +753,6 @@ internal class TerragenTest(val params: TerragenParams) : NoiseMaker {
it.seed = seed shake caveBlockageMagic
}
val caveBlockageFractal = ModuleCombiner().also { // 0: air, 1: rock
it.setType(ModuleCombiner.CombinerType.MULT)
it.setSource(0, caveBlockageFractal0)
}
// will only close-up deeper caves. Shallow caves will be less likely to be closed up
val caveBlockageAttenuate = ModuleCombiner().also {
it.setType(ModuleCombiner.CombinerType.MULT)

View File

@@ -538,14 +538,12 @@ object LightmapRenderer {
}*/
if (_thisFluid.type != Fluid.NULL) {
_thisFluid.amount.coerceAtMost(1f).let {
_fluidAmountToCol.set(it, it, it, it)
}
val fluidAmount = _thisFluid.amount.coerceIn(0f, 1f)
_thisTileLuminosity.set(_thisTerrainProp.getLumCol(worldX, worldY))
_thisTileLuminosity.maxAndAssign(_thisFluidProp.lumCol.mul(_fluidAmountToCol))
_thisTileLuminosity.maxAndAssign(_thisFluidProp.lumCol.cpy().mul(fluidAmount))
_mapThisTileOpacity.setVec(lx, ly, _thisTerrainProp.opacity)
_mapThisTileOpacity.max(lx, ly, _thisFluidProp.opacity.mul(_fluidAmountToCol))
_mapThisTileOpacity.max(lx, ly, _thisFluidProp.opacity.cpy().mul(fluidAmount))
}
else {
_thisTileLuminosity.set(_thisTerrainProp.getLumCol(worldX, worldY))
@@ -569,9 +567,8 @@ object LightmapRenderer {
}
// blend lantern
_mapLightLevelThis.max(lx, ly, _thisTileLuminosity.maxAndAssign(
lanternMap[LandUtil.getBlockAddr(world, worldX, worldY)] ?: colourNull
))
_mapLightLevelThis.max(lx, ly, _thisTileLuminosity)
_mapLightLevelThis.max(lx, ly, lanternMap[LandUtil.getBlockAddr(world, worldX, worldY)] ?: colourNull)
}
private fun precalculate2(lightmap: UnsafeCvecArray, rawx: Int, rawy: Int) {
@@ -698,6 +695,8 @@ object LightmapRenderer {
// val uvlwg = (uvl.sqrt() - 1f) * (1f / 1279f)
// val uvlwb = (uvl.sqrt() - 1f) * (1f / 319f)
if (red.isNaN() || grn.isNaN() || blu.isNaN() || uvl.isNaN()) throw IllegalArgumentException("Light vector contains NaN ($red,$grn,$blu,$uvl)")
if (solidMultMagic == null)
lightBuffer.drawPixel(
x - this_x_start,
@@ -867,6 +866,8 @@ object LightmapRenderer {
* @return -0.5..0.5
*/
private fun clipfun0(x0: Float): Float {
if (x0.isNaN()) throw IllegalArgumentException("Cannot clip NaN value.")
val x = x0 * (1.0f + clip_p1) / 2.0f
val t = 0.5f * clip_p1