Complete refactor (#123)

This commit is contained in:
Misode
2020-11-23 14:29:16 +01:00
committed by GitHub
parent 0ad33cd88f
commit 982b4728e7
45 changed files with 1162 additions and 1113 deletions

View File

@@ -0,0 +1,122 @@
import { DataModel, Path, ModelPath } from "@mcschema/core"
import { Property } from "../state/Property"
import { NormalNoise } from './NormalNoise'
import { Preview } from './Preview'
const LOCAL_STORAGE_BIOME_COLORS = 'biome_colors'
export class BiomeNoisePreview extends Preview {
static readonly noiseMaps = ['altitude', 'temperature', 'humidity', 'weirdness']
private noise: NormalNoise[]
seed: string
offsetX: number = 0
offsetY: number = 0
viewScale: number = 0
biomeColors: Property<{ [id: string]: number[] }>
constructor() {
super()
this.seed = this.hexId()
this.biomeColors = new Property({})
this.biomeColors.set(JSON.parse(localStorage.getItem(LOCAL_STORAGE_BIOME_COLORS) ?? '{}'))
this.noise = []
}
getName() {
return 'biome-noise'
}
active(path: ModelPath) {
return path.endsWith(new Path(['generator', 'biome_source']))
&& path.push('type').get() === 'minecraft:multi_noise'
}
draw(model: DataModel, img: ImageData) {
this.noise = BiomeNoisePreview.noiseMaps.map((id, i) => {
const config = this.state[`${id}_noise`]
return new NormalNoise(this.seed + i, config.firstOctave, config.amplitudes)
})
const biomeColorCache: {[key: string]: number[]} = {}
this.state.biomes.forEach((b: any) => {
biomeColorCache[b.biome] = this.getBiomeColor(b.biome)
})
const data = img.data
const s = (2 ** this.viewScale)
for (let x = 0; x < 200; x += 1) {
for (let y = 0; y < 100; y += 1) {
const i = (y * (img.width * 4)) + (x * 4)
const xx = (x - this.offsetX) * s - 100 * s
const yy = (y - this.offsetY) * s - 50 * s
const b = this.closestBiome(xx, yy)
const color = biomeColorCache[b] ?? [128, 128, 128]
data[i] = color[0]
data[i + 1] = color[1]
data[i + 2] = color[2]
data[i + 3] = 255
}
}
}
onDrag(fromX: number, fromY: number, toX: number, toY: number) {
this.offsetX += toX - fromX
this.offsetY += toY - fromY
}
private closestBiome(x: number, y: number): string {
if (!this.state.biomes || this.state.biomes.length === 0) return ''
const noise = this.noise.map(n => n.getValue(x, y))
let minDist = Infinity
let minBiome = ''
for (const b of this.state.biomes) {
const dist = this.fitness(b.parameters, {altitude: noise[0], temperature: noise[1], humidity: noise[2], weirdness: noise[3], offset: 0})
if (dist < minDist) {
minDist = dist
minBiome = b.biome
}
}
return minBiome
}
private fitness(a: any, b: any) {
return (a.altitude - b.altitude) * (a.altitude - b.altitude) + (a.temperature - b.temperature) * (a.temperature - b.temperature) + (a.humidity - b.humidity) * (a.humidity - b.humidity) + (a.weirdness - b.weirdness) * (a.weirdness - b.weirdness) + (a.offset - b.offset) * (a.offset - b.offset)
}
getBiomeColor(biome: string): number[] {
const color = this.biomeColors.get()[biome]
if (color === undefined) {
return this.colorFromBiome(biome)
}
return color
}
setBiomeColor(biome: string, value: string) {
const color = [parseInt(value.slice(1, 3), 16), parseInt(value.slice(3, 5), 16), parseInt(value.slice(5, 7), 16)]
this.biomeColors.set({...this.biomeColors.get(), [biome]: color})
localStorage.setItem(LOCAL_STORAGE_BIOME_COLORS, JSON.stringify(this.biomeColors.get()))
}
getBiomeHex(biome: string): string {
return '#' + this.getBiomeColor(biome).map(e => e.toString(16).padStart(2, '0')).join('')
}
private colorFromBiome(biome: string): number[] {
const h = Math.abs(this.hash(biome))
return [h % 256, (h >> 8) % 256, (h >> 16) % 256]
}
private hash(s: string) {
return (s ?? '').split('').reduce((a, b) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a }, 0)
}
private dec2hex(dec: number) {
return ('0' + dec.toString(16)).substr(-2)
}
private hexId(length = 12) {
var arr = new Uint8Array(length / 2)
window.crypto.getRandomValues(arr)
return Array.from(arr, this.dec2hex).join('')
}
}

View File

@@ -0,0 +1,102 @@
import SimplexNoise from 'simplex-noise'
import { DataModel, Path, ModelPath } from "@mcschema/core"
import { Preview } from './Preview'
const debug = false
export class NoiseSettingsPreview extends Preview {
private noise: SimplexNoise
private offsetX: number
constructor() {
super()
this.noise = new SimplexNoise()
this.offsetX = 0
}
getName() {
return 'noise-settings'
}
active(path: ModelPath) {
return path.endsWith(new Path(['noise']))
}
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
}
}
}
onDrag(fromX: number, fromY: number, toX: number, toY: number) {
this.offsetX += toX - fromX
}
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,46 @@
import SimplexNoise from 'simplex-noise'
export class NormalNoise {
private noiseLevels: SimplexNoise[]
private amplitudes: number[]
private valueFactor: number
private lowestFreqInputFactor: number
private lowestFreqValueFactor: number
constructor(seed: string, firstOctave: number, amplitudes: number[]) {
this.amplitudes = amplitudes
this.noiseLevels = amplitudes.map((a, i) => new SimplexNoise(seed + 'a' + i))
let min = +Infinity
let max = -Infinity
for (let a of amplitudes) {
if (a !== 0) {
min = Math.min(min, a)
max = Math.max(max, a)
}
}
const expectedDeviation = 0.1 * (1 + 1 / (max - min + 1))
this.valueFactor = (1/6) / expectedDeviation
this.lowestFreqInputFactor = Math.pow(2, firstOctave)
this.lowestFreqValueFactor = Math.pow(2, (amplitudes.length - 1)) / (Math.pow(2, amplitudes.length) - 1)
}
getValue(x: number, y: number) {
let value = 0
let inputF = this.lowestFreqInputFactor
let valueF = this.lowestFreqValueFactor
for (let i = 0; i < this.amplitudes.length; i += 1) {
if (this.amplitudes[i] !== 0) {
value += this.amplitudes[i] * this.noiseLevels[i].noise2D(this.wrap(x * inputF), this.wrap(y * inputF) + i) * valueF
}
inputF *= 2
valueF /= 2
}
return 2 * value * this.valueFactor
}
private wrap(value: number) {
return value - Math.floor(value / 3.3554432E7 + 0.5) * 3.3554432E7
}
}

View File

@@ -0,0 +1,16 @@
import { DataModel, ModelPath } from "@mcschema/core"
export abstract class Preview {
state: any
path?: ModelPath
dirty(path: ModelPath): boolean {
return JSON.stringify(this.state) !== JSON.stringify(path.get())
}
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 {}
}