Add noise visualizations to biome map

This commit is contained in:
Misode
2021-10-29 02:28:55 +02:00
parent c75c5b7d65
commit 3e4d8d7df5
10 changed files with 216 additions and 86 deletions

View File

@@ -1,7 +1,7 @@
import { DataModel } from '@mcschema/core'
import type { BiomeSource, Climate, NoiseOctaves } from 'deepslate'
import { FixedBiome, NoiseGeneratorSettings, NoiseSampler, NormalNoise, Random } from 'deepslate'
import init, { biome_parameters, climate_sampler, multi_noise } from 'deepslate-rs'
import type { NoiseOctaves } from 'deepslate'
import { FixedBiome, LegacyRandom, NormalNoise, TerrainShaper } from 'deepslate'
import init, { biome_parameters, climate_noise, climate_sampler, multi_noise } from 'deepslate-rs'
// @ts-expect-error
import wasm from 'deepslate-rs/deepslate_rs_bg.wasm?url'
import { fetchPreset } from '../DataFetcher'
@@ -16,7 +16,21 @@ async function loadWasm() {
console.debug(`Loaded deepslate-rs from "${wasm}"`)
}
type BiomeColors = Record<string, number[]>
const OverworldShaper = TerrainShaper.overworld()
const LAYERS = {
temperature: [-1, 1],
humidity: [-1, 1],
continentalness: [-1.1, 1],
erosion: [-1, 1],
weirdness: [-1, 1],
offset: [-1, 1],
factor: [0, 12],
jaggedness: [0, 1],
}
type Triple = [number, number, number]
type BiomeColors = Record<string, Triple>
type BiomeSourceOptions = {
octaves: NoiseOctaves,
biomeColors: BiomeColors,
@@ -25,18 +39,20 @@ type BiomeSourceOptions = {
res: number,
seed: bigint,
version: VersionId,
layers: Set<keyof typeof LAYERS | 'biomes'>,
}
interface CachedBiomeSource extends BiomeSource {
interface CachedBiomeSource {
getBiome(x: number, y: number, z: number): string
getBiomes?(xFrom: number, xTo: number, xStep: number, yFrom: number, yTo: number, yStep: number, zFrom: number, zTo: number, zStep: number): string[]
getClimate?(layers: Set<keyof typeof LAYERS>, xFrom: number, xTo: number, xStep: number, yFrom: number, yTo: number, yStep: number, zFrom: number, zTo: number, zStep: number): {[k: string]: number}[]
}
let cacheState: any
let biomeSourceCache: CachedBiomeSource
let climateSamplerCache: Climate.Sampler
export async function biomeMap(state: any, img: ImageData, options: BiomeSourceOptions) {
const { biomeSource, climateSampler } = await getCached(state, options)
const { biomeSource } = await getCached(state, options)
const data = img.data
const ox = -Math.round(options.offset[0]) - 100 + options.res / 2
@@ -44,19 +60,29 @@ export async function biomeMap(state: any, img: ImageData, options: BiomeSourceO
const row = img.width * 4 / options.res
const col = 4 / options.res
const biomes = biomeSource.getBiomes?.(
ox * options.scale, (200 + ox) * options.scale, options.res * options.scale,
64, 65, 1,
oz * options.scale, (200 + oz) * options.scale, options.res * options.scale,
)
const xRange: Triple = [ox * options.scale, (200 + ox) * options.scale, options.res * options.scale]
const zRange: Triple = [oz * options.scale, (200 + oz) * options.scale, options.res * options.scale]
const biomes = !options.layers.has('biomes') ? undefined : biomeSource.getBiomes?.(...xRange, 64, 65, 1, ...zRange)
const layers = [...options.layers].filter(l => l !== 'biomes') as (keyof typeof LAYERS)[]
const noise = layers.length === 0 ? undefined : biomeSource.getClimate?.(new Set(layers), ...xRange, 64, 65, 1, ...zRange)
for (let x = 0; x < 200; x += options.res) {
for (let z = 0; z < 200; z += options.res) {
const i = z * row + x * col
const j = (x / options.res) * 200 / options.res + z / options.res
const worldX = (x + ox) * options.scale
const worldZ = (z + oz) * options.scale
const b = biomes?.[(x / options.res) * 200 / options.res + z / options.res] ?? biomeSource.getBiome(worldX, 64, worldZ, climateSampler)
const color = getBiomeColor(b, options.biomeColors)
let color: Triple = [50, 50, 50]
if (options.layers.has('biomes')) {
const biome = biomes?.[j] ?? biomeSource.getBiome(worldX, 64, worldZ)
color = getBiomeColor(biome, options.biomeColors)
} else if (noise && layers[0]) {
const value = noise[j][layers[0]]
const [min, max] = LAYERS[layers[0]]
const brightness = (value - min) / (max - min) * 256
color = [brightness, brightness, brightness]
}
data[i] = color[0]
data[i + 1] = color[1]
data[i + 2] = color[2]
@@ -66,26 +92,21 @@ export async function biomeMap(state: any, img: ImageData, options: BiomeSourceO
}
export async function getBiome(state: any, x: number, z: number, options: BiomeSourceOptions): Promise<string | undefined> {
const { biomeSource, climateSampler } = await getCached(state, options)
const { biomeSource } = await getCached(state, options)
const [xx, zz] = toWorld([x, z], options)
return biomeSource.getBiome(xx, 64, zz, climateSampler)
return biomeSource.getBiome(xx, 64, zz)
}
async function getCached(state: any, options: BiomeSourceOptions): Promise<{ biomeSource: CachedBiomeSource, climateSampler: Climate.Sampler }> {
async function getCached(state: any, options: BiomeSourceOptions): Promise<{ biomeSource: CachedBiomeSource}> {
const newState = [state, options.octaves, `${options.seed}`, options.version]
if (!deepEqual(newState, cacheState)) {
cacheState = deepClone(newState)
biomeSourceCache = await getBiomeSource(state, options)
const settings = NoiseGeneratorSettings.fromJson({ octaves: options.octaves })
const noiseSampler = new NoiseSampler(4, 4, 32, biomeSourceCache, settings.noise, options.octaves, options.seed)
climateSamplerCache = noiseSampler.getClimate.bind(noiseSampler)
}
return {
biomeSource: biomeSourceCache,
climateSampler: climateSamplerCache,
}
}
@@ -149,13 +170,32 @@ async function getBiomeSource(state: any, options: BiomeSourceOptions): Promise<
const ids = multi_noise(parameters, sampler, xFrom, xTo, xStep, yFrom, yTo, yStep, zFrom, zTo, zStep)
return [...ids].map(id => BiomeIds.getA(id) ?? 'unknown')
},
getClimate(layers, xFrom, xTo, xStep, yFrom, yTo, yStep, zFrom, zTo, zStep) {
const climate = climate_noise(sampler, xFrom, xTo, xStep, yFrom, yTo, yStep, zFrom, zTo, zStep)
const result = []
for (let i = 0; i < climate.length; i += 7) {
const [t, h, c, e, w] = climate.slice(i, i + 5)
const point = TerrainShaper.point(c, e, w)
result.push({
temperature: t,
humidity: h,
continentalness: c,
erosion: e,
weirdness: w,
...layers.has('offset') && { offset: OverworldShaper.offset(point) },
...layers.has('factor') && { factor: OverworldShaper.factor(point) },
...layers.has('jaggedness') && { jaggedness: OverworldShaper.jaggedness(point) },
})
}
return result
},
}
} else {
const noise = ['altitude', 'temperature', 'humidity', 'weirdness']
.map((id, i) => {
const config = state[`${id}_noise`]
config.firstOctave = clamp(config.firstOctave ?? -7, -100, -1)
return new NormalNoise(new Random(options.seed + BigInt(i)), config)
return new NormalNoise(new LegacyRandom(options.seed + BigInt(i)), config)
})
if (!Array.isArray(state.biomes) || state.biomes.length === 0) {
return new FixedBiome('unknown')
@@ -180,7 +220,7 @@ async function getBiomeSource(state: any, options: BiomeSourceOptions): Promise<
throw new Error('Unknown biome source')
}
function getBiomeColor(biome: string, biomeColors: BiomeColors) {
function getBiomeColor(biome: string, biomeColors: BiomeColors): Triple {
if (!biome) {
return [128, 128, 128]
}
@@ -197,7 +237,7 @@ function toWorld([x, z]: [number, number], options: BiomeSourceOptions) {
return [xx, zz]
}
const VanillaColors: Record<string, [number, number, number]> = {
const VanillaColors: Record<string, Triple> = {
'minecraft:badlands': [217,69,21],
'minecraft:badlands_plateau': [202,140,101],
'minecraft:bamboo_jungle': [118,142,20],