|
1 | 1 | <script lang="ts" module> |
2 | | - export const SupportedPreviewKinds = ['Noise']; |
| 2 | + export const SupportedPreviewKinds = ['Noise', 'DensityFunction']; |
3 | 3 | </script> |
4 | 4 |
|
5 | 5 | <script lang="ts"> |
6 | 6 | import type { MmsSymbol } from '@minecraftmetascript/mms-wasm'; |
7 | 7 |
|
8 | | - import * as deepslate from 'deepslate'; |
9 | | - import { debounce } from 'es-toolkit'; |
10 | | - import type { Action } from 'svelte/action'; |
| 8 | + import { MMSProject } from '../MMSProject.svelte'; |
| 9 | + import type { MouseEventHandler } from 'svelte/elements'; |
| 10 | + import { Icon } from '@steeze-ui/svelte-icon'; |
| 11 | + import { |
| 12 | + ArrowDown, |
| 13 | + ArrowLeft, |
| 14 | + ArrowRight, |
| 15 | + ArrowUp, |
| 16 | + Lock, |
| 17 | + Minus, |
| 18 | + Plus |
| 19 | + } from '@steeze-ui/tabler-icons'; |
| 20 | + import { deepslateCanvas } from './deepslate.svelte'; |
| 21 | + import type { ValueLookupFn } from './types'; |
11 | 22 |
|
12 | | - let { symbol }: { symbol: MmsSymbol } = $props(); |
| 23 | + let { symbol }: { symbol: MmsSymbol; project: MMSProject } = $props(); |
| 24 | + let seed = [ |
| 25 | + BigInt(Math.floor(performance.now())), |
| 26 | + BigInt(Math.floor(performance.timeOrigin)) |
| 27 | + ] as [bigint, bigint]; |
13 | 28 |
|
14 | | - const deepslatePreview: Action<HTMLCanvasElement, MmsSymbol> = ( |
15 | | - canvas: HTMLCanvasElement, |
16 | | - s: MmsSymbol |
17 | | - ) => { |
18 | | - let symbol = s; |
| 29 | + let scale = $state(20); |
| 30 | + let originX = $state(0); |
| 31 | + let originY = $state(0); |
| 32 | + let origin = $derived({ x: originX, y: originY }); |
| 33 | + let worldHeight = $state(256); |
19 | 34 |
|
20 | | - const update = debounce(() => { |
21 | | - const random = new deepslate.XoroshiroRandom([ |
22 | | - BigInt(Math.floor(performance.now())), |
23 | | - BigInt(Math.floor(performance.timeOrigin)) |
24 | | - ]); |
| 35 | + let mousePos = $state<{ x: number; y: number } | null>(null); |
| 36 | + let valueLookupFn = $state<ValueLookupFn | null>(null); |
| 37 | + let lookupLabel = $derived.by(() => (mousePos && valueLookupFn ? valueLookupFn(mousePos) : null)); |
25 | 38 |
|
26 | | - if (!canvas) { |
27 | | - console.warn('No Canvas'); |
28 | | - return; |
29 | | - } |
30 | | - if (!symbol || !symbol.value) { |
31 | | - console.warn('No Symbol or Value'); |
32 | | - return; |
33 | | - } |
34 | | - const ctx = canvas.getContext('2d'); |
35 | | - if (!ctx) { |
36 | | - console.warn('No Canvas 2d Context'); |
37 | | - return; |
38 | | - } |
39 | | - // TypeScript |
40 | | - const dpr = window.devicePixelRatio || 1; |
41 | | - const rect = canvas.getBoundingClientRect(); |
42 | | -
|
43 | | - // Set intrinsic size to match CSS size * DPR |
44 | | - canvas.width = Math.floor(rect.width * dpr); |
45 | | - canvas.height = Math.floor(rect.height * dpr); |
46 | | -
|
47 | | - // Scale the context so 1 unit = 1 CSS pixel |
48 | | - ctx.setTransform(dpr, 0, 0, dpr, 0, 0); |
49 | | - ctx.scale(3, 3); |
50 | | -
|
51 | | - switch (symbol.type) { |
52 | | - case 'Noise': { |
53 | | - requestAnimationFrame(() => { |
54 | | - try { |
55 | | - console.log(symbol.value); |
56 | | - const normalNoise = new deepslate.NormalNoise(random, symbol.value as any); |
57 | | - for (let pixelX = 0; pixelX < rect.width; pixelX += 1) { |
58 | | - for (let pixelY = 0; pixelY < rect.height; pixelY += 1) { |
59 | | - const val = normalNoise.sample(pixelX, pixelY, 0); |
60 | | - const color = `hsl(0, ${val * 50 + 50}%, ${val * 20 + 40}%)`; |
61 | | - ctx.fillStyle = color; |
62 | | - ctx.fillRect(pixelX, pixelY, 1, 1); |
63 | | - } |
64 | | - } |
65 | | - } catch (e) { |
66 | | - console.warn(e); |
67 | | - } |
68 | | - }); |
69 | | - } |
70 | | - } |
71 | | - }, 250); |
72 | | -
|
73 | | - update(); |
74 | | -
|
75 | | - window.addEventListener('resize', update); |
76 | | -
|
77 | | - return { |
78 | | - update(next: MmsSymbol) { |
79 | | - symbol = next; |
80 | | - update(); |
81 | | - }, |
82 | | - destroy() { |
83 | | - window.removeEventListener('resize', update); |
84 | | - } |
| 39 | + let mouseLocked = $state(false); |
| 40 | + const updateMousePos: MouseEventHandler<HTMLCanvasElement> = (e) => { |
| 41 | + if (mouseLocked) return; |
| 42 | + const box = e.currentTarget.getBoundingClientRect(); |
| 43 | + mousePos = { |
| 44 | + x: Math.floor((e.pageX - box.left) / scale), |
| 45 | + y: Math.floor((box.height - (e.pageY - box.top)) / scale) |
85 | 46 | }; |
86 | 47 | }; |
| 48 | + const clearMousePos = () => { |
| 49 | + if (mouseLocked) return; |
| 50 | + mousePos = null; |
| 51 | + }; |
| 52 | +
|
| 53 | + const toggleMouseLock = () => { |
| 54 | + mouseLocked = !mouseLocked; |
| 55 | + }; |
87 | 56 | </script> |
88 | 57 |
|
89 | | -<canvas class="h-full w-full" use:deepslatePreview={symbol}></canvas> |
| 58 | +<div class="relative h-full w-full"> |
| 59 | + {#if mousePos || lookupLabel} |
| 60 | + <div |
| 61 | + class="absolute top-2 right-2 flex items-center gap-2 bg-slate-700 px-2 py-1 font-mono text-sm text-slate-50" |
| 62 | + > |
| 63 | + {#if mouseLocked} |
| 64 | + <Icon src={Lock} class="w-4" /> |
| 65 | + {/if} |
| 66 | + {#if mousePos} |
| 67 | + <span>X: {mousePos?.x + origin.x}</span> |
| 68 | + <span>Y: {mousePos?.y + origin.y}</span> |
| 69 | + {/if} |
| 70 | + {#if lookupLabel} |
| 71 | + <span>{lookupLabel?.label}: {lookupLabel?.value}</span> |
| 72 | + {/if} |
| 73 | + </div> |
| 74 | + {/if} |
| 75 | + <div class="absolute top-2 left-2 flex flex-col items-center gap-2"> |
| 76 | + <button class="bg-slate-600/50 text-slate-50 hover:bg-slate-600" onclick={() => scale++}> |
| 77 | + <Icon src={Plus} class="w-6" /> |
| 78 | + </button> |
| 79 | + <button class="bg-slate-600/50 text-slate-50 hover:bg-slate-600" onclick={() => scale--}> |
| 80 | + <Icon src={Minus} class="w-6" /> |
| 81 | + </button> |
| 82 | + </div> |
| 83 | + |
| 84 | + <div class="absolute bottom-2 left-2 grid grid-cols-3 grid-rows-3"> |
| 85 | + <button |
| 86 | + class="col-start-2 row-start-1 bg-slate-600/50 text-slate-50 hover:bg-slate-600" |
| 87 | + onclick={() => (originY += scale * 5)} |
| 88 | + > |
| 89 | + <Icon src={ArrowUp} class="w-6" /> |
| 90 | + </button> |
| 91 | + <button |
| 92 | + class="col-start-1 row-start-2 bg-slate-600/50 text-slate-50 hover:bg-slate-600" |
| 93 | + onclick={() => (originX -= scale * 5)} |
| 94 | + > |
| 95 | + <Icon src={ArrowLeft} class="w-6" /> |
| 96 | + </button> |
| 97 | + <button |
| 98 | + class="col-start-3 row-start-2 bg-slate-600/50 text-slate-50 hover:bg-slate-600" |
| 99 | + onclick={() => (originX += scale * 5)} |
| 100 | + > |
| 101 | + <Icon src={ArrowRight} class="w-6" /> |
| 102 | + </button> |
| 103 | + <button |
| 104 | + class="col-start-2 row-start-3 bg-slate-600/50 text-slate-50 hover:bg-slate-600" |
| 105 | + onclick={() => (originY -= scale * 5)} |
| 106 | + > |
| 107 | + <Icon src={ArrowDown} class="w-6" /> |
| 108 | + </button> |
| 109 | + </div> |
| 110 | + <canvas |
| 111 | + class="h-full w-full cursor-pointer" |
| 112 | + {@attach deepslateCanvas({ |
| 113 | + symbol, |
| 114 | + seed, |
| 115 | + scale, |
| 116 | + origin, |
| 117 | + setLookup(fn) { |
| 118 | + valueLookupFn = fn; |
| 119 | + } |
| 120 | + })} |
| 121 | + onmousemove={updateMousePos} |
| 122 | + onmouseout={clearMousePos} |
| 123 | + onblur={clearMousePos} |
| 124 | + onclick={toggleMouseLock} |
| 125 | + onmousedown={() => { |
| 126 | + originX++; |
| 127 | + }} |
| 128 | + ></canvas> |
| 129 | +</div> |
0 commit comments