mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-27 08:48:46 +00:00
Voxel rendering + refactor interactive canvas (#322)
* Add voxel rendering to density function preview * InteractiveCanvas component * Use interactive canvas for noise preview * Use interactive canvas for noise settings preview * Extract common iterateWorld2D logic * Use InteractiveCanvas2D for biome source preview * Display final density in noise settings preview hover * Move remaining preview code * Hide noise router info for checkerboard and fixed * Add higher resolution biome map * User interactive canvas for decorator preview
This commit is contained in:
@@ -1,42 +1,86 @@
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { BlockPos, ChunkPos, LegacyRandom, PerlinNoise } from 'deepslate'
|
||||
import type { mat3 } from 'gl-matrix'
|
||||
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
|
||||
import { useLocale } from '../../contexts/index.js'
|
||||
import { useCanvas } from '../../hooks/index.js'
|
||||
import { decorator } from '../../previews/index.js'
|
||||
import { randomSeed } from '../../Utils.js'
|
||||
import { computeIfAbsent, iterateWorld2D, randomSeed } from '../../Utils.js'
|
||||
import { Btn } from '../index.js'
|
||||
import type { PlacedFeature, PlacementContext } from './Decorator.js'
|
||||
import { decorateChunk } from './Decorator.js'
|
||||
import type { PreviewProps } from './index.js'
|
||||
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
|
||||
|
||||
export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => {
|
||||
const { locale } = useLocale()
|
||||
const [scale, setScale] = useState(4)
|
||||
const [seed, setSeed] = useState(randomSeed())
|
||||
|
||||
const state = JSON.stringify(data)
|
||||
|
||||
const { canvas, redraw } = useCanvas({
|
||||
size() {
|
||||
return [scale * 16, scale * 16]
|
||||
},
|
||||
async draw(img) {
|
||||
decorator(data, img, { seed, version, size: [scale * 16, 128, scale * 16] })
|
||||
},
|
||||
}, [version, state, seed])
|
||||
|
||||
useEffect(() => {
|
||||
if (shown) {
|
||||
redraw()
|
||||
const { context, chunkFeatures } = useMemo(() => {
|
||||
const random = new LegacyRandom(seed)
|
||||
const context: PlacementContext = {
|
||||
placements: [],
|
||||
features: [],
|
||||
random,
|
||||
biomeInfoNoise: new PerlinNoise(random.fork(), 0, [1]),
|
||||
seaLevel: 63,
|
||||
version: version,
|
||||
nextFloat: () => random.nextFloat(),
|
||||
nextInt: (max: number) => random.nextInt(max),
|
||||
nextGaussian: () => Math.sqrt(-2 * Math.log(1 - random.nextFloat())) * Math.cos(2 * Math.PI * random.nextFloat()),
|
||||
}
|
||||
}, [version, state, scale, seed, shown])
|
||||
return {
|
||||
context,
|
||||
chunkFeatures: new Map<string, PlacedFeature[]>(),
|
||||
}
|
||||
}, [state, version, seed])
|
||||
|
||||
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) return
|
||||
|
||||
iterateWorld2D(imageData.current, transform, (x, y) => {
|
||||
const pos = ChunkPos.create(Math.floor(x / 16), Math.floor(-y / 16))
|
||||
const features = computeIfAbsent(chunkFeatures, `${pos[0]} ${pos[1]}`, () => decorateChunk(pos, data, context))
|
||||
return features.find(f => f.pos[0] === x && f.pos[2] == -y) ?? { pos: BlockPos.create(x, 0, -y) }
|
||||
}, (feature) => {
|
||||
if ('color' in feature) {
|
||||
return feature.color
|
||||
}
|
||||
if ((Math.floor(feature.pos[0] / 16) + Math.floor(feature.pos[2] / 16)) % 2 === 0) {
|
||||
return [0.85 * 256, 0.85 * 256, 0.85 * 256]
|
||||
}
|
||||
return [256, 256, 256]
|
||||
})
|
||||
ctx.current.putImageData(imageData.current, 0, 0)
|
||||
}, [context, chunkFeatures, shown])
|
||||
const onHover = useCallback(function onHover(pos: [number, number] | undefined) {
|
||||
if (!pos) {
|
||||
setFocused([])
|
||||
} else {
|
||||
const [x, y] = pos
|
||||
setFocused([`X=${x} Z=${-y}`])
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <>
|
||||
<div class="controls preview-controls">
|
||||
<Btn icon="dash" tooltip={locale('zoom_out')}
|
||||
onClick={() => setScale(Math.min(16, scale + 1))} />
|
||||
<Btn icon="plus" tooltip={locale('zoom_in')}
|
||||
onClick={() => setScale(Math.max(1, scale - 1))} />
|
||||
{focused.map(s => <Btn label={s} class="no-pointer" /> )}
|
||||
<Btn icon="sync" tooltip={locale('generate_new_seed')}
|
||||
onClick={() => setSeed(randomSeed())} />
|
||||
</div>
|
||||
<canvas ref={canvas} width="64" height="64"></canvas>
|
||||
<div class="full-preview">
|
||||
<InteractiveCanvas2D onSetup={onSetup} onResize={onResize} onDraw={onDraw} onHover={onHover} pixelSize={4} startScale={1/8} minScale={1/32} maxScale={1} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user