mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
biome in worldgen
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
work_files/graphics/terrain/tree_foliage.kra
Normal file
BIN
work_files/graphics/terrain/tree_foliage.kra
Normal file
Binary file not shown.
Reference in New Issue
Block a user