mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
biome in worldgen
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
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