Add NoiseSettingsVisualizer

This commit is contained in:
Misode
2020-08-08 02:02:10 +02:00
parent 2f8f42bd8e
commit b37b9a19cb
5 changed files with 163 additions and 38 deletions

View File

@@ -1,6 +1,5 @@
import Split from 'split.js' import Split from 'split.js'
import { import {
AbstractView,
Base, Base,
DataModel, DataModel,
locale, locale,
@@ -8,10 +7,10 @@ import {
ModelPath, ModelPath,
SourceView, SourceView,
TreeView, TreeView,
Path,
} from '@mcschema/core' } from '@mcschema/core'
import { getCollections, getSchemas } from '@mcschema/java-1.16' import { getCollections, getSchemas } from '@mcschema/java-1.16'
import { VisualizerView } from './visualization/VisualizerView' import { VisualizerView } from './visualization/VisualizerView'
import { Visualizer } from './visualization/Visualizer'
import { RegistryFetcher } from './RegistryFetcher' import { RegistryFetcher } from './RegistryFetcher'
import { ErrorsView } from './ErrorsView' import { ErrorsView } from './ErrorsView'
import config from '../config.json' import config from '../config.json'
@@ -64,9 +63,8 @@ const treeViewObserver = (el: HTMLElement) => {
} }
const treeViewNodeInjector = (path: ModelPath, view: TreeView) => { const treeViewNodeInjector = (path: ModelPath, view: TreeView) => {
return Object.keys(VisualizerView.visualizers) return Visualizer.visualizers
.map(id => VisualizerView.visualizers[id]) .filter(v => v.onPath(path))
.filter(v => path.equals(v.path()))
.filter(v => v.active(path.getModel())) .filter(v => v.active(path.getModel()))
.map(v => { .map(v => {
const id = view.registerClick(() => { const id = view.registerClick(() => {

View File

@@ -1,36 +1,34 @@
import SimplexNoise from 'simplex-noise' import SimplexNoise from 'simplex-noise'
import { DataModel, Path } from "@mcschema/core"; import { DataModel, Path } from "@mcschema/core"
import { Visualizer } from './VisualizerView'; import { Visualizer } from './Visualizer'
export class BiomeNoiseVisualizer implements Visualizer { export class BiomeNoiseVisualizer extends Visualizer {
static readonly noiseMaps = ['altitude', 'temperature', 'humidity', 'weirdness'] static readonly noiseMaps = ['altitude', 'temperature', 'humidity', 'weirdness']
static readonly path = new Path(['generator', 'biome_source'])
private noise: SimplexNoise[] private noise: SimplexNoise[]
private biomeSource: any
private offsetX: number = 0 private offsetX: number = 0
private offsetY: number = 0 private offsetY: number = 0
constructor() { constructor() {
super()
this.noise = BiomeNoiseVisualizer.noiseMaps.map(e => new SimplexNoise()) this.noise = BiomeNoiseVisualizer.noiseMaps.map(e => new SimplexNoise())
} }
path() { onPath(path: Path) {
return new Path(['generator', 'biome_source']) return path.equals(BiomeNoiseVisualizer.path)
} }
active(model: DataModel) { active(model: DataModel) {
const biomeSource = new Path(['generator', 'biome_source']) return model.get(BiomeNoiseVisualizer.path) !== undefined
return model.get(biomeSource) !== undefined && model.get(BiomeNoiseVisualizer.path.push('type')) === 'minecraft:multi_noise'
&& model.get(biomeSource.push('type')) === 'minecraft:multi_noise'
} }
dirty(model: DataModel) { getState(model: DataModel) {
return JSON.stringify(this.biomeSource) return model.get(BiomeNoiseVisualizer.path)
!== JSON.stringify(model.get(new Path(['generator', 'biome_source'])))
} }
draw(model: DataModel, img: ImageData) { draw(model: DataModel, img: ImageData) {
this.biomeSource = JSON.parse(JSON.stringify(model.get(new Path(['generator', 'biome_source']))))
const data = img.data const data = img.data
for (let x = 0; x < 200; x += 1) { for (let x = 0; x < 200; x += 1) {
for (let y = 0; y < 100; y += 1) { for (let y = 0; y < 100; y += 1) {
@@ -48,14 +46,13 @@ export class BiomeNoiseVisualizer implements Visualizer {
onDrag(from: number[], to: number[]) { onDrag(from: number[], to: number[]) {
this.offsetX += (to[0] - from[0]) / 128 this.offsetX += (to[0] - from[0]) / 128
this.offsetY += (to[1] - from[1]) / 128 this.offsetY += (to[1] - from[1]) / 128
this.biomeSource = {}
} }
private closestBiome(x: number, y: number): string { private closestBiome(x: number, y: number): string {
const noise = this.getNoise(x, y) 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) => ({ .map((b: any) => ({
biome: b.biome ?? '', biome: b.biome ?? '',
distance: this.distance([...noise, 0], [...BiomeNoiseVisualizer.noiseMaps.map(s => b.parameters[s]), b.parameters.offset]) 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[] { private getNoise(x: number, y: number): number[] {
return BiomeNoiseVisualizer.noiseMaps.map((id, index) => { return BiomeNoiseVisualizer.noiseMaps.map((id, index) => {
const config = this.biomeSource[`${id}_noise`] const config = this.state[`${id}_noise`]
let n = 0 let n = 0
let scale = 2**config.firstOctave let scale = 2**config.firstOctave
for (let i = 0; i < config.amplitudes.length; i++) { for (let i = 0; i < config.amplitudes.length; i++) {

View File

@@ -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);
}
}
}

View File

@@ -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()
]
}

View File

@@ -1,13 +1,7 @@
import { AbstractView, DataModel, Path } from "@mcschema/core"; import { AbstractView, DataModel, Path } from "@mcschema/core"
import { BiomeNoiseVisualizer } from "./BiomeNoiseVisualizer"; import { BiomeNoiseVisualizer } from "./BiomeNoiseVisualizer"
import { NoiseSettingsVisualizer } from "./NoiseSettingsVisualizer"
export interface Visualizer { import { Visualizer } from "./Visualizer"
path(): Path
active(model: DataModel): boolean
dirty(model: DataModel): boolean
draw(model: DataModel, img: ImageData): void
onDrag?(from: number[], to: number[]): void
}
export class VisualizerView extends AbstractView { export class VisualizerView extends AbstractView {
ctx: CanvasRenderingContext2D ctx: CanvasRenderingContext2D
@@ -34,9 +28,10 @@ export class VisualizerView extends AbstractView {
if (this.dragStart === undefined) return if (this.dragStart === undefined) return
if (this.visualizer?.onDrag) { if (this.visualizer?.onDrag) {
this.visualizer.onDrag(this.dragStart, [evt.offsetX, evt.offsetY]) this.visualizer.onDrag(this.dragStart, [evt.offsetX, evt.offsetY])
this.visualizer.state = {}
this.invalidated()
} }
this.dragStart = [evt.offsetX, evt.offsetY] this.dragStart = [evt.offsetX, evt.offsetY]
this.invalidated()
}) })
canvas.addEventListener('mouseup', evt => { canvas.addEventListener('mouseup', evt => {
this.dragStart = undefined this.dragStart = undefined
@@ -44,9 +39,13 @@ export class VisualizerView extends AbstractView {
} }
invalidated() { 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)) { if (this.visualizer.dirty(this.model)) {
const img = this.ctx.createImageData(200, 100) const img = this.ctx.createImageData(200, 100)
this.visualizer.state = JSON.parse(JSON.stringify(newState))
this.visualizer.draw(this.model, img) this.visualizer.draw(this.model, img)
this.ctx.putImageData(img, 0, 0) this.ctx.putImageData(img, 0, 0)
} }
@@ -68,10 +67,7 @@ export class VisualizerView extends AbstractView {
set(visualizer: Visualizer) { set(visualizer: Visualizer) {
this.active = true this.active = true
this.visualizer = visualizer this.visualizer = visualizer
this.visualizer.state = undefined
this.invalidated() this.invalidated()
} }
static visualizers: {[key: string]: Visualizer} = {
'biome-noise': new BiomeNoiseVisualizer()
}
} }