Add structure set preview

This commit is contained in:
Misode
2023-06-23 00:36:36 +02:00
parent e519f1c757
commit 08581889d2
7 changed files with 170 additions and 15 deletions

14
package-lock.json generated
View File

@@ -25,7 +25,7 @@
"brace": "^0.11.1",
"buffer": "^6.0.3",
"comment-json": "^4.1.1",
"deepslate": "^0.17.5",
"deepslate": "^0.18.0",
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
"deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13",
"highlight.js": "^11.5.1",
@@ -1998,9 +1998,9 @@
"dev": true
},
"node_modules/deepslate": {
"version": "0.17.5",
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.17.5.tgz",
"integrity": "sha512-kS0Hn1RbAZ/6WS2DDeFKYkcQXd0VC9PrahYfec96bA6RY0+s0jTpEejY9m3og/qZ5hNq+NgHu5qQxvcaN0MZ1g==",
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.18.0.tgz",
"integrity": "sha512-mip9lv9ka0ksdaQ6OrfwQHFyr171vtTrmy1FAIWWy+owWbGnYiXioIS6uX4jEeQ9MMaWrBXwKHuPu5Hv+H7E3w==",
"dependencies": {
"gl-matrix": "^3.3.0",
"md5": "^2.3.0",
@@ -6773,9 +6773,9 @@
"dev": true
},
"deepslate": {
"version": "0.17.5",
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.17.5.tgz",
"integrity": "sha512-kS0Hn1RbAZ/6WS2DDeFKYkcQXd0VC9PrahYfec96bA6RY0+s0jTpEejY9m3og/qZ5hNq+NgHu5qQxvcaN0MZ1g==",
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.18.0.tgz",
"integrity": "sha512-mip9lv9ka0ksdaQ6OrfwQHFyr171vtTrmy1FAIWWy+owWbGnYiXioIS6uX4jEeQ9MMaWrBXwKHuPu5Hv+H7E3w==",
"requires": {
"gl-matrix": "^3.3.0",
"md5": "^2.3.0",

View File

@@ -31,7 +31,7 @@
"brace": "^0.11.1",
"buffer": "^6.0.3",
"comment-json": "^4.1.1",
"deepslate": "^0.17.5",
"deepslate": "^0.18.0",
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
"deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13",
"highlight.js": "^11.5.1",

View File

@@ -4,9 +4,9 @@ import { useState } from 'preact/hooks'
import { useModel } from '../../hooks/index.js'
import type { VersionId } from '../../services/index.js'
import { checkVersion } from '../../services/index.js'
import { BiomeSourcePreview, BlockStatePreview, DecoratorPreview, DensityFunctionPreview, LootTablePreview, ModelPreview, NoisePreview, NoiseSettingsPreview } from '../previews/index.js'
import { BiomeSourcePreview, BlockStatePreview, DecoratorPreview, DensityFunctionPreview, LootTablePreview, ModelPreview, NoisePreview, NoiseSettingsPreview, StructureSetPreview } from '../previews/index.js'
export const HasPreview = ['loot_table', 'dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature', 'block_definition', 'model']
export const HasPreview = ['loot_table', 'dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature', 'worldgen/structure_set', 'block_definition', 'model']
type PreviewPanelProps = {
model: DataModel | undefined,
@@ -50,6 +50,10 @@ export function PreviewPanel({ model, version, id, shown }: PreviewPanelProps) {
return <DecoratorPreview {...{ model, version, shown, data }} />
}
if (id === 'worldgen/structure_set' && checkVersion(version, '1.19')) {
return <StructureSetPreview {...{ model, version, shown, data }} />
}
if (id === 'block_definition') {
return <BlockStatePreview {...{ model, version, shown, data }} />
}

View File

@@ -1,9 +1,9 @@
import { DataModel } from '@mcschema/core'
import type { BlockPos, ChunkPos, PerlinNoise, Random } from 'deepslate/worldgen'
import type { VersionId } from '../../services/index.js'
import { checkVersion } from '../../services/index.js'
import type { Color } from '../../Utils.js'
import { clamp, isObject, stringToColor } from '../../Utils.js'
import type { VersionId } from '../../services/index.js'
import { checkVersion } from '../../services/index.js'
export type Placement = [BlockPos, number]
@@ -27,7 +27,7 @@ export type PlacedFeature = {
const terrain = [50, 50, 51, 51, 52, 52, 53, 54, 56, 57, 57, 58, 58, 59, 60, 60, 60, 59, 59, 59, 60, 61, 61, 62, 63, 63, 64, 64, 64, 65, 65, 66, 66, 65, 65, 66, 66, 67, 67, 67, 68, 69, 71, 73, 74, 76, 79, 80, 81, 81, 82, 83, 83, 82, 82, 81, 81, 80, 80, 80, 81, 81, 82, 82]
const featureColors: Color[] = [
export const featureColors: Color[] = [
[255, 77, 54], // red
[59, 118, 255], // blue
[91, 207, 25], // green

View File

@@ -25,6 +25,7 @@ export class Deepslate {
private generatorCache: ChunkGenerator | undefined
private biomeSourceCache: BiomeSource | undefined
private randomStateCache: deepslate19.RandomState | undefined
private structureContextCache: deepslate19.WorldgenStructure.GenerationContext | undefined
private chunksCache: Chunk[] = []
private biomeCache: Map<string, string> = new Map()
private readonly presetCache: Map<string, unknown> = new Map()
@@ -296,16 +297,72 @@ export class Deepslate {
}
}
public loadStructureSet(state: unknown, seed: bigint) {
if (!this.isVersion('1.19')) {
throw new Error('Cannot load structure set prior to 1.19')
}
const settings = this.d.NoiseGeneratorSettings.create({
noise: {
minY: 0,
height: 256,
xzSize: 1,
ySize: 2,
},
noiseRouter: this.d.NoiseRouter.create({
finalDensity: this.d.DensityFunction.fromJson(state),
}),
})
const unknownBiome = this.d.Identifier.create('unknown')
const randomState = new this.d.RandomState(settings, seed)
const biomeSource = new this.d.FixedBiomeSource(unknownBiome)
const chunkGenerator = new this.d.NoiseChunkGenerator(biomeSource, settings)
this.structureContextCache = { seed, settings, randomState, biomeSource, chunkGenerator }
class SimpleStructure extends this.d.WorldgenStructure {
constructor(settings: deepslate19.WorldgenStructure.StructureSettings) {
super(settings)
}
findGenerationPoint(chunkX: number, chunkZ: number, _random: deepslate19.Random, _context: deepslate19.WorldgenStructure.GenerationContext): deepslate19.BlockPos {
return [(chunkX << 4) + 8, 0, (chunkZ << 4) + 8]
}
}
const structureSet = this.d.StructureSet.fromJson(state)
for (const e of structureSet.structures) {
const id = e.structure.key()
if (id === undefined) {
continue
}
const structure = new SimpleStructure({ validBiomes: new this.d.HolderSet([this.d.Holder.reference(this.d.WorldgenRegistries.BIOME, unknownBiome)])})
this.d.WorldgenStructure.REGISTRY.register(id, structure)
this.d.WorldgenRegistries.BIOME.register(unknownBiome, {})
}
if (structureSet.placement instanceof this.d.StructurePlacement.ConcentricRingsStructurePlacement) {
const biomeTag = structureSet.placement['preferredBiomes'].key()
if (biomeTag instanceof this.d.Identifier) {
this.d.WorldgenRegistries.BIOME.getTagRegistry().register(biomeTag, new this.d.HolderSet([this.d.Holder.reference(this.d.WorldgenRegistries.BIOME, unknownBiome)]))
}
}
return structureSet
}
public getWorldgenStructureContext(): deepslate19.WorldgenStructure.GenerationContext {
if (!this.structureContextCache) {
throw new Error('Tried to access structure context when it isn\'t loaded')
}
return this.structureContextCache
}
public getNoiseSettings(): NoiseSettings {
if (!this.settingsCache) {
throw new Error('Tried to access noise settings when they are not loaded')
throw new Error('Tried to access noise settings when it isn\'t loaded')
}
return this.settingsCache
}
public getNoiseRouter(): NoiseRouter {
if (!this.routerCache) {
throw new Error('Tried to access noise router when they are not loaded')
throw new Error('Tried to access noise router when it isn\'t loaded')
}
return this.routerCache
}

View File

@@ -0,0 +1,93 @@
import { DataModel } from '@mcschema/core'
import type { Identifier } from 'deepslate'
import { ChunkPos } from 'deepslate'
import type { mat3 } from 'gl-matrix'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
import type { Color } from '../../Utils.js'
import { computeIfAbsent, iterateWorld2D, randomSeed, stringToColor } from '../../Utils.js'
import { useLocale } from '../../contexts/index.js'
import { useAsync } from '../../hooks/useAsync.js'
import { Btn } from '../index.js'
import { featureColors } from './Decorator.js'
import { DEEPSLATE } from './Deepslate.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
import type { PreviewProps } from './index.js'
export const StructureSetPreview = ({ data, version, shown }: PreviewProps) => {
const { locale } = useLocale()
const [seed, setSeed] = useState(randomSeed())
const state = JSON.stringify(data)
const { value: structureSet } = useAsync(async () => {
await DEEPSLATE.loadVersion(version)
const structureSet = DEEPSLATE.loadStructureSet(DataModel.unwrapLists(data), seed)
return structureSet
}, [state, version, seed])
const { chunkStructures, structureColors } = useMemo(() => {
return {
chunkStructures: new Map<string, Identifier | undefined>(),
structureColors: new Map<string, Color>(),
}
}, [structureSet])
const ctx = useRef<CanvasRenderingContext2D>()
const imageData = useRef<ImageData>()
const [focused, setFocused] = useState<string[]>([])
const onSetup = useCallback(function onSetup(canvas: HTMLCanvasElement) {
const ctx2D = canvas.getContext('2d')
if (!ctx2D) return
ctx.current = ctx2D
}, [])
const onResize = useCallback(function onResize(width: number, height: number) {
if (!ctx.current) return
imageData.current = ctx.current.getImageData(0, 0, width, height)
}, [])
const onDraw = useCallback(function onDraw(transform: mat3) {
if (!ctx.current || !imageData.current || !shown || !structureSet) return
const context = DEEPSLATE.getWorldgenStructureContext()
if (!context) return
iterateWorld2D(imageData.current, transform, (x, y) => {
const pos = ChunkPos.create(x, y)
const structure = computeIfAbsent(chunkStructures, `${pos[0]} ${pos[1]}`, () => structureSet?.getStructureInChunk(pos[0], pos[1], context))
return { structure, pos }
}, ({ structure, pos }) => {
if (structure !== undefined) {
const color = computeIfAbsent(structureColors, structure.toString(), () => {
const index = structureColors.size
return index < featureColors.length ? featureColors[index] : stringToColor(structure.toString())
})
return [0.8 * color[0], 0.8 * color[1], 0.8 * color[2]]
}
if ((Math.floor(pos[0] / 32) + Math.floor(pos[1] / 32)) % 2 === 0) {
return [0.85 * 256, 0.85 * 256, 0.85 * 256]
}
return [256, 256, 256]
})
ctx.current.putImageData(imageData.current, 0, 0)
}, [structureSet, chunkStructures, structureColors, shown])
const onHover = useCallback(function onHover(pos: [number, number] | undefined) {
if (!pos) {
setFocused([])
} else {
const [x, y] = pos
const context = DEEPSLATE.getWorldgenStructureContext()
if (!context) return
const structure = structureSet?.getStructureInChunk(x, -y, context)
setFocused([...(structure ? [structure.toString().replace(/^minecraft:/, '')] : []), `X=${x << 4} Z=${(-y) << 4}`])
}
}, [structureSet])
return <>
<div class="controls preview-controls">
{focused.map(s => <Btn label={s} class="no-pointer" /> )}
<Btn icon="sync" tooltip={locale('generate_new_seed')}
onClick={() => setSeed(randomSeed())} />
</div>
<div class="full-preview">
<InteractiveCanvas2D onSetup={onSetup} onResize={onResize} onDraw={onDraw} onHover={onHover} pixelSize={4} startScale={1/8} minScale={1/32} maxScale={1/2} />
</div>
</>
}

View File

@@ -9,6 +9,7 @@ export * from './LootTablePreview.jsx'
export * from './ModelPreview.jsx'
export * from './NoisePreview.js'
export * from './NoiseSettingsPreview.js'
export * from './StructureSetPreview.jsx'
export type PreviewProps = {
model: DataModel,