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:
Misode
2023-01-26 01:21:02 +01:00
committed by GitHub
parent 23b3046dee
commit 00029a2010
32 changed files with 996 additions and 1085 deletions
-1
View File
@@ -1,7 +1,6 @@
export * from './useActiveTimout.js'
export * from './useAsync.js'
export * from './useAsyncFn.js'
export * from './useCanvas.js'
export * from './useFocus.js'
export * from './useHash.js'
export * from './useLocalStorage.js'
-99
View File
@@ -1,99 +0,0 @@
import type { Inputs } from 'preact/hooks'
import { useEffect, useRef } from 'preact/hooks'
type Vec2 = [number, number]
export function useCanvas({ size, draw, onDrag, onHover, onLeave }: {
size: () => Vec2,
draw: (img: ImageData) => Promise<unknown>,
onDrag?: (dx: number, dy: number) => Promise<unknown>,
onHover?: (x: number, y: number) => unknown,
onLeave?: () => unknown,
}, inputs?: Inputs) {
const canvas = useRef<HTMLCanvasElement>(null)
const dragStart = useRef<Vec2 | undefined>()
const dragRequest = useRef<number>()
const dragPending = useRef<Vec2>([0, 0])
const dragBusy = useRef(false)
useEffect(() => {
if (!canvas.current) return
const onMouseDown = (e: MouseEvent) => {
dragStart.current = [e.offsetX, e.offsetY]
}
const onMouseMove = (e: MouseEvent) => {
if (dragStart.current === undefined) {
if (!canvas.current) return
const x = e.offsetX / canvas.current.clientWidth
const y = e.offsetY / canvas.current.clientHeight
onHover?.(x, y)
return
}
if (!onDrag) return
const dx = e.offsetX - dragStart.current[0]
const dy = e.offsetY - dragStart.current[1]
if (!(dx === 0 && dy === 0)) {
dragPending.current = [dragPending.current[0] + dx, dragPending.current[1] + dy]
if (!dragBusy.current) {
if (dragRequest.current) {
cancelAnimationFrame(dragRequest.current)
}
dragRequest.current = requestAnimationFrame(async () => {
if (!canvas.current) return
dragBusy.current = true
const dx = dragPending.current[0] / canvas.current.clientWidth
const dy = dragPending.current[1] / canvas.current.clientHeight
dragPending.current = [0, 0]
await onDrag?.(dx, dy)
dragBusy.current = false
})
}
}
dragStart.current = [e.offsetX, e.offsetY]
}
const onMouseUp = () => {
dragStart.current = undefined
}
const onMouseLeave = () => {
onLeave?.()
}
canvas.current.addEventListener('mousedown', onMouseDown)
canvas.current.addEventListener('mousemove', onMouseMove)
canvas.current.addEventListener('mouseleave', onMouseLeave)
document.body.addEventListener('mouseup', onMouseUp)
return () => {
canvas.current?.removeEventListener('mousedown', onMouseDown)
canvas.current?.removeEventListener('mousemove', onMouseMove)
canvas.current?.removeEventListener('mouseleave', onMouseLeave)
document.body.removeEventListener('mouseup', onMouseUp)
}
}, [...inputs ?? [], canvas.current])
const redraw = useRef<() => Promise<unknown>>()
const redrawCount = useRef(0)
redraw.current = async () => {
if (!canvas.current) return
const ctx = canvas.current.getContext('2d')!
const s = size()
canvas.current.width = s[0]
canvas.current.height = s[1]
const img = ctx.getImageData(0, 0, s[0], s[1])
const ownCount = redrawCount.current += 1
try {
await draw(img)
} catch (e) {
throw e
}
if (ownCount === redrawCount.current) {
ctx.putImageData(img, 0, 0)
}
}
return {
canvas,
redraw: redraw.current,
}
}