Use wasm to compute multi noise biome map (#181)

This commit is contained in:
Misode
2021-10-28 18:47:19 +02:00
committed by GitHub
parent 89354a9743
commit c75c5b7d65
4 changed files with 143 additions and 29 deletions

45
package-lock.json generated
View File

@@ -16,6 +16,7 @@
"@mcschema/java-1.18": "^0.1.16",
"@mcschema/locales": "^0.1.35",
"deepslate": "^0.9.0-beta.2",
"deepslate-rs": "^0.1.4",
"howler": "^2.2.3",
"rfdc": "^1.3.0"
},
@@ -956,9 +957,9 @@
}
},
"node_modules/debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
@@ -987,6 +988,11 @@
"pako": "^2.0.3"
}
},
"node_modules/deepslate-rs": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deepslate-rs/-/deepslate-rs-0.1.4.tgz",
"integrity": "sha512-vaajhtPObmqsjMM5PYD5SLPqb91tmpHGKwdR1cdwYWorKGk9yI9tf906MRQAG4J8QCO4TuQKV/+55TUvX/Xq4w=="
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -1413,17 +1419,16 @@
"dev": true
},
"node_modules/fast-glob": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
"integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==",
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
"integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.0",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.2",
"picomatch": "^2.2.1"
"micromatch": "^4.0.4"
},
"engines": {
"node": ">=8"
@@ -3397,9 +3402,9 @@
}
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"dev": true,
"requires": {
"ms": "2.1.2"
@@ -3420,6 +3425,11 @@
"pako": "^2.0.3"
}
},
"deepslate-rs": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deepslate-rs/-/deepslate-rs-0.1.4.tgz",
"integrity": "sha512-vaajhtPObmqsjMM5PYD5SLPqb91tmpHGKwdR1cdwYWorKGk9yI9tf906MRQAG4J8QCO4TuQKV/+55TUvX/Xq4w=="
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -3734,17 +3744,16 @@
"dev": true
},
"fast-glob": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
"integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==",
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
"integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.0",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.2",
"picomatch": "^2.2.1"
"micromatch": "^4.0.4"
}
},
"fast-json-stable-stringify": {

View File

@@ -6,9 +6,10 @@
"main": "index.js",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "npm run wasmfixer && tsc && vite build",
"serve": "vite preview",
"lint": "eslint . --ext .ts,.tsx"
"lint": "eslint . --ext .ts,.tsx",
"wasmfixer": "sed -i \"s/{: number, |: number/aa: number, ab: number/\" ./node_modules/deepslate-rs/deepslate_rs.d.ts"
},
"keywords": [],
"author": "Misode",
@@ -21,6 +22,7 @@
"@mcschema/java-1.18": "^0.1.16",
"@mcschema/locales": "^0.1.35",
"deepslate": "^0.9.0-beta.2",
"deepslate-rs": "^0.1.4",
"howler": "^2.2.3",
"rfdc": "^1.3.0"
},

View File

@@ -140,3 +140,35 @@ export function deepEqual(a: any, b: any) {
}
return a !== a && b !== b
}
export class BiMap<A, B> {
private readonly forward: Map<A, B>
private readonly backward: Map<B, A>
constructor() {
this.forward = new Map()
this.backward = new Map()
}
public set(a: A, b: B) {
this.forward.set(a, b)
this.backward.set(b, a)
}
public getA(key: B) {
return this.backward.get(key)
}
public getB(key: A) {
return this.forward.get(key)
}
public getOrPut(key: A, defaultValue: B) {
const b = this.forward.get(key)
if (b === undefined) {
this.set(key, defaultValue)
return defaultValue
}
return b
}
}

View File

@@ -1,9 +1,20 @@
import { DataModel } from '@mcschema/core'
import type { BiomeSource, Climate, NoiseOctaves } from 'deepslate'
import { FixedBiome, MultiNoise, NoiseGeneratorSettings, NoiseSampler, NormalNoise, Random } from 'deepslate'
import { FixedBiome, NoiseGeneratorSettings, NoiseSampler, NormalNoise, Random } from 'deepslate'
import init, { biome_parameters, climate_sampler, multi_noise } from 'deepslate-rs'
// @ts-expect-error
import wasm from 'deepslate-rs/deepslate_rs_bg.wasm?url'
import { fetchPreset } from '../DataFetcher'
import type { VersionId } from '../Schemas'
import { clamp, deepClone, deepEqual, square, stringToColor } from '../Utils'
import { BiMap, clamp, deepClone, deepEqual, square, stringToColor } from '../Utils'
let ready = false
async function loadWasm() {
if (ready) return
await init(wasm)
ready = true
console.debug(`Loaded deepslate-rs from "${wasm}"`)
}
type BiomeColors = Record<string, number[]>
type BiomeSourceOptions = {
@@ -16,24 +27,35 @@ type BiomeSourceOptions = {
version: VersionId,
}
interface CachedBiomeSource extends BiomeSource {
getBiomes?(xFrom: number, xTo: number, xStep: number, yFrom: number, yTo: number, yStep: number, zFrom: number, zTo: number, zStep: number): string[]
}
let cacheState: any
let biomeSourceCache: BiomeSource
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 data = img.data
const ox = -options.offset[0] - 100 + options.res / 2
const oz = -options.offset[1] - 100 + options.res / 2
const ox = -Math.round(options.offset[0]) - 100 + options.res / 2
const oz = -Math.round(options.offset[1]) - 100 + options.res / 2
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,
)
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 worldX = (x + ox) * options.scale
const worldZ = (z + oz) * options.scale
const b = biomeSource.getBiome(worldX, 64, worldZ, climateSampler)
const b = biomes?.[(x / options.res) * 200 / options.res + z / options.res] ?? biomeSource.getBiome(worldX, 64, worldZ, climateSampler)
const color = getBiomeColor(b, options.biomeColors)
data[i] = color[0]
data[i + 1] = color[1]
@@ -50,7 +72,7 @@ export async function getBiome(state: any, x: number, z: number, options: BiomeS
return biomeSource.getBiome(xx, 64, zz, climateSampler)
}
async function getCached(state: any, options: BiomeSourceOptions): Promise<{ biomeSource: BiomeSource, climateSampler: Climate.Sampler }> {
async function getCached(state: any, options: BiomeSourceOptions): Promise<{ biomeSource: CachedBiomeSource, climateSampler: Climate.Sampler }> {
const newState = [state, options.octaves, `${options.seed}`, options.version]
if (!deepEqual(newState, cacheState)) {
cacheState = deepClone(newState)
@@ -67,7 +89,7 @@ async function getCached(state: any, options: BiomeSourceOptions): Promise<{ bio
}
}
async function getBiomeSource(state: any, options: BiomeSourceOptions): Promise<BiomeSource> {
async function getBiomeSource(state: any, options: BiomeSourceOptions): Promise<CachedBiomeSource> {
switch (state?.type?.replace(/^minecraft:/, '')) {
case 'fixed':
return new FixedBiome(state.biome as string)
@@ -93,7 +115,41 @@ async function getBiomeSource(state: any, options: BiomeSourceOptions): Promise<
}
state = DataModel.unwrapLists(state)
if (options.version === '1.18') {
return MultiNoise.fromJson(state)
await loadWasm()
const BiomeIds = new BiMap<string, number>()
const param = (p: number | number[]) => {
return typeof p === 'number' ? [p, p] : p
}
const [t0, t1, h0, h1, c0, c1, e0, e1, w0, w1, d0, d1, o, b] = [[], [], [], [], [], [], [], [], [], [], [], [], [], []] as number[][]
for (const i of state.biomes) {
const { temperature, humidity, continentalness, erosion, weirdness, depth, offset } = i.parameters
t0.push(param(temperature)[0])
t1.push(param(temperature)[1])
h0.push(param(humidity)[0])
h1.push(param(humidity)[1])
c0.push(param(continentalness)[0])
c1.push(param(continentalness)[1])
e0.push(param(erosion)[0])
e1.push(param(erosion)[1])
w0.push(param(weirdness)[0])
w1.push(param(weirdness)[1])
d0.push(param(depth)[0])
d1.push(param(depth)[1])
o.push(offset)
b.push(BiomeIds.getOrPut(i.biome, Math.floor(Math.random() * 2147483647)))
}
const parameters = biome_parameters(new Float64Array(t0), new Float64Array(t1), new Float64Array(h0), new Float64Array(h1), new Float64Array(c0), new Float64Array(c1), new Float64Array(e0), new Float64Array(e1), new Float64Array(w0), new Float64Array(w1), new Float64Array(d0), new Float64Array(d1), new Float64Array(o), new Int32Array(b))
const sampler = climate_sampler(options.seed, options.octaves.temperature.firstOctave, new Float64Array(options.octaves.temperature.amplitudes), options.octaves.humidity.firstOctave, new Float64Array(options.octaves.humidity.amplitudes), options.octaves.continentalness.firstOctave, new Float64Array(options.octaves.continentalness.amplitudes), options.octaves.erosion.firstOctave, new Float64Array(options.octaves.erosion.amplitudes), options.octaves.weirdness.firstOctave, new Float64Array(options.octaves.weirdness.amplitudes), options.octaves.shift.firstOctave, new Float64Array(options.octaves.shift.amplitudes))
return {
getBiome(x, y, z) {
const ids = multi_noise(parameters, sampler, x, x + 1, 1, y, y + 1, 1, z, z + 1, 1)
return BiomeIds.getA(ids[0]) ?? 'unknown'
},
getBiomes(xFrom, xTo, xStep, yFrom, yTo, yStep, zFrom, zTo, zStep) {
const ids = multi_noise(parameters, sampler, xFrom, xTo, xStep, yFrom, yTo, yStep, zFrom, zTo, zStep)
return [...ids].map(id => BiomeIds.getA(id) ?? 'unknown')
},
}
} else {
const noise = ['altitude', 'temperature', 'humidity', 'weirdness']
.map((id, i) => {
@@ -171,13 +227,18 @@ const VanillaColors: Record<string, [number, number, number]> = {
'minecraft:frozen_ocean': [112,112,214],
'minecraft:frozen_river': [160,160,255],
'minecraft:giant_spruce_taiga': [129,142,121],
'minecraft:old_growth_spruce_taiga': [129,142,121],
'minecraft:giant_spruce_taiga_hills': [109,119,102],
'minecraft:giant_tree_taiga': [89,102,81],
'minecraft:old_growth_pine_taiga': [89,102,81],
'minecraft:giant_tree_taiga_hills': [69,79,62],
'minecraft:gravelly_hills': [136,136,136],
'minecraft:gravelly_mountains': [136,136,136],
'minecraft:windswept_gravelly_hills': [136,136,136],
'minecraft:ice_spikes': [180,220,220],
'minecraft:jungle': [83,123,9],
'minecraft:jungle_edge': [98,139,23],
'minecraft:sparse_jungle': [98,139,23],
'minecraft:jungle_hills': [44,66,5],
'minecraft:lukewarm_ocean': [0,0,144],
'minecraft:modified_badlands_plateau': [242,180,141],
@@ -186,7 +247,9 @@ const VanillaColors: Record<string, [number, number, number]> = {
'minecraft:modified_jungle_edge': [138,179,63],
'minecraft:modified_wooded_badlands_plateau': [216,191,141],
'minecraft:mountain_edge': [114,120,154],
'minecraft:extreme_hills': [96,96,96],
'minecraft:mountains': [96,96,96],
'minecraft:windswept_hills': [96,96,96],
'minecraft:mushroom_field_shore': [160,0,255],
'minecraft:mushroom_fields': [255,0,255],
'minecraft:nether_wastes': [191,59,59],
@@ -196,6 +259,7 @@ const VanillaColors: Record<string, [number, number, number]> = {
'minecraft:savanna': [189,178,95],
'minecraft:savanna_plateau': [167,157,100],
'minecraft:shattered_savanna': [229,218,135],
'minecraft:windswept_savanna': [229,218,135],
'minecraft:shattered_savanna_plateau': [207,197,140],
'minecraft:small_end_islands': [128,128,255],
'minecraft:snowy_beach': [250,240,192],
@@ -204,8 +268,10 @@ const VanillaColors: Record<string, [number, number, number]> = {
'minecraft:snowy_taiga_hills': [36,63,54],
'minecraft:snowy_taiga_mountains': [89,125,114],
'minecraft:snowy_tundra': [255,255,255],
'minecraft:snowy_plains': [255,255,255],
'minecraft:soul_sand_valley': [94,56,48],
'minecraft:stone_shore': [162,162,132],
'minecraft:stony_shore': [162,162,132],
'minecraft:sunflower_plains': [181,219,136],
'minecraft:swamp': [7,249,178],
'minecraft:swamp_hills': [47,255,218],
@@ -213,17 +279,22 @@ const VanillaColors: Record<string, [number, number, number]> = {
'minecraft:taiga_hills': [22,57,51],
'minecraft:taiga_mountains': [51,142,129],
'minecraft:tall_birch_forest': [88,156,108],
'minecraft:old_growth_birch_forest': [88,156,108],
'minecraft:tall_birch_hills': [71,135,90],
'minecraft:the_end': [128,128,255],
'minecraft:the_void': [0,0,0],
'minecraft:warm_ocean': [0,0,172],
'minecraft:warped_forest': [73,144,123],
'minecraft:wooded_badlands_plateau': [176,151,101],
'minecraft:wooded_badlands': [176,151,101],
'minecraft:wooded_hills': [34,85,28],
'minecraft:wooded_mountains': [80,112,80],
'minecraft:windswept_forest': [80,112,80],
'minecraft:snowy_slopes': [140, 195, 222],
'minecraft:lofty_peaks': [196, 168, 193],
'minecraft:jagged_peaks': [196, 168, 193],
'minecraft:snowcapped_peaks': [200, 198, 200],
'minecraft:frozen_peaks': [200, 198, 200],
'minecraft:stony_peaks': [82, 92, 103],
'minecraft:grove': [150, 150, 189],
'minecraft:meadow': [169, 197, 80],