biome in worldgen

This commit is contained in:
minjaesong
2020-06-12 15:21:55 +09:00
parent 38d5b17eeb
commit 5b05a84684
9 changed files with 162 additions and 36 deletions

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum.concurrent
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
@@ -12,13 +13,19 @@ typealias ThreadableFun = (Int) -> Unit
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
private var executor = Executors.newFixedThreadPool(threadCount)
private lateinit var executor: ExecutorService// = Executors.newFixedThreadPool(threadCount)
private fun checkShutdown() {
if (!executor.isShutdown) return
if (executor.isShutdown&& !executor.isTerminated)
throw IllegalStateException("Pool is closed, come back when all the threads are terminated.")
try {
if (executor.isTerminated)
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)
}
@@ -32,6 +39,7 @@ object ThreadExecutor {
}
fun join() {
println("ThreadExecutor.join")
executor.shutdown() // thread status of completed ones will be WAIT instead of TERMINATED without this line...
executor.awaitTermination(24L, TimeUnit.HOURS)
}

View File

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

View File

@@ -38,7 +38,8 @@ class UIProxyNewRandomGame : UICanvas() {
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)
ingame.gameLoadInfoPayload = worldParam
ingame.gameLoadMode = TerrarumIngame.GameLoadMode.CREATE_NEW

View File

@@ -1,20 +1,140 @@
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.fmod
import java.util.concurrent.Future
import kotlin.math.cos
import kotlin.math.sin
/**
* Created by minjaesong on 2019-09-02.
*/
class Biomegen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, params) {
override var generationStarted: Boolean
get() = TODO("not implemented")
set(value) {}
private val genSlices = maxOf(ThreadExecutor.threadCount, world.width / 8)
private var genFutures: Array<Future<*>?> = arrayOfNulls(genSlices)
override var generationStarted: Boolean = false
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() {
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(

View File

@@ -18,7 +18,7 @@ import kotlin.math.sin
*/
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)
override var generationStarted: Boolean = false
@@ -32,6 +32,7 @@ class Terragen(world: GameWorld, seed: Long, params: Any) : Gen(world, seed, par
generationStarted = true
ThreadExecutor.renew()
(0 until world.width).sliceEvenly(genSlices).mapIndexed { i, xs ->
genFutures[i] = ThreadExecutor.submit {
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
)
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 {
tiers.reversed().forEachIndexed { index, it ->
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(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
// 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.

View File

@@ -23,8 +23,8 @@ object Worldgen {
fun generateMap() {
val jobs = listOf(
Work("Reticulating Splines", Terragen(world, params.seed, params.terragenParams))
//Work("Adding Vegetations") { Biomegen(world, params.seed, params.biomegenParams) }
Work("Reticulating Splines", Terragen(world, params.seed, params.terragenParams)),
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> {
return NOISE_MAKER.getGenerator(SEED, TerragenParams())
//return NOISE_MAKER.getGenerator(SEED, TerragenParams())
return NOISE_MAKER.getGenerator(SEED, BiomegenParams())
}
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(WIDTH / 8)
//private val xSlices = (0 until WIDTH).sliceEvenly(ThreadExecutor.threadCount)
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()
@@ -261,7 +262,7 @@ fun main(args: Array<String>) {
}
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>
}
@@ -269,12 +270,12 @@ val locklock = java.lang.Object()
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 control = noiseValue[0].times(colPal.size).minus(0.00001f).toInt().fmod(colPal.size)
//outTex.setColor(colPal[control])
//outTex.drawPixel(x, y)
outTex.setColor(colPal[control])
outTex.drawPixel(x, y)
}
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
// 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) {
throw StackOverflowError()
}
@@ -98,8 +99,8 @@ class CircularArray<T>(val size: Int, val overwriteOnOverflow: Boolean): Iterabl
}
}
fun removeHead(): T {
if (isEmpty) throw EmptyStackException()
fun removeHead(): T? {
if (isEmpty) return null
decHead()
overflow = false
@@ -107,8 +108,8 @@ class CircularArray<T>(val size: Int, val overwriteOnOverflow: Boolean): Iterabl
return buffer[head]
}
fun removeTail(): T {
if (isEmpty) throw EmptyStackException()
fun removeTail(): T? {
if (isEmpty) return null
val ret = buffer[tail]
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.
*/
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 {
head == tail -> buffer.size

Binary file not shown.