mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-23 07:10:41 +00:00
Refactor: create deepslate abstraction
This commit is contained in:
23
package-lock.json
generated
23
package-lock.json
generated
@@ -23,6 +23,7 @@
|
||||
"comment-json": "^4.1.1",
|
||||
"deepslate": "^0.11.0-beta.1",
|
||||
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
|
||||
"deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13",
|
||||
"deepslate-rs": "^0.1.6",
|
||||
"highlight.js": "^11.5.1",
|
||||
"howler": "^2.2.3",
|
||||
@@ -1788,6 +1789,18 @@
|
||||
"pako": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/deepslate-1.18.2": {
|
||||
"name": "deepslate",
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.9.0.tgz",
|
||||
"integrity": "sha512-juHNoXVu7+Gejw0xk4AwIt99FgIe/nkxTqJTv3bTlcfXINcF3VNlg1l6MnSiVTUFnuFHZl81woE9lVMAMp53Aw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"gl-matrix": "^3.3.0",
|
||||
"md5": "^2.3.0",
|
||||
"pako": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/deepslate-rs": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/deepslate-rs/-/deepslate-rs-0.1.6.tgz",
|
||||
@@ -6441,6 +6454,16 @@
|
||||
"pako": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"deepslate-1.18.2": {
|
||||
"version": "npm:deepslate@0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.9.0.tgz",
|
||||
"integrity": "sha512-juHNoXVu7+Gejw0xk4AwIt99FgIe/nkxTqJTv3bTlcfXINcF3VNlg1l6MnSiVTUFnuFHZl81woE9lVMAMp53Aw==",
|
||||
"requires": {
|
||||
"gl-matrix": "^3.3.0",
|
||||
"md5": "^2.3.0",
|
||||
"pako": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"deepslate-rs": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/deepslate-rs/-/deepslate-rs-0.1.6.tgz",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"comment-json": "^4.1.1",
|
||||
"deepslate": "^0.11.0-beta.1",
|
||||
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
|
||||
"deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13",
|
||||
"deepslate-rs": "^0.1.6",
|
||||
"highlight.js": "^11.5.1",
|
||||
"howler": "^2.2.3",
|
||||
|
||||
@@ -36,8 +36,9 @@ export function useCanvas({ size, draw, onDrag, onHover, onLeave }: {
|
||||
if (!(dx === 0 && dy === 0)) {
|
||||
dragPending.current = [dragPending.current[0] + dx, dragPending.current[1] + dy]
|
||||
if (!dragBusy.current) {
|
||||
if (!dragRequest.current) return
|
||||
cancelAnimationFrame(dragRequest.current)
|
||||
if (dragRequest.current) {
|
||||
cancelAnimationFrame(dragRequest.current)
|
||||
}
|
||||
dragRequest.current = requestAnimationFrame(async () => {
|
||||
if (!canvas.current) return
|
||||
dragBusy.current = true
|
||||
|
||||
129
src/app/previews/Deepslate.ts
Normal file
129
src/app/previews/Deepslate.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { DataModel } from '@mcschema/core'
|
||||
import * as deepslate19 from 'deepslate/worldgen'
|
||||
import type { VersionId } from '../services'
|
||||
import { checkVersion, fetchAllPresets } from '../services'
|
||||
import { deepClone, deepEqual } from '../Utils'
|
||||
|
||||
export class Deepslate {
|
||||
private d = deepslate19
|
||||
private loadedVersion: VersionId | undefined
|
||||
private readonly registriesLoaded = new Set<VersionId>()
|
||||
private static readonly Z = 0
|
||||
|
||||
private cacheState: unknown
|
||||
private settingsCache: NoiseSettings | undefined
|
||||
private generatorCache: ChunkGenerator | undefined
|
||||
private chunksCache: Chunk[] = []
|
||||
|
||||
public async loadVersion(version: VersionId) {
|
||||
console.debug('Load deepslate version', this.loadedVersion, '->', version)
|
||||
if (checkVersion(version, '1.19')) {
|
||||
this.d = deepslate19
|
||||
} else if (checkVersion(version, '1.18.2')) {
|
||||
this.d = await import('deepslate-1.18.2') as any
|
||||
} else {
|
||||
this.d = await import('deepslate-1.18') as any
|
||||
}
|
||||
|
||||
if (this.d.WorldgenRegistries) {
|
||||
if (!this.registriesLoaded.has(version)) {
|
||||
const REGISTRIES: [string, keyof typeof this.d.WorldgenRegistries, { fromJson(obj: unknown): any}][] = [
|
||||
['worldgen/noise', 'NOISE', this.d.NoiseParameters],
|
||||
['worldgen/density_function', 'DENSITY_FUNCTION', this.d.DensityFunction],
|
||||
]
|
||||
await Promise.all(REGISTRIES.map(async ([id, name, parser]) => {
|
||||
const entries = await fetchAllPresets(version, id)
|
||||
const registry = new this.d.Registry<typeof parser>(this.d.Identifier.create(id))
|
||||
for (const [key, value] of entries.entries()) {
|
||||
registry.register(this.d.Identifier.parse(key), parser.fromJson(value))
|
||||
}
|
||||
this.d.WorldgenRegistries[name].assign(registry as any)
|
||||
}))
|
||||
this.registriesLoaded.add(version)
|
||||
}
|
||||
}
|
||||
console.debug('Finished loading deepslate version', this.loadedVersion, '->', version)
|
||||
this.loadedVersion = version
|
||||
}
|
||||
|
||||
public loadChunkGenerator(settings: unknown, seed: bigint, biome = 'unknown') {
|
||||
const newCacheState = [settings, `${seed}`, biome]
|
||||
if (!deepEqual(this.cacheState, newCacheState)) {
|
||||
const biomeSource = new this.d.FixedBiome(this.d.Identifier.parse(biome))
|
||||
const noiseSettings = this.d.NoiseGeneratorSettings.fromJson(DataModel.unwrapLists(settings))
|
||||
const chunkGenerator = new this.d.NoiseChunkGenerator(seed, biomeSource, noiseSettings)
|
||||
this.settingsCache = noiseSettings.noise
|
||||
this.generatorCache = chunkGenerator
|
||||
this.chunksCache = []
|
||||
this.cacheState = deepClone(newCacheState)
|
||||
}
|
||||
}
|
||||
|
||||
public generateChunks(minX: number, width: number, biome = 'unknown') {
|
||||
if (!this.settingsCache) {
|
||||
throw new Error('Tried to generate chunks before settings are loaded')
|
||||
}
|
||||
const minY = this.settingsCache.minY
|
||||
const height = this.settingsCache.height
|
||||
|
||||
return [...Array(Math.ceil(width / 16) + 1)].map((_, i) => {
|
||||
const x = (minX >> 4) + i
|
||||
const cached = this.chunksCache.find(c => c.pos[0] === x)
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
const chunk = new this.d.Chunk(minY, height, this.d.ChunkPos.create(x, Deepslate.Z >> 4))
|
||||
if (!this.generatorCache) {
|
||||
throw new Error('Tried to generate chunks before generator is loaded')
|
||||
}
|
||||
this.generatorCache.fill(chunk, true)
|
||||
this.generatorCache.buildSurface(chunk, biome)
|
||||
this.chunksCache.push(chunk)
|
||||
return chunk
|
||||
})
|
||||
}
|
||||
|
||||
public loadDensityFunction(state: unknown, seed: bigint) {
|
||||
const random = this.d.XoroshiroRandom.create(seed).forkPositional()
|
||||
const settings = this.d.NoiseSettings.fromJson({
|
||||
min_y: -64,
|
||||
height: 384,
|
||||
size_horizontal: 1,
|
||||
size_vertical: 2,
|
||||
sampling: { xz_scale: 1, y_scale: 1, xz_factor: 80, y_factor: 160 },
|
||||
bottom_slide: { target: 0.1171875, size: 3, offset: 0 },
|
||||
top_slide: { target: -0.078125, size: 2, offset: 8 },
|
||||
terrain_shaper: { offset: 0.044, factor: 4, jaggedness: 0 },
|
||||
})
|
||||
this.settingsCache = settings
|
||||
const originalFn = this.d.DensityFunction.fromJson(state)
|
||||
return originalFn.mapAll(new this.d.NoiseRouter.Visitor(random, settings))
|
||||
}
|
||||
|
||||
public getNoiseSettings(): NoiseSettings {
|
||||
if (!this.settingsCache) {
|
||||
throw new Error('Tried to access noise settings when they are not loaded')
|
||||
}
|
||||
return this.settingsCache
|
||||
}
|
||||
|
||||
public getBlockState(x: number, y: number) {
|
||||
const chunk = this.chunksCache.find(c => this.d.ChunkPos.minBlockX(c.pos) <= x && this.d.ChunkPos.maxBlockX(c.pos) >= x)
|
||||
return chunk?.getBlockState(this.d.BlockPos.create(x, y, Deepslate.Z))
|
||||
}
|
||||
}
|
||||
|
||||
interface NoiseSettings {
|
||||
minY: number,
|
||||
height: number,
|
||||
}
|
||||
|
||||
interface ChunkGenerator {
|
||||
fill(chunk: Chunk, onlyFirstZ?: boolean): void
|
||||
buildSurface(chunk: Chunk, biome: string): void
|
||||
}
|
||||
|
||||
interface Chunk {
|
||||
readonly pos: deepslate19.ChunkPos;
|
||||
getBlockState(pos: deepslate19.BlockPos): deepslate19.BlockState;
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { DataModel } from '@mcschema/core'
|
||||
import type { BlockState } from 'deepslate/worldgen'
|
||||
import { BlockPos, Chunk, ChunkPos, clampedMap, DensityFunction, FixedBiome, Identifier, NoiseChunkGenerator, NoiseGeneratorSettings, NoiseParameters, NoiseRouter, NoiseSettings, Registry, WorldgenRegistries, XoroshiroRandom } from 'deepslate/worldgen'
|
||||
import { BlockState, clampedMap, DensityFunction } from 'deepslate/worldgen'
|
||||
import type { VersionId } from '../services'
|
||||
import { checkVersion, fetchAllPresets } from '../services'
|
||||
import { deepClone, deepEqual } from '../Utils'
|
||||
import { checkVersion } from '../services'
|
||||
import { Deepslate } from './Deepslate'
|
||||
import { NoiseChunkGenerator as OldNoiseChunkGenerator } from './noise/NoiseChunkGenerator'
|
||||
|
||||
export type NoiseSettingsOptions = {
|
||||
@@ -16,8 +14,6 @@ export type NoiseSettingsOptions = {
|
||||
version: VersionId,
|
||||
}
|
||||
|
||||
const Z = 0
|
||||
|
||||
const colors: Record<string, [number, number, number]> = {
|
||||
'minecraft:air': [150, 160, 170],
|
||||
'minecraft:water': [20, 80, 170],
|
||||
@@ -37,27 +33,21 @@ const colors: Record<string, [number, number, number]> = {
|
||||
'minecraft:end_stone': [200, 200, 140],
|
||||
}
|
||||
|
||||
let cacheState: any
|
||||
let generatorCache: NoiseChunkGenerator
|
||||
let chunkCache: Chunk[] = []
|
||||
const registryCache = new Map<VersionId, Registry<Registry<any>>>()
|
||||
const DEEPSLATE = new Deepslate()
|
||||
|
||||
export async function noiseSettings(state: any, img: ImageData, options: NoiseSettingsOptions) {
|
||||
if (checkVersion(options.version, '1.18')) {
|
||||
if (checkVersion(options.version, '1.18.2')) {
|
||||
await initRegistries(options.version)
|
||||
}
|
||||
|
||||
const { settings, generator } = await getCached(state, options)
|
||||
|
||||
const slice = new LevelSlice(-options.offset, options.width, settings.noise.minY, settings.noise.height)
|
||||
slice.generate(generator, options.biome)
|
||||
await DEEPSLATE.loadVersion(options.version)
|
||||
DEEPSLATE.loadChunkGenerator(state, options.seed, options.biome)
|
||||
DEEPSLATE.generateChunks(-options.offset, options.width, options.biome)
|
||||
const noise = DEEPSLATE.getNoiseSettings()
|
||||
|
||||
const data = img.data
|
||||
for (let x = 0; x < options.width; x += 1) {
|
||||
for (let y = 0; y < settings.noise.height; y += 1) {
|
||||
const i = x * 4 + (settings.noise.height-y-1) * 4 * img.width
|
||||
const state = slice.getBlockState([x - options.offset, y + settings.noise.minY, Z])
|
||||
for (let y = 0; y < noise.height; y += 1) {
|
||||
const i = x * 4 + (noise.height-y-1) * 4 * img.width
|
||||
const state = DEEPSLATE.getBlockState(x - options.offset, y + noise.minY) ?? BlockState.AIR
|
||||
const color = colors[state.getName().toString()] ?? [0, 0, 0]
|
||||
data[i] = color[0]
|
||||
data[i + 1] = color[1]
|
||||
@@ -86,22 +76,20 @@ export async function noiseSettings(state: any, img: ImageData, options: NoiseSe
|
||||
}
|
||||
|
||||
export function getNoiseBlock(x: number, y: number) {
|
||||
const chunk = chunkCache.find(c => ChunkPos.minBlockX(c.pos) <= x && ChunkPos.maxBlockX(c.pos) >= x)
|
||||
if (!chunk) {
|
||||
return undefined
|
||||
}
|
||||
return chunk.getBlockState(BlockPos.create(x, y, Z))
|
||||
return DEEPSLATE.getBlockState(x, y)
|
||||
}
|
||||
|
||||
export async function densityFunction(state: any, img: ImageData, options: NoiseSettingsOptions) {
|
||||
const { fn, settings } = await createDensityFunction(state, options)
|
||||
await DEEPSLATE.loadVersion(options.version)
|
||||
const fn = DEEPSLATE.loadDensityFunction(state, options.seed)
|
||||
const noise = DEEPSLATE.getNoiseSettings()
|
||||
|
||||
const arr = Array(options.width * settings.height)
|
||||
const arr = Array(options.width * noise.height)
|
||||
let min = Infinity
|
||||
let max = -Infinity
|
||||
for (let x = 0; x < options.width; x += 1) {
|
||||
for (let y = 0; y < settings.height; y += 1) {
|
||||
const i = x + (settings.height-y-1) * options.width
|
||||
for (let y = 0; y < noise.height; y += 1) {
|
||||
const i = x + (noise.height-y-1) * options.width
|
||||
const density = fn.compute(DensityFunction.context(x - options.offset, y, 0))
|
||||
min = Math.min(min, density)
|
||||
max = Math.max(max, density)
|
||||
@@ -110,7 +98,7 @@ export async function densityFunction(state: any, img: ImageData, options: Noise
|
||||
}
|
||||
|
||||
const data = img.data
|
||||
for (let i = 0; i < options.width * settings.height; i += 1) {
|
||||
for (let i = 0; i < options.width * noise.height; i += 1) {
|
||||
const color = Math.floor(clampedMap(arr[i], min, max, 0, 256))
|
||||
data[4 * i] = color
|
||||
data[4 * i + 1] = color
|
||||
@@ -119,75 +107,6 @@ export async function densityFunction(state: any, img: ImageData, options: Noise
|
||||
}
|
||||
}
|
||||
|
||||
async function createDensityFunction(state: any, options: NoiseSettingsOptions) {
|
||||
await initRegistries(options.version)
|
||||
|
||||
const random = XoroshiroRandom.create(options.seed).forkPositional()
|
||||
const settings = NoiseSettings.fromJson({
|
||||
min_y: -64,
|
||||
height: 384,
|
||||
size_horizontal: 1,
|
||||
size_vertical: 2,
|
||||
sampling: { xz_scale: 1, y_scale: 1, xz_factor: 80, y_factor: 160 },
|
||||
bottom_slide: { target: 0.1171875, size: 3, offset: 0 },
|
||||
top_slide: { target: -0.078125, size: 2, offset: 8 },
|
||||
terrain_shaper: { offset: 0.044, factor: 4, jaggedness: 0 },
|
||||
})
|
||||
const originalFn = DensityFunction.fromJson(state)
|
||||
const fn = originalFn.mapAll(new NoiseRouter.Visitor(random, settings))
|
||||
|
||||
return {
|
||||
fn,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
const Registries: [string, { fromJson(obj: unknown): any}][] = [
|
||||
['worldgen/noise', NoiseParameters],
|
||||
['worldgen/density_function', DensityFunction],
|
||||
]
|
||||
|
||||
async function initRegistries(version: VersionId) {
|
||||
const rootRegistries = registryCache.get(version) ?? new Registry(new Identifier('misode', 'temp'))
|
||||
if (!registryCache.has(version)) {
|
||||
await Promise.all(Registries.map(([id, c]) => fetchRegistry(version, rootRegistries, id, c)))
|
||||
registryCache.set(version, rootRegistries)
|
||||
}
|
||||
WorldgenRegistries.DENSITY_FUNCTION.clear().assign(rootRegistries.getOrThrow(Identifier.create('worldgen/density_function')))
|
||||
WorldgenRegistries.NOISE.clear().assign(rootRegistries.getOrThrow(Identifier.create('worldgen/noise')))
|
||||
}
|
||||
|
||||
async function fetchRegistry<T extends { fromJson(obj: unknown): T }>(version: VersionId, root: Registry<Registry<unknown>>, id: string, clazz: T) {
|
||||
const entries = await fetchAllPresets(version, id)
|
||||
const registry = new Registry<typeof clazz>(Identifier.create(id))
|
||||
for (const [key, value] of entries.entries()) {
|
||||
registry.register(Identifier.parse(key), clazz.fromJson(value))
|
||||
}
|
||||
root.register(registry.key, registry)
|
||||
}
|
||||
|
||||
async function getCached(state: unknown, options: NoiseSettingsOptions) {
|
||||
const settings = NoiseGeneratorSettings.fromJson(DataModel.unwrapLists(state))
|
||||
|
||||
const newState = [state, `${options.seed}`, options.biome]
|
||||
if (!deepEqual(newState, cacheState)) {
|
||||
cacheState = deepClone(newState)
|
||||
chunkCache = []
|
||||
if (checkVersion(options.version, '1.18.2')) {
|
||||
const biomeSource = new FixedBiome(Identifier.create('unknown'))
|
||||
generatorCache = new NoiseChunkGenerator(options.seed, biomeSource, settings)
|
||||
} else {
|
||||
const deepslate18 = await import('deepslate-1.18')
|
||||
const biomeSource = new deepslate18.FixedBiome('unknown')
|
||||
generatorCache = new deepslate18.NoiseChunkGenerator(options.seed, biomeSource, settings as any) as any
|
||||
}
|
||||
}
|
||||
return {
|
||||
settings,
|
||||
generator: generatorCache,
|
||||
}
|
||||
}
|
||||
|
||||
function getColor(noise: number[], y: number): number {
|
||||
if (noise[y] > 0) {
|
||||
return 0
|
||||
@@ -197,43 +116,3 @@ function getColor(noise: number[], y: number): number {
|
||||
}
|
||||
return 255
|
||||
}
|
||||
|
||||
class LevelSlice {
|
||||
private readonly chunks: Chunk[]
|
||||
private readonly done: boolean[]
|
||||
|
||||
constructor(
|
||||
private readonly minX: number,
|
||||
width: number,
|
||||
minY: number,
|
||||
height: number,
|
||||
) {
|
||||
this.done = []
|
||||
this.chunks = [...Array(Math.ceil(width / 16) + 1)]
|
||||
.map((_, i) => {
|
||||
const x = (minX >> 4) + i
|
||||
const cached = chunkCache.find(c => c.pos[0] === x)
|
||||
if (cached) {
|
||||
this.done[i] = true
|
||||
return cached
|
||||
}
|
||||
return new Chunk(minY, height, ChunkPos.create(x, Z >> 4))
|
||||
})
|
||||
}
|
||||
|
||||
public generate(generator: NoiseChunkGenerator, forcedBiome?: string) {
|
||||
this.chunks.forEach((chunk, i) => {
|
||||
if (!this.done[i]) {
|
||||
generator.fill(chunk, true)
|
||||
generator.buildSurface(chunk, forcedBiome)
|
||||
this.done[i] = true
|
||||
chunkCache.push(chunk)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public getBlockState(pos: BlockPos): BlockState {
|
||||
const chunkIndex = (pos[0] >> 4) - (this.minX >> 4)
|
||||
return this.chunks[chunkIndex].getBlockState(pos)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user