From a1b4ab4fb4c4aa6104d582ef74fbede13d5bc96f Mon Sep 17 00:00:00 2001 From: Misode Date: Tue, 1 Dec 2020 01:14:42 +0100 Subject: [PATCH] Rewrite noise settings preview --- src/app/components/Octicon.ts | 4 + src/app/components/panels/PreviewPanel.ts | 35 +++-- src/app/preview/BiomeNoisePreview.ts | 10 +- src/app/preview/NoiseSettingsPreview.ts | 174 ++++++++++++++++------ src/app/preview/Preview.ts | 3 +- src/app/views/View.ts | 4 +- src/locales/en.json | 4 + src/styles/global.css | 11 +- 8 files changed, 184 insertions(+), 61 deletions(-) diff --git a/src/app/components/Octicon.ts b/src/app/components/Octicon.ts index 63965eeb..fdd9486a 100644 --- a/src/app/components/Octicon.ts +++ b/src/app/components/Octicon.ts @@ -1,4 +1,5 @@ export const Octicon = { + arrow_both: '', arrow_left: '', arrow_right: '', chevron_down: '', @@ -7,6 +8,7 @@ export const Octicon = { code: '', dash: '', download: '', + gear: '', globe: '', history: '', info: '', @@ -19,6 +21,8 @@ export const Octicon = { play: '', plus: '', plus_circle: '', + square: '', + square_fill: '', sun: '', tag: '', trashcan: '', diff --git a/src/app/components/panels/PreviewPanel.ts b/src/app/components/panels/PreviewPanel.ts index 683c4235..86dd0b09 100644 --- a/src/app/components/panels/PreviewPanel.ts +++ b/src/app/components/panels/PreviewPanel.ts @@ -7,20 +7,30 @@ import { Octicon } from '../Octicon'; export const PreviewPanel = (view: View, model: DataModel) => { const panel = view.register(el => { - const canvasEl = el.querySelector('canvas')! - const controlsEl = el.querySelector('.panel-controls')! + const canvas = el.querySelector('canvas')! const redraw = () => { const preview = App.preview.get() if (preview && preview.path && preview.path.withModel(model).get()) { - const ctx = canvasEl.getContext('2d')! - const img = ctx.createImageData(200, 100) + const ctx = canvas.getContext('2d')! const newState = preview.path.withModel(model).get() preview.state = JSON.parse(JSON.stringify(newState)) + const [width, height] = preview.getSize() + canvas.width = width + canvas.height = height + const img = ctx.createImageData(width, height) preview.draw(model, img) ctx.putImageData(img, 0, 0) } else { App.preview.set(null) } + + view.mount(el.querySelector('.panel-controls')!, ` + ${App.preview.get()?.menu(view, redraw) ?? ''} +
+ ${Octicon.x} +
`, false) } model.addListener({ invalidated: redraw @@ -44,23 +54,22 @@ export const PreviewPanel = (view: View, model: DataModel) => { ;(el as HTMLCanvasElement).addEventListener('mousemove', evt => { if (dragStart === undefined) return if (App.preview.get()?.onDrag) { - App.preview.get()?.onDrag(dragStart[0], dragStart[1], evt.offsetX, evt.offsetY) - redraw() + const [width, height] = App.preview.get()!.getSize() + const dx = (evt.offsetX - dragStart[0]) * width / canvas.clientWidth + const dy = (evt.offsetY - dragStart[1]) * height / canvas.clientHeight + if (!(dx === 0 && dy === 0)) { + App.preview.get()?.onDrag(dx, dy) + redraw() + } } dragStart = [evt.offsetX, evt.offsetY] }) ;(el as HTMLCanvasElement).addEventListener('mouseup', evt => { dragStart = undefined }) - - view.mount(controlsEl, ` - ${App.preview.get()?.menu(view, redraw) ?? ''} -
${Octicon.x}
`, false) }) return `
- +
` } diff --git a/src/app/preview/BiomeNoisePreview.ts b/src/app/preview/BiomeNoisePreview.ts index ea8e295c..295220a0 100644 --- a/src/app/preview/BiomeNoisePreview.ts +++ b/src/app/preview/BiomeNoisePreview.ts @@ -50,6 +50,10 @@ export class BiomeNoisePreview extends Preview { ` } + getSize(): [number, number] { + return [200, 100] + } + draw(model: DataModel, img: ImageData) { this.noise = BiomeNoisePreview.noiseMaps.map((id, i) => { const config = this.state[`${id}_noise`] @@ -78,9 +82,9 @@ export class BiomeNoisePreview extends Preview { } } - onDrag(fromX: number, fromY: number, toX: number, toY: number) { - this.offsetX += toX - fromX - this.offsetY += toY - fromY + onDrag(dx: number, dy: number) { + this.offsetX += dx + this.offsetY += dy } private closestBiome(x: number, y: number): string { diff --git a/src/app/preview/NoiseSettingsPreview.ts b/src/app/preview/NoiseSettingsPreview.ts index d4db3954..0fac7917 100644 --- a/src/app/preview/NoiseSettingsPreview.ts +++ b/src/app/preview/NoiseSettingsPreview.ts @@ -1,17 +1,23 @@ import SimplexNoise from 'simplex-noise' import { DataModel, Path, ModelPath } from "@mcschema/core" import { Preview } from './Preview' - -const debug = false +import { toggleMenu, View } from '../views/View' +import { Octicon } from '../components/Octicon' export class NoiseSettingsPreview extends Preview { private noise: SimplexNoise - private offsetX: number + private width: number = 512 + private chunkWidth: number = 4 + private chunkHeight: number = 4 + private chunkCountY: number = 32 + private offsetX: number = 0 + private debug: boolean = false + private depth: number = 0.1 + private scale: number = 0.2 constructor() { super() this.noise = new SimplexNoise() - this.offsetX = 0 } getName() { @@ -22,14 +28,69 @@ export class NoiseSettingsPreview extends Preview { return path.endsWith(new Path(['noise'])) } + menu(view: View, redraw: () => void) { + return `
+
+ ${Octicon.kebab_horizontal} +
+
+
+ ${Octicon.gear} + + +
+
+ ${Octicon.gear} + + +
+
+ ${Octicon.arrow_both} + + +
+
+ ${this.debug ? Octicon.square_fill : Octicon.square} + +
+
+
` + } + + getSize(): [number, number] { + return [this.width, this.state.height] + } + draw(model: DataModel, img: ImageData) { + this.chunkWidth = this.state.size_horizontal * 4 + this.chunkHeight = this.state.size_vertical * 4 + this.chunkCountY = Math.floor(this.state.height / this.chunkHeight) + const data = img.data - for (let x = 0; x < 200; x += 1) { - const densities = this.fillNoiseColumn(x - this.offsetX).reverse() - for (let y = 0; y < 100; y += 1) { + for (let x = 0; x < this.width; x += 1) { + const noise = this.iterateNoiseColumn(x - this.offsetX).reverse() + for (let y = 0; y < this.state.height; y += 1) { const i = (y * (img.width * 4)) + (x * 4) - const color = this.getColor(densities, y) - data[i] = (debug && densities[y] > 0) ? 255 : color + const color = this.getColor(noise, y) + data[i] = (this.debug && noise[y] > 0) ? 255 : color data[i + 1] = color data[i + 2] = color data[i + 3] = 255 @@ -37,57 +98,78 @@ export class NoiseSettingsPreview extends Preview { } } - onDrag(fromX: number, fromY: number, toX: number, toY: number) { - this.offsetX += toX - fromX + onDrag(dx: number, dy: number) { + this.offsetX += dx } - private getColor(densities: number[], y: number): number { - if (debug) { - return -densities[y] * 128 + 128 + private getColor(noise: number[], y: number): number { + if (this.debug) { + return -noise[y] / 2 + 128 } - if (densities[y] > 0) { + if (noise[y] > 0) { return 0 } - if (densities[y+1] > 0) { + if (noise[y+1] > 0) { return 150 } return 255 } - private fillNoiseColumn(x: number) { - const data = Array(100) - for (let y = 0; y < 100; y += 1) { - let density = this.getNoise(x, y) - density = density < -1 ? -1 : density > 1 ? 1 : density + private iterateNoiseColumn(x: number): number[] { + const data = Array(this.chunkCountY * this.chunkHeight) + const cx = Math.floor(x / this.chunkWidth) + const ox = Math.floor(x % this.chunkWidth) / this.chunkWidth + const noise1 = this.fillNoiseColumn(cx) + const noise2 = this.fillNoiseColumn(cx + 1) - const heightFactor = (1 - y / 50) * this.state.density_factor + this.state.density_offset - density += heightFactor * (heightFactor > 0 ? 16 : 4) - - if (this.state.top_slide.size > 0) { - density = this.clampedLerp( - this.state.top_slide.target / 100, - density, - (100 - y - (this.state.top_slide.offset * 4)) / (this.state.top_slide.size * 4) - ) + for (let y = this.chunkCountY - 1; y >= 0; y -= 1) { + for (let yy = this.chunkHeight; yy >= 0; yy -= 1) { + const oy = yy / this.chunkHeight + const i = y * this.chunkHeight + yy + data[i] = this.lerp2(oy, ox, noise1[y], noise1[y+1], noise2[y], noise2[y+1]); } - - if (this.state.bottom_slide.size > 0) { - density = this.clampedLerp( - this.state.bottom_slide.target / 100, - density, - (y - (this.state.bottom_slide.offset * 4)) / (this.state.bottom_slide.size * 4) - ) - } - data[y] = density } return data } - private getNoise(x: number, y: number) { + private fillNoiseColumn(x: number): number[] { + const data = Array(this.chunkCountY + 1) + + const scaledDepth = 0.265625 * this.depth + const scaledScale = 96 / this.scale + + for (let y = 0; y <= this.chunkCountY; y += 1) { + let noise = this.getNoise(x, y) + const yOffset = 1 - y * 2 / this.chunkCountY + const density = yOffset * this.state.density_factor + this.state.density_offset + const falloff = (density + scaledDepth) * scaledScale + noise += falloff * (falloff > 0 ? 4 : 1) + + if (this.state.top_slide.size > 0) { + noise = this.clampedLerp( + this.state.top_slide.target, + noise, + (this.chunkCountY - y - (this.state.top_slide.offset)) / (this.state.top_slide.size) + ) + } + + if (this.state.bottom_slide.size > 0) { + noise = this.clampedLerp( + this.state.bottom_slide.target, + noise, + (y - (this.state.bottom_slide.offset)) / (this.state.bottom_slide.size) + ) + } + data[y] = noise + } + return data + } + + private getNoise(x: number, y: number): number { const octaves = [ [64, 1], [32, 2], [16, 4], [8, 8], [4, 16] ] return octaves .map(o => this.noise.noise2D(x / o[0], y / o[0]) / o[1]) - .reduce((prev, acc) => prev + acc) + .reduce((prev, acc) => prev + acc) * 256 } private clampedLerp(a: number, b: number, c: number): number { @@ -96,7 +178,15 @@ export class NoiseSettingsPreview extends Preview { } else if (c > 1) { return b } else { - return a + c * (b - a); + return this.lerp(c, a, b) } } + + public lerp(a: number, b: number, c: number): number { + return b + a * (c - b); + } + + public lerp2(a: number, b: number, c: number, d: number, e: number, f: number): number { + return this.lerp(b, this.lerp(a, c, d), this.lerp(a, e, f)); + } } diff --git a/src/app/preview/Preview.ts b/src/app/preview/Preview.ts index 7b2c6c79..35b97926 100644 --- a/src/app/preview/Preview.ts +++ b/src/app/preview/Preview.ts @@ -13,9 +13,10 @@ export abstract class Preview { return '' } + abstract getSize(): [number, number] abstract getName(): string abstract active(path: ModelPath): boolean abstract draw(model: DataModel, img: ImageData): void - onDrag(fromX: number, fromY: number, toX: number, toY: number): void {} + onDrag(dx: number, dy: number): void {} } diff --git a/src/app/views/View.ts b/src/app/views/View.ts index b4162350..fdc24525 100644 --- a/src/app/views/View.ts +++ b/src/app/views/View.ts @@ -54,8 +54,10 @@ export class View { } export const toggleMenu = (el: Element) => { - el.classList.toggle('active') + el.classList.add('active') document.body.addEventListener('click', evt => { + if ((evt.target as Element).matches('.btn.input')) return + if ((evt.target as Element).closest('.btn')?.classList.contains('input')) return el.classList.remove('active') }, { capture: true, once: true }) } diff --git a/src/locales/en.json b/src/locales/en.json index 422eb762..632446f8 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -15,6 +15,10 @@ "title.home": "Data Pack Generators", "title.suffix": "%0% Minecraft 1.16, 1.17", "preview": "Visualize", + "preview.show_density": "Show Density", + "preview.scale": "Scale", + "preview.depth": "Depth", + "preview.width": "Width", "undo": "Undo", "world": "World Settings", "worldgen/biome": "Biome", diff --git a/src/styles/global.css b/src/styles/global.css index b7c536a1..db2fd9c3 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -311,7 +311,7 @@ nav > .toggle span { transition: background-color var(--style-transition); } -.btn:hover { +.btn:not(.input):hover { background-color: var(--btn-hover); } @@ -335,6 +335,15 @@ nav > .toggle span { color: var(--btn-active); } +.btn.input { + cursor: initial; +} + +.btn input { + margin-left: 5px; + width: 100px; +} + .errors { position: fixed; display: flex;