diff --git a/src/net/torvald/terrarum/concurrent/ThreadParallel.kt b/src/net/torvald/terrarum/concurrent/ThreadParallel.kt index 10710a004..42e980390 100644 --- a/src/net/torvald/terrarum/concurrent/ThreadParallel.kt +++ b/src/net/torvald/terrarum/concurrent/ThreadParallel.kt @@ -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) } diff --git a/src/net/torvald/terrarum/modulebasegame/console/ExportMap.kt b/src/net/torvald/terrarum/modulebasegame/console/ExportMap.kt index 6bccc3f53..f4f159c3a 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/ExportMap.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/ExportMap.kt @@ -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() ) diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UIProxyNewRandomGame.kt b/src/net/torvald/terrarum/modulebasegame/ui/UIProxyNewRandomGame.kt index db32c1f90..49118bd0f 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UIProxyNewRandomGame.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UIProxyNewRandomGame.kt @@ -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 diff --git a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Biomegen.kt b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Biomegen.kt index e28f35e08..67d6c963f 100644 --- a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Biomegen.kt +++ b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Biomegen.kt @@ -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?> = 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, 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 { + //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( diff --git a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt index f0f045dcf..676523b76 100644 --- a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt +++ b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Terragen.kt @@ -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?> = 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, world: GameWorld) { + private fun draw(x: Int, y: Int, noiseValue: List, 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. diff --git a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Worldgen.kt b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Worldgen.kt index a1c0bc976..f3c2fb32a 100644 --- a/src/net/torvald/terrarum/modulebasegame/worldgenerator/Worldgen.kt +++ b/src/net/torvald/terrarum/modulebasegame/worldgenerator/Worldgen.kt @@ -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)) ) diff --git a/src/net/torvald/terrarum/tests/WorldgenNoiseSandbox.kt b/src/net/torvald/terrarum/tests/WorldgenNoiseSandbox.kt index 6b918a7c4..782b0dc82 100644 --- a/src/net/torvald/terrarum/tests/WorldgenNoiseSandbox.kt +++ b/src/net/torvald/terrarum/tests/WorldgenNoiseSandbox.kt @@ -115,10 +115,11 @@ class WorldgenNoiseSandbox : ApplicationAdapter() { } } - private val NOISE_MAKER = AccidentalCave + private val NOISE_MAKER = BiomeMaker private fun getNoiseGenerator(SEED: Long): List { - 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) { } internal interface NoiseMaker { - fun draw(x: Int, y: Int, noiseValue: List, outTex: UnsafePtr) + fun draw(x: Int, y: Int, noiseValue: List, outTex: Pixmap) fun getGenerator(seed: Long, params: Any): List } @@ -269,12 +270,12 @@ val locklock = java.lang.Object() internal object BiomeMaker : NoiseMaker { - override fun draw(x: Int, y: Int, noiseValue: List, outTex: UnsafePtr) { + override fun draw(x: Int, y: Int, noiseValue: List, 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 { diff --git a/src/net/torvald/util/CircularArray.kt b/src/net/torvald/util/CircularArray.kt index fd1078c71..6c6f0add6 100644 --- a/src/net/torvald/util/CircularArray.kt +++ b/src/net/torvald/util/CircularArray.kt @@ -84,6 +84,7 @@ class CircularArray(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(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(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(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 diff --git a/work_files/graphics/terrain/tree_foliage.kra b/work_files/graphics/terrain/tree_foliage.kra new file mode 100644 index 000000000..e3fb0ce25 Binary files /dev/null and b/work_files/graphics/terrain/tree_foliage.kra differ