biome in worldgen

This commit is contained in:
minjaesong
2020-06-12 15:21:55 +09:00
parent 04cf817303
commit 37bc8a6aff
9 changed files with 162 additions and 36 deletions

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum.concurrent package net.torvald.terrarum.concurrent
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.Future import java.util.concurrent.Future
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -12,13 +13,19 @@ typealias ThreadableFun = (Int) -> Unit
object ThreadExecutor { object ThreadExecutor {
val threadCount = Runtime.getRuntime().availableProcessors() // not using (logicalCores + 1) method; it's often better idea to reserve one extra thread for other jobs in the app val threadCount = Runtime.getRuntime().availableProcessors() // not using (logicalCores + 1) method; it's often better idea to reserve one extra thread for other jobs in the app
private var executor = Executors.newFixedThreadPool(threadCount) private lateinit var executor: ExecutorService// = Executors.newFixedThreadPool(threadCount)
private fun checkShutdown() { private fun checkShutdown() {
if (!executor.isShutdown) return try {
if (executor.isShutdown&& !executor.isTerminated) if (executor.isTerminated)
throw IllegalStateException("Pool is closed, come back when all the threads are terminated.") throw IllegalStateException("Executor terminated, renew the executor service.")
if (executor.isShutdown)
throw IllegalStateException("Pool is closed, come back when all the threads are terminated.")
}
catch (e: UninitializedPropertyAccessException) {}
}
fun renew() {
executor = Executors.newFixedThreadPool(threadCount) executor = Executors.newFixedThreadPool(threadCount)
} }
@@ -32,6 +39,7 @@ object ThreadExecutor {
} }
fun join() { fun join() {
println("ThreadExecutor.join")
executor.shutdown() // thread status of completed ones will be WAIT instead of TERMINATED without this line... executor.shutdown() // thread status of completed ones will be WAIT instead of TERMINATED without this line...
executor.awaitTermination(24L, TimeUnit.HOURS) executor.awaitTermination(24L, TimeUnit.HOURS)
} }

View File

@@ -72,9 +72,9 @@ internal object ExportMap : ConsoleCommand {
* R-G-B-A order for RGBA input value * R-G-B-A order for RGBA input value
*/ */
private fun Int.toByteArray() = byteArrayOf( private fun Int.toByteArray() = byteArrayOf(
this.shl(24).and(0xff).toByte(), this.shr(24).and(0xff).toByte(),
this.shl(16).and(0xff).toByte(), this.shr(16).and(0xff).toByte(),
this.shl(8).and(0xff).toByte(), this.shr(8).and(0xff).toByte(),
this.and(0xff).toByte() this.and(0xff).toByte()
) )

View File

@@ -38,7 +38,8 @@ class UIProxyNewRandomGame : UICanvas() {
val ingame = TerrarumIngame(AppLoader.batch) val ingame = TerrarumIngame(AppLoader.batch)
val worldParam = TerrarumIngame.NewWorldParameters(2400, 800, HQRNG().nextLong()) val worldParam = TerrarumIngame.NewWorldParameters(2400, 800, 0x51621DL)
//val worldParam = TerrarumIngame.NewWorldParameters(2400, 800, HQRNG().nextLong())
//val worldParam = TerrarumIngame.NewWorldParameters(8192, 2048, 0x51621DL) //val worldParam = TerrarumIngame.NewWorldParameters(8192, 2048, 0x51621DL)
ingame.gameLoadInfoPayload = worldParam ingame.gameLoadInfoPayload = worldParam
ingame.gameLoadMode = TerrarumIngame.GameLoadMode.CREATE_NEW ingame.gameLoadMode = TerrarumIngame.GameLoadMode.CREATE_NEW

View File

@@ -1,20 +1,140 @@
package net.torvald.terrarum.modulebasegame.worldgenerator package net.torvald.terrarum.modulebasegame.worldgenerator
import com.badlogic.gdx.graphics.Pixmap
import com.sudoplay.joise.Joise
import com.sudoplay.joise.module.ModuleAutoCorrect
import com.sudoplay.joise.module.ModuleBasisFunction
import com.sudoplay.joise.module.ModuleFractal
import com.sudoplay.joise.module.ModuleScaleDomain
import net.torvald.terrarum.AppLoader
import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.concurrent.ThreadExecutor
import net.torvald.terrarum.concurrent.sliceEvenly
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.fmod
import java.util.concurrent.Future
import kotlin.math.cos
import kotlin.math.sin
/** /**
* Created by minjaesong on 2019-09-02. * Created by minjaesong on 2019-09-02.
*/ */
class Biomegen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, params) { class Biomegen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, params) {
override var generationStarted: Boolean
get() = TODO("not implemented") private val genSlices = maxOf(ThreadExecutor.threadCount, world.width / 8)
set(value) {}
private var genFutures: Array<Future<*>?> = arrayOfNulls(genSlices)
override var generationStarted: Boolean = false
override val generationDone: Boolean override val generationDone: Boolean
get() = TODO("not implemented") get() = generationStarted && genFutures.fold(true) { acc, f -> acc and (f?.isDone ?: true) }
private val YHEIGHT_MAGIC = 2800.0 / 3.0
private val YHEIGHT_DIVISOR = 2.0 / 7.0
override fun run() { override fun run() {
TODO("not implemented")
generationStarted = true
ThreadExecutor.renew()
(0 until world.width).sliceEvenly(genSlices).mapIndexed { i, xs ->
genFutures[i] = ThreadExecutor.submit {
val localJoise = getGenerator(seed, params as BiomegenParams)
for (x in xs) {
for (y in 0 until world.height) {
val sampleTheta = (x.toDouble() / world.width) * TWO_PI
val sampleOffset = world.width / 8.0
val sampleX = sin(sampleTheta) * sampleOffset + sampleOffset // plus sampleOffset to make only
val sampleZ = cos(sampleTheta) * sampleOffset + sampleOffset // positive points are to be sampled
val sampleY = y - (world.height - YHEIGHT_MAGIC) * YHEIGHT_DIVISOR // Q&D offsetting to make ratio of sky:ground to be constant
// DEBUG NOTE: it is the OFFSET FROM THE IDEAL VALUE (observed land height - (HEIGHT * DIVISOR)) that must be constant
val noise = localJoise.map { it.get(sampleX, sampleY, sampleZ) }
draw(x, y, noise, world)
}
}
}
}
ThreadExecutor.join()
AppLoader.printdbg(this, "Waking up Worldgen")
} }
val nearbyArr = arrayOf(
(-1 to -1), // tileTL
(+1 to -1), // tileTR
(-1 to +1), // tileBL
(+1 to +1), // tileBR
(0 to -1), // tileT
(0 to +1), // tileB
(-1 to 0), // tileL
(+1 to 0) // tileR
)
private fun draw(x: Int, y: Int, noiseValue: List<Double>, world: GameWorld) {
val control = noiseValue[0].times(4).minus(0.00001f).toInt().fmod(4)
if (y > 0) {
val tileThis = world.getTileFromTerrain(x, y)
val wallThis = world.getTileFromWall(x, y)
val nearbyTerr = nearbyArr.map { world.getTileFromTerrain(x + it.first, y + it.second) }
val nearbyWall = nearbyArr.map { world.getTileFromWall(x + it.first, y + it.second) }
when (control) {
0 -> { // woodlands
if (world.getTileFromTerrain(x, y) == Block.DIRT && nearbyTerr.any { it == Block.AIR } && nearbyWall.any { it == Block.AIR }) {
world.setTileTerrain(x, y, Block.GRASS)
}
}
1 -> { // shrublands
if (world.getTileFromTerrain(x, y) == Block.DIRT && nearbyTerr.any { it == Block.AIR } && nearbyWall.any { it == Block.AIR }) {
world.setTileTerrain(x, y, Block.GRASS)
}
}
2 -> { // plains
if (world.getTileFromTerrain(x, y) == Block.DIRT && nearbyTerr.any { it == Block.AIR } && nearbyWall.any { it == Block.AIR }) {
world.setTileTerrain(x, y, Block.GRASS)
}
}
3 -> { // rockylands
if (world.getTileFromTerrain(x, y) == Block.DIRT) {
world.setTileTerrain(x, y, Block.STONE)
world.setTileWall(x, y, Block.STONE)
}
}
}
}
}
private fun getGenerator(seed: Long, params: BiomegenParams): List<Joise> {
//val biome = ModuleBasisFunction()
//biome.setType(ModuleBasisFunction.BasisType.SIMPLEX)
// simplex AND fractal for more noisy edges, mmmm..!
val fractal = ModuleFractal()
fractal.setType(ModuleFractal.FractalType.MULTI)
fractal.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.SIMPLEX)
fractal.setNumOctaves(4)
fractal.setFrequency(1.0)
fractal.seed = seed shake 0x7E22A
val autocorrect = ModuleAutoCorrect()
autocorrect.setSource(fractal)
autocorrect.setRange(0.0, 1.0)
val scale = ModuleScaleDomain()
scale.setSource(autocorrect)
scale.setScaleX(1.0 / params.featureSize) // adjust this value to change features size
scale.setScaleY(1.0 / params.featureSize)
scale.setScaleZ(1.0 / params.featureSize)
val last = scale
return listOf(Joise(last))
}
} }
data class BiomegenParams( data class BiomegenParams(

View File

@@ -18,7 +18,7 @@ import kotlin.math.sin
*/ */
class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, params) { class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, params) {
private val genSlices = maxOf(world.width, ThreadExecutor.threadCount, world.width / 8) private val genSlices = maxOf(ThreadExecutor.threadCount, world.width / 8)
private var genFutures: Array<Future<*>?> = arrayOfNulls(genSlices) private var genFutures: Array<Future<*>?> = arrayOfNulls(genSlices)
override var generationStarted: Boolean = false override var generationStarted: Boolean = false
@@ -32,6 +32,7 @@ class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, par
generationStarted = true generationStarted = true
ThreadExecutor.renew()
(0 until world.width).sliceEvenly(genSlices).mapIndexed { i, xs -> (0 until world.width).sliceEvenly(genSlices).mapIndexed { i, xs ->
genFutures[i] = ThreadExecutor.submit { genFutures[i] = ThreadExecutor.submit {
val localJoise = getGenerator(seed, params as TerragenParams) val localJoise = getGenerator(seed, params as TerragenParams)
@@ -61,7 +62,7 @@ class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, par
Block.AIR, Block.DIRT, Block.STONE Block.AIR, Block.DIRT, Block.STONE
) )
fun draw(x: Int, y: Int, noiseValue: List<Double>, world: GameWorld) { private fun draw(x: Int, y: Int, noiseValue: List<Double>, world: GameWorld) {
fun Double.tiered(vararg tiers: Double): Int { fun Double.tiered(vararg tiers: Double): Int {
tiers.reversed().forEachIndexed { index, it -> tiers.reversed().forEachIndexed { index, it ->
if (this >= it) return (tiers.lastIndex - index) // why?? if (this >= it) return (tiers.lastIndex - index) // why??
@@ -298,12 +299,6 @@ class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, par
caveInMix.setSource(0, caveSelect) caveInMix.setSource(0, caveSelect)
caveInMix.setSource(1, caveBlockageSelect) caveInMix.setSource(1, caveBlockageSelect)
/*val groundCaveMult = ModuleCombiner()
groundCaveMult.setType(ModuleCombiner.CombinerType.MULT)
groundCaveMult.setSource(0, caveInMix)
//groundCaveMult.setSource(0, caveSelect) // disables the cave-in for quick cavegen testing
groundCaveMult.setSource(1, groundSelect)*/
// this noise tree WILL generate noise value greater than 1.0 // this noise tree WILL generate noise value greater than 1.0
// they should be treated properly when you actually generate the world out of the noisemap // 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. // for the visualisation, no treatment will be done in this demo app.

View File

@@ -23,8 +23,8 @@ object Worldgen {
fun generateMap() { fun generateMap() {
val jobs = listOf( val jobs = listOf(
Work("Reticulating Splines", Terragen(world, params.seed, params.terragenParams)) Work("Reticulating Splines", Terragen(world, params.seed, params.terragenParams)),
//Work("Adding Vegetations") { Biomegen(world, params.seed, params.biomegenParams) } Work("Adding Vegetations", Biomegen(world, params.seed, params.biomegenParams))
) )

View File

@@ -115,10 +115,11 @@ class WorldgenNoiseSandbox : ApplicationAdapter() {
} }
} }
private val NOISE_MAKER = AccidentalCave private val NOISE_MAKER = BiomeMaker
private fun getNoiseGenerator(SEED: Long): List<Joise> { private fun getNoiseGenerator(SEED: Long): List<Joise> {
return NOISE_MAKER.getGenerator(SEED, TerragenParams()) //return NOISE_MAKER.getGenerator(SEED, TerragenParams())
return NOISE_MAKER.getGenerator(SEED, BiomegenParams())
} }
val colourNull = Color(0x1b3281ff) val colourNull = Color(0x1b3281ff)
@@ -157,8 +158,8 @@ class WorldgenNoiseSandbox : ApplicationAdapter() {
} }
} }
private val xSlices = (0 until WIDTH).sliceEvenly(ThreadExecutor.threadCount) //private val xSlices = (0 until WIDTH).sliceEvenly(ThreadExecutor.threadCount)
//private val xSlices = (0 until WIDTH).sliceEvenly(WIDTH / 8) private val xSlices = (0 until WIDTH).sliceEvenly(maxOf(WIDTH, ThreadExecutor.threadCount, WIDTH / 8))
private val runs = (0 until WIDTH).map { x -> (x until WIDTH * HEIGHT step WIDTH) }.flatten() private val runs = (0 until WIDTH).map { x -> (x until WIDTH * HEIGHT step WIDTH) }.flatten()
@@ -261,7 +262,7 @@ fun main(args: Array<String>) {
} }
internal interface NoiseMaker { internal interface NoiseMaker {
fun draw(x: Int, y: Int, noiseValue: List<Double>, outTex: UnsafePtr) fun draw(x: Int, y: Int, noiseValue: List<Double>, outTex: Pixmap)
fun getGenerator(seed: Long, params: Any): List<Joise> fun getGenerator(seed: Long, params: Any): List<Joise>
} }
@@ -269,12 +270,12 @@ val locklock = java.lang.Object()
internal object BiomeMaker : NoiseMaker { internal object BiomeMaker : NoiseMaker {
override fun draw(x: Int, y: Int, noiseValue: List<Double>, outTex: UnsafePtr) { override fun draw(x: Int, y: Int, noiseValue: List<Double>, outTex: Pixmap) {
val colPal = biomeColors val colPal = biomeColors
val control = noiseValue[0].times(colPal.size).minus(0.00001f).toInt().fmod(colPal.size) val control = noiseValue[0].times(colPal.size).minus(0.00001f).toInt().fmod(colPal.size)
//outTex.setColor(colPal[control]) outTex.setColor(colPal[control])
//outTex.drawPixel(x, y) outTex.drawPixel(x, y)
} }
override fun getGenerator(seed: Long, params: Any): List<Joise> { override fun getGenerator(seed: Long, params: Any): List<Joise> {

View File

@@ -84,6 +84,7 @@ class CircularArray<T>(val size: Int, val overwriteOnOverflow: Boolean): Iterabl
// even if overflowing is enabled, appending at tail causes head element to be altered, therefore such action // even if overflowing is enabled, appending at tail causes head element to be altered, therefore such action
// must be blocked by throwing overflow error // must be blocked by throwing overflow error
// if you think this behaviour is wrong, you're confusing appendHead() with appendTail(). Use appendHead() and removeTail()
if (overflow) { if (overflow) {
throw StackOverflowError() throw StackOverflowError()
} }
@@ -98,8 +99,8 @@ class CircularArray<T>(val size: Int, val overwriteOnOverflow: Boolean): Iterabl
} }
} }
fun removeHead(): T { fun removeHead(): T? {
if (isEmpty) throw EmptyStackException() if (isEmpty) return null
decHead() decHead()
overflow = false overflow = false
@@ -107,8 +108,8 @@ class CircularArray<T>(val size: Int, val overwriteOnOverflow: Boolean): Iterabl
return buffer[head] return buffer[head]
} }
fun removeTail(): T { fun removeTail(): T? {
if (isEmpty) throw EmptyStackException() if (isEmpty) return null
val ret = buffer[tail] val ret = buffer[tail]
incTail() incTail()
@@ -125,7 +126,7 @@ class CircularArray<T>(val size: Int, val overwriteOnOverflow: Boolean): Iterabl
/** /**
* Relative-indexed get. Index of zero will return the head element. * Relative-indexed get. Index of zero will return the head element.
*/ */
operator fun get(index: Int) = buffer[(head - 1 - index).wrap()] operator fun get(index: Int): T? = buffer[(head - 1 - index).wrap()]
private fun getAbsoluteRange() = 0 until when { private fun getAbsoluteRange() = 0 until when {
head == tail -> buffer.size head == tail -> buffer.size

Binary file not shown.