mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-26 00:16:51 +00:00
Complete refactor (#123)
This commit is contained in:
122
src/app/preview/BiomeNoisePreview.ts
Normal file
122
src/app/preview/BiomeNoisePreview.ts
Normal 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('')
|
||||
}
|
||||
}
|
||||
102
src/app/preview/NoiseSettingsPreview.ts
Normal file
102
src/app/preview/NoiseSettingsPreview.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/app/preview/NormalNoise.ts
Normal file
46
src/app/preview/NormalNoise.ts
Normal 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
|
||||
}
|
||||
}
|
||||
16
src/app/preview/Preview.ts
Normal file
16
src/app/preview/Preview.ts
Normal 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 {}
|
||||
}
|
||||
Reference in New Issue
Block a user