Files
Terrarum/src/net/torvald/terrarum/tests/WorldgenNoiseSandbox.kt
2024-09-08 01:51:40 +09:00

1012 lines
37 KiB
Kotlin

package net.torvald.terrarum.tests
import com.badlogic.gdx.ApplicationAdapter
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.OrthographicCamera
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.BitmapFont
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.glutils.ShaderProgram
import com.jme3.math.FastMath
import com.sudoplay.joise.Joise
import com.sudoplay.joise.ModuleInstanceMap
import com.sudoplay.joise.ModuleMap
import com.sudoplay.joise.ModulePropertyMap
import com.sudoplay.joise.module.*
import net.torvald.random.HQRNG
import net.torvald.terrarum.FlippingSpriteBatch
import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.concurrent.ThreadExecutor
import net.torvald.terrarum.inUse
import net.torvald.terrarum.modulebasegame.worldgenerator.BiomegenParams
import net.torvald.terrarum.modulebasegame.worldgenerator.TerragenParams
import net.torvald.terrarum.modulebasegame.worldgenerator.TerragenParamsAlpha2
import net.torvald.terrarum.modulebasegame.worldgenerator.Worldgen.YHEIGHT_DIVISOR
import net.torvald.terrarum.modulebasegame.worldgenerator.Worldgen.YHEIGHT_MAGIC
import net.torvald.terrarum.modulebasegame.worldgenerator.shake
import net.torvald.terrarum.sqr
import net.torvald.terrarum.worlddrawer.toRGBA
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap
import java.io.PrintStream
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.Future
import kotlin.math.*
import kotlin.random.Random
const val NOISEBOX_WIDTH = 90 * 18
const val NOISEBOX_HEIGHT = 90 * 26
const val TWO_PI = Math.PI * 2
const val WORLDGEN_YOFF = 5400 - NOISEBOX_HEIGHT
/**
* Created by minjaesong on 2019-07-23.
*/
class WorldgenNoiseSandbox : ApplicationAdapter() {
private val threadExecutor = ThreadExecutor()
private lateinit var batch: SpriteBatch
private lateinit var camera: OrthographicCamera
private lateinit var font: BitmapFont
private lateinit var testTex: Pixmap
private lateinit var tempTex: Texture
private val RNG = HQRNG()
private var seed = 373231L // old default seed: 10000L
private var initialGenDone = false
private var generationStartTime = 0L
private var genSlices: Int = 0
private var genFutures: Array<Future<*>?> = arrayOfNulls(genSlices)
override fun create() {
font = TerrarumSansBitmap("assets/graphics/fonts/terrarum-sans-bitmap")
batch = FlippingSpriteBatch(1000)
camera = OrthographicCamera(NOISEBOX_WIDTH.toFloat(), NOISEBOX_HEIGHT.toFloat())
camera.setToOrtho(true) // some elements are pre-flipped, while some are not. The statement itself is absolutely necessary to make edge of the screen as the origin
camera.update()
batch.projectionMatrix = camera.combined
testTex = Pixmap(NOISEBOX_WIDTH, NOISEBOX_HEIGHT, Pixmap.Format.RGBA8888)
testTex.blending = Pixmap.Blending.None
tempTex = Texture(1, 1, Pixmap.Format.RGBA8888)
genSlices = max(threadExecutor.threadCount, testTex.width / 8)
println("Init done")
}
private var generationTime = 0f
private var today = ""
private val NM_TERR = TerragenTest(TerragenParamsAlpha2())
private val NM_BIOME = BiomeMaker to BiomegenParams()
private val NOISEMAKER = NM_TERR
override fun render() {
if (!initialGenDone) {
renderNoise(NOISEMAKER)
initialGenDone = true
}
// draw using pixmap
batch.inUse {
tempTex.dispose()
tempTex = Texture(testTex)
batch.draw(tempTex, 0f, 0f, NOISEBOX_WIDTH.toFloat(), NOISEBOX_HEIGHT.toFloat())
}
// read key input
if (Gdx.input.isKeyPressed(Input.Keys.SPACE) && worldgenDone) {
seed = RNG.nextLong()
renderNoise(NOISEMAKER)
}
// draw timer
batch.inUse {
if (worldgenDone) {
font.draw(batch, "Generation time: ${generationTime} seconds Time: $today", 8f, 8f)
}
else {
font.draw(batch, "Generating...", 8f, 8f)
}
font.draw(batch, "Seed: $seed", 8f, 8f + 1*20)
font.draw(batch, "caveAttenuateScale=${NM_TERR.params.caveAttenuateScale}", 8f, 8f + 2*20)
font.draw(batch, "caveAttenuateScale1=${NM_TERR.params.caveAttenuateScale1}", 8f, 8f + 3*20)
font.draw(batch, "caveAttenuateBias=${NM_TERR.params.caveAttenuateBias}", 8f, 8f + 4*20)
font.draw(batch, "caveAttenuateBias1=${NM_TERR.params.caveAttenuateBias1}", 8f, 8f +5*20)
font.draw(batch, "caveSelectThre=${NM_TERR.params.caveSelectThre}", 8f, 8f + 6*20)
}
}
val colourNull = Color(0x1b3281ff)
private val sampleOffset = NOISEBOX_WIDTH / 8.0
private val testColSet = arrayOf(
Color(0xff0000ff.toInt()),
Color(0xffff00ff.toInt()),
Color(0x00ff00ff.toInt()),
Color(0x00ffffff.toInt()),
Color(0x0000ffff.toInt()),
Color(0xff00ffff.toInt()),
Color(0xffffffff.toInt()),
Color(0xff)
)
private val testColSet2 = arrayOf(
0xff0000ff.toInt(),
0xffff00ff.toInt(),
0x00ff00ff.toInt(),
0x00ffffff.toInt(),
0x0000ffff.toInt(),
0xff00ffff.toInt(),
0xffffffff.toInt(),
0xff
)
fun <T> Array<T>.shuffle() {
val rng = Random(System.nanoTime())
for (k in this.size - 1 downTo 1) {
val r = rng.nextInt(k + 1)
val t = this[r]
this[r] = this[k]
this[k] = t
}
}
private fun printStackTrace(obj: Any, out: PrintStream = System.out) {
val indentation = " ".repeat(obj.javaClass.simpleName.length + 4)
Thread.currentThread().stackTrace.forEachIndexed { index, it ->
if (index == 1)
out.println("[${obj.javaClass.simpleName}]> $it")
else if (index > 1)
out.println("$indentation$it")
}
}
fun getClampedHeight(): Int {
return 3200
}
private fun Int.addSY(): Int {
val offset = 90 * 8
return this + offset
}
private fun Int.subtractSY(): Int {
val offset = 90 * 8
return this - offset
}
private fun getSY(y: Int): Double = y - (getClampedHeight() - YHEIGHT_MAGIC) * YHEIGHT_DIVISOR // Q&D offsetting to make ratio of sky:ground to be constant
private fun renderNoise(noiseMaker: NoiseMaker, callback: () -> Unit = {}) {
generationStartTime = System.nanoTime()
// erase first
testTex.setColor(colourNull)
testTex.fill()
testColSet.shuffle()
testColSet2.shuffle()
worldgenDone = false
Thread {
val callables = ArrayList<Callable<Unit>>()
for (cy in 0 until NOISEBOX_HEIGHT / 90) { for (cx in 0 until NOISEBOX_WIDTH / 90) {
val localJoise = noiseMaker.getGenerator(seed, 0)
callables.add(Callable { for (x in cx * 90 until (cx + 1) * 90) {
for (y in cy * 90 until (cy + 1) * 90) {
val sampleTheta = (x.toDouble() / NOISEBOX_WIDTH) * TWO_PI
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 = getSY(y) + WORLDGEN_YOFF
noiseMaker.draw(x, y, localJoise.mapIndexed { index, joise ->
joise.get(sampleX, sampleY, sampleZ)
}, testTex)
}
} })
} }
threadExecutor.renew()
callables.shuffle()
threadExecutor.submitAll(callables)
threadExecutor.join()
worldgenDone = true
val timeNow = System.nanoTime()
val time = timeNow - generationStartTime
generationTime = time / 1000000000f
Calendar.getInstance().apply {
today =
"${get(Calendar.YEAR)}-" +
"${get(Calendar.MONTH).plus(1).toString().padStart(2,'0')}-" +
"${get(Calendar.DAY_OF_MONTH).toString().padStart(2,'0')}T" +
"${get(Calendar.HOUR_OF_DAY).toString().padStart(2,'0')}:" +
"${get(Calendar.MINUTE).toString().padStart(2,'0')}:" +
"${get(Calendar.SECOND).toString().padStart(2,'0')}"
}
callback()
}.start()
}
var worldgenDone = true; private set
override fun dispose() {
testTex.dispose()
tempTex.dispose()
}
}
fun main(args: Array<String>) {
ShaderProgram.pedantic = false
val appConfig = Lwjgl3ApplicationConfiguration()
appConfig.useVsync(false)
appConfig.setResizable(false)
appConfig.setWindowedMode(NOISEBOX_WIDTH, NOISEBOX_HEIGHT)
appConfig.setForegroundFPS(60)
appConfig.setOpenGLEmulation(Lwjgl3ApplicationConfiguration.GLEmulation.GL30, 3, 2)
Lwjgl3Application(WorldgenNoiseSandbox(), appConfig)
}
interface NoiseMaker {
fun draw(x: Int, y: Int, noiseValue: List<Double>, outTex: Pixmap)
fun getGenerator(seed: Long, params: Any): List<Joise>
}
val locklock = java.lang.Object()
internal object BiomeMaker : NoiseMaker {
override fun draw(x: Int, y: Int, noiseValue: List<Double>, outTex: Pixmap) {
val colPal = biomeColors
val control = noiseValue[0].coerceIn(0.0, 0.99999).times(colPal.size).toInt().coerceIn(colPal.indices)
outTex.setColor(colPal[control])
outTex.drawPixel(x, y)
}
override fun getGenerator(seed: Long, params: Any): List<Joise> {
val params = params as BiomegenParams
//val biome = ModuleBasisFunction()
//biome.setType(ModuleBasisFunction.BasisType.SIMPLEX)
// simplex AND fractal for more noisy edges, mmmm..!
val fractal = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.MULTI)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.SIMPLEX)
it.setNumOctaves(4)
it.setFrequency(1.0)
it.seed = seed shake 0x7E22A
}
val scaleDomain = ModuleScaleDomain().also {
it.setSource(fractal)
it.setScaleX(1.0 / params.featureSize1) // adjust this value to change features size
it.setScaleY(1.0 / params.featureSize1)
it.setScaleZ(1.0 / params.featureSize1)
}
val scale = ModuleScaleOffset().also {
it.setSource(scaleDomain)
it.setOffset(1.0)
it.setScale(1.0)
}
val last = scale
return listOf(Joise(last))
}
// with this method, only TWO distinct (not bland) biomes are possible. CLUT order is important here.
val biomeColors = intArrayOf(
//0x2288ccff.toInt(), // ísland
0x229944ff.toInt(), // woodlands
0x77bb77ff.toInt(), // shrubland
0xbbdd99ff.toInt(), // plains
0xbbdd99ff.toInt(), // plains
// 0xeeddbbff.toInt(), // sands
0x888888ff.toInt() // rockyland
)
}
// http://accidentalnoise.sourceforge.net/minecraftworlds.html
internal class TerragenTest(val params: TerragenParams) : NoiseMaker {
private infix fun Color.mul(other: Color) = this.mul(other)
private val notationColours = arrayOf(
Color.WHITE,
Color.MAGENTA,
Color(0f, 186f/255f, 1f, 1f),
Color(.5f, 1f, .5f, 1f),
Color(1f, 0.93f, 0.07f, 1f),
Color(0.97f, 0.6f, 0.56f, 1f)
)
private val groundDepthBlockWall = listOf(
Block.AIR, Block.DIRT, Block.STONE, Block.STONE_SLATE, Block.STONE_SLATE
)
private val groundDepthBlockTERR = ArrayList(groundDepthBlockWall).also {
it[it.lastIndex] = Block.AIR
}
private fun Double.tiered(tiers: List<Double>): Int {
tiers.reversed().forEachIndexed { index, it ->
if (this >= it) return (tiers.lastIndex - index) // why??
}
return tiers.lastIndex
}
private val BACK = Color(0.6f, 0.66f, 0.78f, 1f).toRGBA()
private val blockToCol = hashMapOf(
Block.AIR to Color(0f, 0f, 0f, 1f),
Block.DIRT to Color(0.588f, 0.45f, 0.3f, 1f),
Block.STONE to Color(0.4f, 0.4f, 0.4f, 1f),
Block.STONE_SLATE to Color(0.2f, 0.2f, 0.2f, 1f),
Block.STONE_MARBLE to Color(0.8f, 0.8f, 0.8f, 1f)
)
private val COPPER_ORE = 0x00e9c8ff.toInt()
private val IRON_ORE = 0xff7e74ff.toInt()
private val COAL_ORE = 0x383314ff.toInt()
private val ZINC_ORE = 0xefde76ff.toInt()
private val TIN_ORE = 0xcd8b62ff.toInt()
private val GOLD_ORE = 0xffcc00ff.toInt()
private val SILVER_ORE = 0xd5d9f9ff.toInt()
private val LEAD_ORE = 0xff9300ff.toInt()
private val QUARTZ = 0x55ff33ff.toInt()
private val AMETHYST = 0xee77ffff.toInt()
private val ROCKSALT = 0xff00ffff.toInt()
private val NITRE = 0xdbd6a1ff.toInt()
private val LAVA = 0xff5900ff.toInt()
private val oreCols = listOf(
COPPER_ORE, IRON_ORE, COAL_ORE, ZINC_ORE, TIN_ORE, GOLD_ORE, SILVER_ORE, LEAD_ORE, ROCKSALT, QUARTZ, AMETHYST, NITRE
)
private val terragenYscaling = (NOISEBOX_HEIGHT / 2400.0).pow(0.75)
private val terragenTiers = (params.terragenTiers).map { it * terragenYscaling } // pow 1.0 for 1-to-1 scaling; 0.75 is used to make deep-rock layers actually deep for huge world size
override fun draw(x: Int, y: Int, noiseValue: List<Double>, outTex: Pixmap) {
val terr = noiseValue[0].tiered(terragenTiers)
val cave = if (noiseValue[1] < 0.5) 0 else 1
val ore = (noiseValue.subList(2, noiseValue.size - 1)).zip(oreCols).firstNotNullOfOrNull { (n, colour) -> if (n > 0.5) colour else null }
val isMarble = false // noiseValue[13] > 0.5
val wallBlock = if (isMarble) Block.STONE_MARBLE else groundDepthBlockWall[terr]
val terrBlock = if (cave == 0) Block.AIR else if (isMarble) Block.STONE_MARBLE else groundDepthBlockTERR[terr]
val lavaVal = noiseValue.last()
val lava = (lavaVal >= 0.5)
outTex.drawPixel(x, y,
if (lava) LAVA
else if (ore != null && (terrBlock == Block.STONE || terrBlock == Block.STONE_SLATE)) ore
else if (wallBlock == Block.AIR && terrBlock == Block.AIR) BACK
else blockToCol[terrBlock]!!.toRGBA()
)
// outTex.drawPixel(x, y, noiseValue[2].toColour())
}
private fun Double.toColour(): Int {
val d = if (this.isNaN()) 0.0 else this.absoluteValue
val b = d.toFloat()
val c = if (b >= 2f)
// 1 0 1 S
// 0 1 1 E
Color(
FastMath.interpolateLinear(b - 2f, 1f, 0f),
FastMath.interpolateLinear(b - 2f, 0f, 1f),
1f, 1f
)
else if (b >= 1f)
Color(1f, 1f - (b - 1f), 1f, 1f)
else if (b >= 0f)
Color(b, b, b, 1f)
else
Color(b, 0f, 0f, 1f)
return c.toRGBA()
}
override fun getGenerator(seed: Long, wtf: Any): List<Joise> {
val lowlandMagic: Long = 0x41A21A114DBE56 // Maria Lindberg
val highlandMagic: Long = 0x0114E091 // Olive Oyl
val mountainMagic: Long = 0x115AA4DE2504 // Lisa Anderson
val selectionMagic: Long = 0x41E10D9B100 // Melody Blue
val caveMagic: Long = 0x00215741CDF // Urist McDF
val cavePerturbMagic: Long = 0xA2410C // Armok
val caveBlockageMagic: Long = 0xD15A57E5 // Disaster
val oreMagic = 0x023L
val orePerturbMagic = 12345L
val groundGradient = ModuleGradient().also {
it.setGradient(0.0, 0.0, 0.0, 1.0)
}
/* lowlands */
val lowlandShapeFractal = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.BILLOW)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(2)
it.setFrequency(0.25)
it.seed = seed shake lowlandMagic
}
val lowlandScale = ModuleScaleOffset().also {
it.setSource(lowlandShapeFractal)
it.setScale(0.22)
it.setOffset(params.lowlandScaleOffset) // linearly alters the height
}
val lowlandYScale = ModuleScaleDomain().also {
it.setSource(lowlandScale)
it.setScaleY(0.02) // greater = more distortion, overhangs
}
val lowlandTerrain = ModuleTranslateDomain().also {
it.setSource(groundGradient)
it.setAxisYSource(lowlandYScale)
}
/* highlands */
val highlandShapeFractal = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.FBM)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(4)
it.setFrequency(2.0)
it.seed = seed shake highlandMagic
}
val highlandScale = ModuleScaleOffset().also {
it.setSource(highlandShapeFractal)
it.setScale(0.5)
it.setOffset(params.highlandScaleOffset) // linearly alters the height
}
val highlandYScale = ModuleScaleDomain().also {
it.setSource(highlandScale)
it.setScaleY(0.14) // greater = more distortion, overhangs
}
val highlandTerrain = ModuleTranslateDomain().also {
it.setSource(groundGradient)
it.setAxisYSource(highlandYScale)
}
/* mountains */
val mountainShapeFractal = ModuleFractal().also {
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(8)
it.setFrequency(1.0)
it.seed = seed shake mountainMagic
}
val mountainScale = ModuleScaleOffset().also {
it.setSource(mountainShapeFractal)
it.setScale(1.0)
it.setOffset(params.mountainScaleOffset) // linearly alters the height
}
val mountainYScale = ModuleScaleDomain().also {
it.setSource(mountainScale)
it.setScaleY(params.mountainDisturbance) // greater = more distortion, overhangs
}
val mountainTerrain = ModuleTranslateDomain().also {
it.setSource(groundGradient)
it.setAxisYSource(mountainYScale)
}
/* selection */
val terrainTypeFractal = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.FBM)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(3)
it.setFrequency(0.125)
it.seed = seed shake selectionMagic
}
val terrainScaleOffset = ModuleScaleOffset().also {
it.setSource(terrainTypeFractal)
it.setOffset(0.5)
it.setScale(0.666666) // greater = more dynamic terrain
}
val terrainTypeYScale = ModuleScaleDomain().also {
it.setSource(terrainScaleOffset)
it.setScaleY(0.0)
}
val terrainTypeCache = ModuleCache().also {
it.setSource(terrainTypeYScale)
}
val highlandMountainSelect = ModuleSelect().also {
it.setLowSource(highlandTerrain)
it.setHighSource(mountainTerrain)
it.setControlSource(terrainTypeCache)
it.setThreshold(0.55)
it.setFalloff(0.2)
}
val highlandLowlandSelect = ModuleSelect().also {
it.setLowSource(lowlandTerrain)
it.setHighSource(highlandMountainSelect)
it.setControlSource(terrainTypeCache)
it.setThreshold(0.25)
it.setFalloff(0.15)
}
val highlandLowlandSelectCache = ModuleCache().also {
it.setSource(highlandLowlandSelect)
}
val groundSelect = ModuleSelect().also {
it.setLowSource(0.0)
it.setHighSource(1.0)
it.setThreshold(0.5)
it.setControlSource(highlandLowlandSelectCache)
}
val groundSelect2 = ModuleSelect().also {
it.setLowSource(0.0)
it.setHighSource(1.0)
it.setThreshold(0.8)
it.setControlSource(highlandLowlandSelectCache)
}
/* caves */
val caveShape = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.RIDGEMULTI)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(1)
it.setFrequency(params.caveShapeFreq) // adjust the "density" of the caves
it.seed = seed shake caveMagic
}
val caveAttenuateBias0 = ModuleCache().also { it.setSource(ModuleBias().also {
it.setSource(highlandLowlandSelectCache)
it.setBias(params.caveAttenuateBias) // (0.5+) adjust the "concentration" of the cave gen. Lower = larger voids
})}
val caveAttenuateBias1 = ModuleCache().also { it.setSource(ModuleBias().also {
it.setSource(highlandLowlandSelectCache)
it.setBias(params.caveAttenuateBias1) // (0.5+) adjust the "concentration" of the cave gen. Lower = larger voids
})}
val caveAttenuateBiasForTerr = ModuleScaleOffset().also {
it.setSource(caveAttenuateBias0)
it.setScale(params.caveAttenuateScale)
}
val caveAttenuateBiasForOres = ModuleScaleOffset().also {
it.setSource(caveAttenuateBias1)
it.setScale(params.caveAttenuateScale1)
}
val caveShapeAttenuate = ModuleCombiner().also {
it.setType(ModuleCombiner.CombinerType.MULT)
it.setSource(0, caveShape)
it.setSource(1, caveAttenuateBiasForTerr)
}
val cavePerturbFractal = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.FBM)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(6)
it.setFrequency(params.caveShapeFreq * 3.0 / 4.0)
it.seed = seed shake cavePerturbMagic
}
val cavePerturbScale = ModuleScaleOffset().also {
it.setSource(cavePerturbFractal)
it.setScale(0.45)
it.setOffset(0.0)
}
val cavePerturb0 = ModuleTranslateDomain().also {
it.setSource(caveShapeAttenuate)
it.setAxisXSource(cavePerturbScale)
}
val caveTerminalClosureGrad = TerrarumModuleCaveLayerClosureGrad()
val cavePerturb = ModuleCombiner().also { // 0: rock, 1: air
it.setType(ModuleCombiner.CombinerType.MULT)
it.setSource(0, cavePerturb0)
it.setSource(1, caveTerminalClosureGrad)
}
val caveSelect = ModuleSelect().also {
it.setLowSource(1.0)
it.setHighSource(0.0)
it.setControlSource(cavePerturb)
it.setThreshold(params.caveSelectThre) // also adjust this if you've touched the bias value. Number can be greater than 1.0
it.setFalloff(0.0)
}
val caveBlockageFractal0 = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.RIDGEMULTI)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(2)
it.setFrequency(params.caveBlockageFractalFreq) // same as caveShape frequency?
it.seed = seed shake caveBlockageMagic
}
val caveBlockageFractal = ModuleCombiner().also { // 0: air, 1: rock
it.setType(ModuleCombiner.CombinerType.MULT)
it.setSource(0, caveBlockageFractal0)
}
// will only close-up deeper caves. Shallow caves will be less likely to be closed up
val caveBlockageAttenuate = ModuleCombiner().also {
it.setType(ModuleCombiner.CombinerType.MULT)
it.setSource(0, caveBlockageFractal)
it.setSource(1, caveAttenuateBiasForTerr)
}
val caveBlockageSelect = ModuleSelect().also {
it.setLowSource(0.0)
it.setHighSource(1.0)
it.setControlSource(caveBlockageAttenuate)
it.setThreshold(params.caveBlockageSelectThre) // adjust cave cloing-up strength. Larger = more closing
it.setFalloff(0.0)
}
// note: gradient-multiply DOESN'T generate "naturally cramped" cave entrance
val caveInMix = ModuleCombiner().also {
it.setType(ModuleCombiner.CombinerType.ADD)
it.setSource(0, caveSelect)
it.setSource(1, caveBlockageSelect)
}
// 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.
val groundClamp = ModuleClamp().also {
it.setRange(0.0, 100.0)
it.setSource(highlandLowlandSelectCache)
}
val groundScaling = ModuleScaleDomain().also {
it.setScaleX(1.0 / params.featureSize) // adjust this value to change features size
it.setScaleY(1.0 / params.featureSize)
it.setScaleZ(1.0 / params.featureSize)
it.setSource(groundClamp)
}
val caveClamp = ModuleClamp().also {
it.setRange(0.0, 1.0)
it.setSource(caveInMix)
}
val caveScaling = ModuleScaleDomain().also {
it.setScaleX(1.0 / params.featureSize) // adjust this value to change features size
it.setScaleY(1.0 / params.featureSize)
it.setScaleZ(1.0 / params.featureSize)
it.setSource(caveClamp)
}
val caveAttenuateBiasScaled = ModuleScaleDomain().also {
it.setScaleX(1.0 / params.featureSize) // adjust this value to change features size
it.setScaleY(1.0 / params.featureSize)
it.setScaleZ(1.0 / params.featureSize)
it.setSource(caveAttenuateBiasForOres)
}
val groundScalingCached = ModuleCache().also { it.setSource(groundScaling) }
val caveAttenuateBiasScaledCache = ModuleCache().also { it.setSource(caveAttenuateBiasScaled) }
val thicknesses = listOf(0.016, 0.021, 0.029, 0.036, 0.036, 0.029, 0.021, 0.016)
val marblerng = HQRNG(seed)
//return Joise(caveInMix)
return listOf(
Joise(groundScalingCached),
Joise(caveScaling),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:1", 0.026, 0.010, 0.517, 1.0)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:2", 0.031, 0.011, 0.521, 1.0)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:3", 0.017, 0.070, 0.511, 3.8)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:4", 0.019, 0.011, 0.511, 1.0)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:5", 0.017, 0.017, 0.511, 1.0)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:6", 0.009, 0.300, 0.474, 1.0)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:7", 0.013, 0.300, 0.476, 1.0)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:8", 0.017, 0.020, 0.511, 1.0)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:256", 0.010, -0.366, 0.528, 2.4)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:257", 0.007, 0.100, 0.494, 1.0)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:258", 0.019, 0.015, 0.509, 1.0)),
Joise(generateOreVeinModule(caveAttenuateBiasScaledCache, seed shake "ores@basegame:259", 0.010, -0.166, 0.517, 1.4)),
Joise(generateRockLayer(groundScalingCached, seed, params, (0..7).map {
thicknesses[it] + marblerng.nextTriangularBal() * 0.006 to (2.6 * terragenYscaling) + it * 0.18 + marblerng.nextTriangularBal() * 0.09
})),
Joise(generateSeaOfLava(seed)),
)
}
private fun generateRockLayer(ground: ModuleCache, seed: Long, params: TerragenParams, thicknessAndRange: List<Pair<Double, Double>>): Module {
val occlusion = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.RIDGEMULTI)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.SIMPLEX)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(2)
it.setFrequency(params.rockBandCutoffFreq / params.featureSize) // adjust the "density" of the veins
it.seed = seed shake 0x41A2B1E5
}
val occlusionScale = ModuleScaleDomain().also {
it.setScaleX(0.5)
it.setScaleZ(0.5)
it.setSource(occlusion)
}
val occlusionBinary = ModuleSelect().also {
it.setLowSource(0.0)
it.setHighSource(1.0)
it.setControlSource(occlusionScale)
it.setThreshold(1.1)
it.setFalloff(0.0)
}
val occlusionCache = ModuleCache().also {
it.setSource(occlusionBinary)
}
val bands = thicknessAndRange.map { (thickness, rangeStart) ->
val thresholdLow = ModuleSelect().also {
it.setLowSource(0.0)
it.setHighSource(1.0)
it.setControlSource(ground)
it.setThreshold(rangeStart)
it.setFalloff(0.0)
}
val thresholdHigh = ModuleSelect().also {
it.setLowSource(1.0)
it.setHighSource(0.0)
it.setControlSource(ground)
it.setThreshold(rangeStart + thickness)
it.setFalloff(0.0)
}
ModuleCombiner().also {
it.setSource(0, thresholdLow)
it.setSource(1, thresholdHigh)
it.setSource(2, occlusionCache)
it.setType(ModuleCombiner.CombinerType.MULT)
}
}
val combinedBands = ModuleCombiner().also {
bands.forEachIndexed { index, module ->
it.setSource(index, module)
}
it.setType(ModuleCombiner.CombinerType.ADD)
}
return combinedBands
}
private fun applyPowMult(joiseModule: Module, pow: Double, mult: Double): Module {
return ModuleScaleOffset().also {
it.setSource(ModulePow().also {
it.setSource(joiseModule)
it.setPower(pow)
})
it.setScale(mult)
}
}
private fun generateOreVeinModule(caveAttenuateBiasScaledCache: ModuleCache, seed: Long, freq: Double, pow: Double, scale: Double, ratio: Double): Module {
val oreShape = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.RIDGEMULTI)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.SIMPLEX)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(2)
it.setFrequency(freq) // adjust the "density" of the caves
it.seed = seed
}
val oreShape2 = ModuleScaleOffset().also {
it.setSource(oreShape)
it.setScale(1.0)
it.setOffset(-0.5)
}
val caveAttenuateBias3 = applyPowMult(caveAttenuateBiasScaledCache, pow, scale)
val oreShapeAttenuate = ModuleCombiner().also {
it.setType(ModuleCombiner.CombinerType.MULT)
it.setSource(0, oreShape2)
it.setSource(1, caveAttenuateBias3)
}
val orePerturbFractal = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.FBM)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.SIMPLEX)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(6)
it.setFrequency(freq * 3.0 / 4.0)
it.seed = seed shake 0x5721CE_76E_EA276L // strike the earth
}
val orePerturbScale = ModuleScaleOffset().also {
it.setSource(orePerturbFractal)
it.setScale(20.0)
it.setOffset(0.0)
}
val orePerturb = ModuleTranslateDomain().also {
it.setSource(oreShapeAttenuate)
it.setAxisXSource(orePerturbScale)
}
val oreStrecth = ModuleScaleDomain().also {
val xratio = if (ratio >= 1.0) ratio else 1.0
val yratio = if (ratio < 1.0) 1.0 / ratio else 1.0
val k = sqrt(2.0 / (xratio.sqr() + yratio.sqr()))
val xs = xratio * k
val ys = yratio * k
it.setSource(orePerturb)
it.setScaleX(1.0 / xs)
it.setScaleZ(1.0 / xs)
it.setScaleY(1.0 / ys)
}
val oreSelect = ModuleSelect().also {
it.setLowSource(0.0)
it.setHighSource(1.0)
it.setControlSource(oreStrecth)
it.setThreshold(0.5)
it.setFalloff(0.0)
}
return oreSelect
}
private fun generateSeaOfLava(seed: Long): Module {
val lavaPipe = ModuleScaleDomain().also {
it.setSource(ModuleFractal().also {
it.setType(ModuleFractal.FractalType.RIDGEMULTI)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(1)
it.setFrequency(params.lavaShapeFreg) // adjust the "density" of the caves
it.seed = seed shake "LattiaOnLavaa"
})
it.setScaleY(1.0 / 6.0)
}
val lavaPerturbFractal = ModuleFractal().also {
it.setType(ModuleFractal.FractalType.FBM)
it.setAllSourceBasisTypes(ModuleBasisFunction.BasisType.GRADIENT)
it.setAllSourceInterpolationTypes(ModuleBasisFunction.InterpolationType.QUINTIC)
it.setNumOctaves(6)
it.setFrequency(params.lavaShapeFreg * 3.0 / 4.0)
it.seed = seed shake "FloorIsLava"
}
val lavaPerturbScale = ModuleScaleOffset().also {
it.setSource(lavaPerturbFractal)
it.setScale(23.0)
it.setOffset(0.0)
}
val lavaPerturb = ModuleTranslateDomain().also {
it.setSource(lavaPipe)
it.setAxisXSource(lavaPerturbScale)
}
// val = sqrt((y-H+L) / L); where H=5300 (world height-100), L=620;
// 100 is the height of the "base lava sheet", 600 is the height of the "transitional layer"
// in this setup, the entire lava layer never exceeds 8 chunks (720 tiles) in height
val lavaGrad = TerrarumModuleLavaFloorGrad().also {
it.setH(5300.0)
it.setL(620.0)
}
val lavaSelect = ModuleSelect().also {
it.setLowSource(1.0)
it.setHighSource(0.0)
it.setControlSource(lavaPerturb)
it.setThreshold(lavaGrad)
it.setFalloff(0.0)
}
return lavaSelect
}
private object DummyModule : Module() {
override fun get(x: Double, y: Double) = 0.0
override fun get(x: Double, y: Double, z: Double) = 0.0
override fun get(x: Double, y: Double, z: Double, w: Double) = 0.0
override fun get(x: Double, y: Double, z: Double, w: Double, u: Double, v: Double) = 0.0
override fun _writeToMap(map: ModuleMap?) {
}
override fun buildFromPropertyMap(props: ModulePropertyMap?, map: ModuleInstanceMap?): Module {
return this
}
}
}
/*infix fun Long.shake(other: Long): Long {
var s0 = this
var s1 = other
s1 = s1 xor s0
s0 = s0 shl 55 or s0.ushr(9) xor s1 xor (s1 shl 14)
s1 = s1 shl 36 or s1.ushr(28)
return s0 + s1
}*/