diff --git a/package-lock.json b/package-lock.json
index 4c7c54f4..68c5a4e4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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": {
diff --git a/package.json b/package.json
index b6c1deda..080d4213 100644
--- a/package.json
+++ b/package.json
@@ -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"
},
diff --git a/src/app/Utils.ts b/src/app/Utils.ts
index ec900265..39c3bd0e 100644
--- a/src/app/Utils.ts
+++ b/src/app/Utils.ts
@@ -140,3 +140,35 @@ export function deepEqual(a: any, b: any) {
}
return a !== a && b !== b
}
+
+export class BiMap {
+ private readonly forward: Map
+ private readonly backward: Map
+
+ 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
+ }
+}
diff --git a/src/app/previews/BiomeSource.ts b/src/app/previews/BiomeSource.ts
index 9e4eac60..48383b9a 100644
--- a/src/app/previews/BiomeSource.ts
+++ b/src/app/previews/BiomeSource.ts
@@ -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
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 {
+async function getBiomeSource(state: any, options: BiomeSourceOptions): Promise {
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()
+ 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 = {
'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 = {
'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 = {
'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 = {
'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 = {
'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],