From b37b9a19cbc73b0bd7c58d334ca5e4e89de8a8e9 Mon Sep 17 00:00:00 2001 From: Misode Date: Sat, 8 Aug 2020 02:02:10 +0200 Subject: [PATCH] Add NoiseSettingsVisualizer --- src/app/app.ts | 8 +- src/app/visualization/BiomeNoiseVisualizer.ts | 31 +++-- .../visualization/NoiseSettingsVisualizer.ts | 108 ++++++++++++++++++ src/app/visualization/Visualizer.ts | 26 +++++ src/app/visualization/VisualizerView.ts | 28 ++--- 5 files changed, 163 insertions(+), 38 deletions(-) create mode 100644 src/app/visualization/NoiseSettingsVisualizer.ts create mode 100644 src/app/visualization/Visualizer.ts diff --git a/src/app/app.ts b/src/app/app.ts index bdac668a..7c2ffd4c 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,6 +1,5 @@ import Split from 'split.js' import { - AbstractView, Base, DataModel, locale, @@ -8,10 +7,10 @@ import { ModelPath, SourceView, TreeView, - Path, } from '@mcschema/core' import { getCollections, getSchemas } from '@mcschema/java-1.16' import { VisualizerView } from './visualization/VisualizerView' +import { Visualizer } from './visualization/Visualizer' import { RegistryFetcher } from './RegistryFetcher' import { ErrorsView } from './ErrorsView' import config from '../config.json' @@ -64,9 +63,8 @@ const treeViewObserver = (el: HTMLElement) => { } const treeViewNodeInjector = (path: ModelPath, view: TreeView) => { - return Object.keys(VisualizerView.visualizers) - .map(id => VisualizerView.visualizers[id]) - .filter(v => path.equals(v.path())) + return Visualizer.visualizers + .filter(v => v.onPath(path)) .filter(v => v.active(path.getModel())) .map(v => { const id = view.registerClick(() => { diff --git a/src/app/visualization/BiomeNoiseVisualizer.ts b/src/app/visualization/BiomeNoiseVisualizer.ts index 96e644a2..787430a7 100644 --- a/src/app/visualization/BiomeNoiseVisualizer.ts +++ b/src/app/visualization/BiomeNoiseVisualizer.ts @@ -1,36 +1,34 @@ import SimplexNoise from 'simplex-noise' -import { DataModel, Path } from "@mcschema/core"; -import { Visualizer } from './VisualizerView'; +import { DataModel, Path } from "@mcschema/core" +import { Visualizer } from './Visualizer' -export class BiomeNoiseVisualizer implements Visualizer { +export class BiomeNoiseVisualizer extends Visualizer { static readonly noiseMaps = ['altitude', 'temperature', 'humidity', 'weirdness'] + static readonly path = new Path(['generator', 'biome_source']) private noise: SimplexNoise[] - private biomeSource: any private offsetX: number = 0 private offsetY: number = 0 constructor() { + super() this.noise = BiomeNoiseVisualizer.noiseMaps.map(e => new SimplexNoise()) } - path() { - return new Path(['generator', 'biome_source']) + onPath(path: Path) { + return path.equals(BiomeNoiseVisualizer.path) } active(model: DataModel) { - const biomeSource = new Path(['generator', 'biome_source']) - return model.get(biomeSource) !== undefined - && model.get(biomeSource.push('type')) === 'minecraft:multi_noise' + return model.get(BiomeNoiseVisualizer.path) !== undefined + && model.get(BiomeNoiseVisualizer.path.push('type')) === 'minecraft:multi_noise' } - dirty(model: DataModel) { - return JSON.stringify(this.biomeSource) - !== JSON.stringify(model.get(new Path(['generator', 'biome_source']))) + getState(model: DataModel) { + return model.get(BiomeNoiseVisualizer.path) } draw(model: DataModel, img: ImageData) { - this.biomeSource = JSON.parse(JSON.stringify(model.get(new Path(['generator', 'biome_source'])))) const data = img.data for (let x = 0; x < 200; x += 1) { for (let y = 0; y < 100; y += 1) { @@ -48,14 +46,13 @@ export class BiomeNoiseVisualizer implements Visualizer { onDrag(from: number[], to: number[]) { this.offsetX += (to[0] - from[0]) / 128 this.offsetY += (to[1] - from[1]) / 128 - this.biomeSource = {} } private closestBiome(x: number, y: number): string { const noise = this.getNoise(x, y) - if (!this.biomeSource.biomes) return '' + if (!this.state.biomes) return '' - return this.biomeSource.biomes + return this.state.biomes .map((b: any) => ({ biome: b.biome ?? '', distance: this.distance([...noise, 0], [...BiomeNoiseVisualizer.noiseMaps.map(s => b.parameters[s]), b.parameters.offset]) @@ -74,7 +71,7 @@ export class BiomeNoiseVisualizer implements Visualizer { private getNoise(x: number, y: number): number[] { return BiomeNoiseVisualizer.noiseMaps.map((id, index) => { - const config = this.biomeSource[`${id}_noise`] + const config = this.state[`${id}_noise`] let n = 0 let scale = 2**config.firstOctave for (let i = 0; i < config.amplitudes.length; i++) { diff --git a/src/app/visualization/NoiseSettingsVisualizer.ts b/src/app/visualization/NoiseSettingsVisualizer.ts new file mode 100644 index 00000000..87c7a4eb --- /dev/null +++ b/src/app/visualization/NoiseSettingsVisualizer.ts @@ -0,0 +1,108 @@ +import SimplexNoise from 'simplex-noise' +import { DataModel, Path } from "@mcschema/core" +import { Visualizer } from './Visualizer' + +const debug = false + +export class NoiseSettingsVisualizer extends Visualizer { + private noise: SimplexNoise + private offsetX: number + + constructor() { + super() + this.noise = new SimplexNoise() + this.offsetX = 0 + } + + onPath(path: Path) { + return path.equals(new Path(['generator', 'settings', 'noise'])) + || path.equals(new Path(['noise'])) + } + + active(model: DataModel) { + return true + } + + getState(model: DataModel) { + return model.get(new Path(['generator', 'settings', 'noise'])) + ?? model.get(new Path(['noise'])) + } + + onDrag(from: number[], to: number[]) { + this.offsetX += (to[0] - from[0]) + } + + draw(model: DataModel, img: ImageData) { + 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) { + const i = (y * (img.width * 4)) + (x * 4) + const color = this.getColor(densities, y) + data[i] = (debug && densities[y] > 0) ? 255 : color + data[i + 1] = color + data[i + 2] = color + data[i + 3] = 255 + } + } + } + + private getColor(densities: number[], y: number): number { + if (debug) { + return -densities[y] * 128 + 128 + } + if (densities[y] > 0) { + return 0 + } + if (densities[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 + + 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) + ) + } + + 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) { + 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) + } + + private clampedLerp(a: number, b: number, c: number): number { + if (c < 0) { + return a; + } else if (c > 1) { + return b + } else { + return a + c * (b - a); + } + } +} diff --git a/src/app/visualization/Visualizer.ts b/src/app/visualization/Visualizer.ts new file mode 100644 index 00000000..e697486d --- /dev/null +++ b/src/app/visualization/Visualizer.ts @@ -0,0 +1,26 @@ +import { DataModel, Path } from "@mcschema/core" +import { BiomeNoiseVisualizer } from "./BiomeNoiseVisualizer" +import { NoiseSettingsVisualizer } from "./NoiseSettingsVisualizer" + +export abstract class Visualizer { + state: any + + dirty(model: DataModel): boolean { + return JSON.stringify(this.state) !== JSON.stringify(this.getState(model)) + } + + active(model: DataModel): boolean { + return true + } + + abstract onPath(path: Path): boolean + abstract getState(model: DataModel): any + abstract draw(model: DataModel, img: ImageData): void + + onDrag(from: number[], to: number[]): void {} + + static visualizers: Visualizer[] = [ + new BiomeNoiseVisualizer(), + new NoiseSettingsVisualizer() + ] +} diff --git a/src/app/visualization/VisualizerView.ts b/src/app/visualization/VisualizerView.ts index b8658119..51602312 100644 --- a/src/app/visualization/VisualizerView.ts +++ b/src/app/visualization/VisualizerView.ts @@ -1,13 +1,7 @@ -import { AbstractView, DataModel, Path } from "@mcschema/core"; -import { BiomeNoiseVisualizer } from "./BiomeNoiseVisualizer"; - -export interface Visualizer { - path(): Path - active(model: DataModel): boolean - dirty(model: DataModel): boolean - draw(model: DataModel, img: ImageData): void - onDrag?(from: number[], to: number[]): void -} +import { AbstractView, DataModel, Path } from "@mcschema/core" +import { BiomeNoiseVisualizer } from "./BiomeNoiseVisualizer" +import { NoiseSettingsVisualizer } from "./NoiseSettingsVisualizer" +import { Visualizer } from "./Visualizer" export class VisualizerView extends AbstractView { ctx: CanvasRenderingContext2D @@ -34,9 +28,10 @@ export class VisualizerView extends AbstractView { if (this.dragStart === undefined) return if (this.visualizer?.onDrag) { this.visualizer.onDrag(this.dragStart, [evt.offsetX, evt.offsetY]) + this.visualizer.state = {} + this.invalidated() } this.dragStart = [evt.offsetX, evt.offsetY] - this.invalidated() }) canvas.addEventListener('mouseup', evt => { this.dragStart = undefined @@ -44,9 +39,13 @@ export class VisualizerView extends AbstractView { } invalidated() { - if (this.active && this.visualizer && this.visualizer.active(this.model)) { + let newState: any + if (this.active && this.visualizer + && this.visualizer.active(this.model) + && (newState = this.visualizer.getState(this.model))) { if (this.visualizer.dirty(this.model)) { const img = this.ctx.createImageData(200, 100) + this.visualizer.state = JSON.parse(JSON.stringify(newState)) this.visualizer.draw(this.model, img) this.ctx.putImageData(img, 0, 0) } @@ -68,10 +67,7 @@ export class VisualizerView extends AbstractView { set(visualizer: Visualizer) { this.active = true this.visualizer = visualizer + this.visualizer.state = undefined this.invalidated() } - - static visualizers: {[key: string]: Visualizer} = { - 'biome-noise': new BiomeNoiseVisualizer() - } }