From 88bf86de3cd9f472eb8d06e9a34552f5a06c5e87 Mon Sep 17 00:00:00 2001 From: Misode Date: Sun, 6 Dec 2020 02:34:34 +0100 Subject: [PATCH] Add decorator preview --- src/app/App.ts | 4 +- src/app/components/Octicon.ts | 1 + src/app/hooks/suffixInjector.ts | 3 + src/app/preview/DecoratorPreview.ts | 202 ++++++++++++++++++++++++++++ tsconfig.json | 4 + 5 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 src/app/preview/DecoratorPreview.ts diff --git a/src/app/App.ts b/src/app/App.ts index 9978de6e..8e7dfbe8 100644 --- a/src/app/App.ts +++ b/src/app/App.ts @@ -7,6 +7,7 @@ import { Preview } from './preview/Preview'; import { RegistryFetcher } from './RegistryFetcher'; import { BiomeNoisePreview } from './preview/BiomeNoisePreview'; import { NoiseSettingsPreview } from './preview/NoiseSettingsPreview'; +import { DecoratorPreview } from './preview/DecoratorPreview'; import config from '../config.json'; import { locale, Locales } from './Locales'; import { Tracker } from './Tracker'; @@ -25,7 +26,8 @@ export const Previews: { [key: string]: Preview } = { 'biome_noise': new BiomeNoisePreview(), - 'noise_settings': new NoiseSettingsPreview() + 'noise_settings': new NoiseSettingsPreview(), + 'decorator': new DecoratorPreview(), } export const Models: { diff --git a/src/app/components/Octicon.ts b/src/app/components/Octicon.ts index fdd9486a..c8728b9f 100644 --- a/src/app/components/Octicon.ts +++ b/src/app/components/Octicon.ts @@ -18,6 +18,7 @@ export const Octicon = { mark_github: '', moon: '', note: '', + package: '', play: '', plus: '', plus_circle: '', diff --git a/src/app/hooks/suffixInjector.ts b/src/app/hooks/suffixInjector.ts index 781454d5..bbd295e1 100644 --- a/src/app/hooks/suffixInjector.ts +++ b/src/app/hooks/suffixInjector.ts @@ -25,6 +25,9 @@ export const suffixInjector: Hook<[Mounter], string | void> = { if (Previews.noise_settings.active(path)) { return setPreview(Previews.noise_settings, path, mounter) } + if (Previews.decorator.active(path)) { + return setPreview(Previews.decorator, path, mounter) + } }, string({}, path, mounter) { diff --git a/src/app/preview/DecoratorPreview.ts b/src/app/preview/DecoratorPreview.ts new file mode 100644 index 00000000..29f98cf3 --- /dev/null +++ b/src/app/preview/DecoratorPreview.ts @@ -0,0 +1,202 @@ +import { DataModel, Path, ModelPath } from "@mcschema/core" +import seedrandom from "seedrandom" +import { App } from "../App" +import { clamp, hexId, stringToColor } from "../Utils" +import { PerlinNoise } from "./noise/PerlinNoise" +import { Preview } from './Preview' +import { Octicon } from '../components/Octicon' +import { View } from "../views/View" + +type BlockPos = [number, number, number] +type Placement = { pos: BlockPos, feature: string } + +export class DecoratorPreview extends Preview { + private seed: string + private perspective: string + + constructor() { + super() + this.seed = hexId() + this.perspective = 'top' + } + + getName() { + return 'decorator' + } + + active(path: ModelPath) { + return App.model.get()?.id === 'worldgen/feature' + && path.equals(new Path(['config', 'decorator'])) + && path.pop().pop().push('type').get() === 'minecraft:decorated' + } + + menu(view: View, redraw: () => void) { + return ` +
+ ${Octicon.package} +
` + } + + getSize(): [number, number] { + return this.perspective === 'top' ? [4 * 16, 3 * 16] : [4 * 16, 128] + } + + draw(model: DataModel, img: ImageData) { + const featureData = JSON.parse(JSON.stringify(model.data)) + const random = seedrandom(this.seed) + + let placements: Placement[] = [] + for (let x = 0; x < 4; x += 1) { + for (let z = 0; z < (this.perspective === 'top' ? 3 : 1); z += 1) { + const p = getPlacements(random, [x * 16, 0, z * 16], featureData) + placements = [...placements, ...p] + } + } + + const data = img.data + img.data.fill(255) + + for (let {pos, feature} of placements) { + const i = this.perspective === 'top' + ? (pos[2] * (img.width * 4)) + (pos[0] * 4) + : ((127 - pos[1]) * (img.width * 4)) + (pos[0] * 4) + const color = stringToColor(feature) + data.set(color.map(c => clamp(30, 205, c)), i) + } + + for (let x = 0; x < 4 * 16; x += 1) { + for (let y = 0; y < (this.perspective === 'top' ? 3 * 16: 128); y += 1) { + if ((Math.floor(x/16) + (this.perspective === 'top' ? Math.floor(y/16) : 0)) % 2 === 0) continue + const i = (y * (img.width * 4)) + (x * 4) + for (let j = 0; j < 4; j += 1) { + data[i + j] = data[i + j] - 30 + } + } + } + } +} + +const biomeInfoNoise = new PerlinNoise(hexId(), 0, [1]) + +function getPlacements (random: seedrandom.prng, pos: BlockPos, feature: any): Placement[] { + if (typeof feature === 'string') { + return [{ pos, feature }] + } + const type = feature?.type?.replace(/^minecraft:/, '') + const featureFn = Features[type] + if (!featureFn) { + return [{ pos, feature: JSON.stringify(feature) }] + } + return featureFn(feature.config, random, pos) +} + +function getPositions (random: seedrandom.prng, pos: BlockPos, decorator: any): BlockPos[] { + const type = decorator?.type?.replace(/^minecraft:/, '') + const decoratorFn = Decorators[type] + if (!decoratorFn) { + return [pos] + } + return decoratorFn(decorator?.config, random, pos) +} + +const Features: { + [key: string]: (config: any, random: seedrandom.prng, pos: BlockPos) => Placement[] +} = { + decorated: (config, random, pos) => { + const positions = getPositions(random, pos, config?.decorator) + return positions.flatMap(p => getPlacements(random, p, config?.feature)) + }, + random_boolean_selector: (config, random, pos) => { + const feature = random() < 0.5 ? config?.feature_true : config?.feature_false + return getPlacements(random, pos, feature) + }, + random_selector: (config, random, pos) => { + for (const f of config?.features ?? []) { + if (random() < (f?.chance ?? 0)) { + return getPlacements(random, pos, f.feature) + } + } + return getPlacements(random, pos, config?.default) + }, + simple_random_selector: (config, random, pos) => { + const feature = config?.features?.[nextInt(random, config?.features?.length ?? 0)] + return getPlacements(random, pos, feature) + } +} + +const Decorators: { + [key: string]: (config: any, random: seedrandom.prng, pos: BlockPos) => BlockPos[] +} = { + count: (config, random, pos) => { + return new Array(sampleUniformInt(random, config?.count ?? 1)).fill(pos) + }, + count_extra: (config, random, pos) => { + let count = config?.count ?? 1 + if (random() < config.extra_chance ?? 0){ + count += config.extra_count ?? 0 + } + return new Array(count).fill(pos) + }, + count_noise: (config, random, pos) => { + const noise = biomeInfoNoise.getValue(pos[0] / 200, 0, pos[2] / 200) + const count = noise < config.noise_level ? config.below_noise : config.above_noise + return new Array(count).fill(pos) + }, + count_noise_biased: (config, random, pos) => { + const factor = Math.max(1, config.noise_factor) + const noise = biomeInfoNoise.getValue(pos[0] / factor, 0, pos[2] / factor) + const count = Math.max(0, Math.ceil((noise + config.noise_offset) * config.noise_to_count_ratio)) + return new Array(count).fill(pos) + }, + decorated: (config, random, pos) => { + return getPositions(random, pos, config?.outer).flatMap(p => { + return getPositions(random, p, config?.inner) + }) + }, + range: (config, random, pos) => { + const y = nextInt(random, (config?.maximum ?? 1) - (config?.top_offset ?? 0)) + (config?.bottom_offset ?? 0) + return decorateY(pos, y) + }, + range_biased: (config, random, pos) => { + const y = nextInt(random, nextInt(random, (config?.maximum ?? 1) - (config?.top_offset ?? 0)) + (config?.bottom_offset ?? 0)) + return decorateY(pos, y) + }, + range_very_biased: (config, random, pos) => { + const y = nextInt(random, nextInt(random, nextInt(random, (config?.maximum ?? 1) - (config?.top_offset ?? 0)) + (config?.bottom_offset ?? 0)) + (config?.bottom_offset ?? 0)) + return decorateY(pos, y) + }, + spread_32_above: (config, random, pos) => { + const y = nextInt(random, pos[1] + 32) + return decorateY(pos, y) + }, + magma: (config, random, pos) => { + const y = nextInt(random, pos[1] + 32) + return decorateY(pos, y) + }, + square: (config, random, pos) => { + return [[ + pos[0] + nextInt(random, 16), + pos[1], + pos[2] + nextInt(random, 16) + ]] + } +} + +function decorateY(pos: BlockPos, y: number): BlockPos[] { + return [[ pos[0], y, pos[2] ]] +} + +function sampleUniformInt(random: seedrandom.prng, value: any): number { + if (typeof value === 'number') { + return value + } else { + return (value.base ?? 1) + nextInt(random, 1 + (value.spread ?? 0)) + } +} + +function nextInt(random: seedrandom.prng, max: number): number { + return Math.floor(random() * max) +} diff --git a/tsconfig.json b/tsconfig.json index f497f803..7ff59641 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,10 @@ { "compilerOptions": { "target": "es6", + "lib": [ + "dom", + "es2019" + ], "module": "esnext", "strict": true, "moduleResolution": "node",