diff --git a/src/app/preview/BiomeNoisePreview.ts b/src/app/preview/BiomeNoisePreview.ts index f8f394e6..db14a728 100644 --- a/src/app/preview/BiomeNoisePreview.ts +++ b/src/app/preview/BiomeNoisePreview.ts @@ -3,7 +3,7 @@ import { Octicon } from "../components/Octicon" import { Property } from "../state/Property" import { hashString, hexId } from "../Utils" import { View } from "../views/View" -import { NormalNoise } from './NormalNoise' +import { NormalNoise } from './math/NormalNoise' import { Preview } from './Preview' const LOCAL_STORAGE_BIOME_COLORS = 'biome_colors' diff --git a/src/app/preview/NoiseChunkGenerator.ts b/src/app/preview/NoiseChunkGenerator.ts new file mode 100644 index 00000000..819f1534 --- /dev/null +++ b/src/app/preview/NoiseChunkGenerator.ts @@ -0,0 +1,127 @@ +import { PerlinNoise } from './math/PerlinNoise' +import { clampedLerp, hexId, lerp2 } from '../Utils' + +export class NoiseChunkGenerator { + private minLimitPerlinNoise: PerlinNoise + private maxLimitPerlinNoise: PerlinNoise + private mainPerlinNoise: PerlinNoise + private depthNoise: PerlinNoise + + private chunkWidth: number = 4 + private chunkHeight: number = 4 + private chunkCountY: number = 32 + + constructor(private state: any, private depth: number, private scale: number) { + this.minLimitPerlinNoise = PerlinNoise.fromRange(hexId(), -15, 0) + this.maxLimitPerlinNoise = PerlinNoise.fromRange(hexId(), -15, 0) + this.mainPerlinNoise = PerlinNoise.fromRange(hexId(), -7, 0) + this.depthNoise = PerlinNoise.fromRange(hexId(), -15, 0) + + this.chunkWidth = state.size_horizontal * 4 + this.chunkHeight = state.size_vertical * 4 + this.chunkCountY = Math.floor(state.height / this.chunkHeight) + } + + public iterateNoiseColumn(x: number): number[] { + const data = Array(this.chunkCountY * this.chunkHeight) + const cx = Math.floor(x / this.chunkWidth) + const ox = Math.floor(x % this.chunkWidth) / this.chunkWidth + const noise1 = this.fillNoiseColumn(cx) + const noise2 = this.fillNoiseColumn(cx + 1) + + for (let y = this.chunkCountY - 1; y >= 0; y -= 1) { + for (let yy = this.chunkHeight; yy >= 0; yy -= 1) { + const oy = yy / this.chunkHeight + const i = y * this.chunkHeight + yy + data[i] = lerp2(oy, ox, noise1[y], noise1[y+1], noise2[y], noise2[y+1]); + } + } + return data + } + + private fillNoiseColumn(x: number): number[] { + const data = Array(this.chunkCountY + 1) + + let scaledDepth = 0.265625 * this.depth + let scaledScale = 96 / this.scale + const xzScale = 684.412 * this.state.sampling.xz_scale + const yScale = 684.412 * this.state.sampling.y_scale + const xzFactor = xzScale / this.state.sampling.xz_factor + const yFactor = yScale / this.state.sampling.y_factor + const randomDensity = this.state.random_density_offset ? this.getRandomDensity(x) : 0 + + for (let y = 0; y <= this.chunkCountY; y += 1) { + let noise = this.sampleAndClampNoise(x, y, this.mainPerlinNoise.getOctaveNoise(0).zo, xzScale, yScale, xzFactor, yFactor) + const yOffset = 1 - y * 2 / this.chunkCountY + randomDensity + const density = yOffset * this.state.density_factor + this.state.density_offset + const falloff = (density + scaledDepth) * scaledScale + noise += falloff * (falloff > 0 ? 4 : 1) + + if (this.state.top_slide.size > 0) { + noise = clampedLerp( + this.state.top_slide.target, + noise, + (this.chunkCountY - y - (this.state.top_slide.offset)) / (this.state.top_slide.size) + ) + } + + if (this.state.bottom_slide.size > 0) { + noise = clampedLerp( + this.state.bottom_slide.target, + noise, + (y - (this.state.bottom_slide.offset)) / (this.state.bottom_slide.size) + ) + } + data[y] = noise + } + return data + } + + private getRandomDensity(x: number): number { + const noise = this.depthNoise.getValue(x * 200, 10, this.depthNoise.getOctaveNoise(0).zo, 1, 0, true) + const a = (noise < 0) ? -noise * 0.3 : noise + const b = a * 24.575625 - 2 + return (b < 0) ? b * 0.009486607142857142 : Math.min(b, 1) * 0.006640625 + } + + private sampleAndClampNoise(x: number, y: number, z: number, xzScale: number, yScale: number, xzFactor: number, yFactor: number): number { + let a = 0 + let b = 0 + let c = 0 + let d = 1 + + for (let i = 0; i < 16; i += 1) { + const x2 = PerlinNoise.wrap(x * xzScale * d) + const y2 = PerlinNoise.wrap(y * yScale * d) + const z2 = PerlinNoise.wrap(z * xzScale * d) + const e = yScale * d + + const minLimitNoise = this.minLimitPerlinNoise.getOctaveNoise(i) + if (minLimitNoise) { + a += minLimitNoise.noise(x2, y2, z2, e, y * e) / d + } + + const maxLimitNoise = this.maxLimitPerlinNoise.getOctaveNoise(i) + if (maxLimitNoise) { + b += maxLimitNoise.noise(x2, y2, z2, e, y * e) / d + } + + if (i < 8) { + const mainNoise = this.mainPerlinNoise.getOctaveNoise(i) + if (mainNoise) { + c += mainNoise.noise( + PerlinNoise.wrap(x * xzFactor * d), + PerlinNoise.wrap(y * yFactor * d), + PerlinNoise.wrap(z * xzFactor * d), + yFactor * d, + y * yFactor * d + ) / d + } + } + + d /= 2 + } + + return clampedLerp(a / 512, b / 512, (c / 10 + 1) / 2) + } +} diff --git a/src/app/preview/NoiseSettingsPreview.ts b/src/app/preview/NoiseSettingsPreview.ts index 3c289d1b..586aa34f 100644 --- a/src/app/preview/NoiseSettingsPreview.ts +++ b/src/app/preview/NoiseSettingsPreview.ts @@ -2,30 +2,14 @@ import { DataModel, Path, ModelPath } from "@mcschema/core" import { Preview } from './Preview' import { toggleMenu, View } from '../views/View' import { Octicon } from '../components/Octicon' -import { clampedLerp, hexId, lerp2 } from '../Utils' -import { PerlinNoise } from './PerlinNoise' +import { NoiseChunkGenerator } from './NoiseChunkGenerator' export class NoiseSettingsPreview extends Preview { - private minLimitPerlinNoise: PerlinNoise - private maxLimitPerlinNoise: PerlinNoise - private mainPerlinNoise: PerlinNoise - private depthNoise: PerlinNoise private width: number = 512 - private chunkWidth: number = 4 - private chunkHeight: number = 4 - private chunkCountY: number = 32 - private offsetX: number = 0 - private debug: boolean = false private depth: number = 0.1 private scale: number = 0.2 - - constructor() { - super() - this.minLimitPerlinNoise = PerlinNoise.fromRange(hexId(), -15, 0) - this.maxLimitPerlinNoise = PerlinNoise.fromRange(hexId(), -15, 0) - this.mainPerlinNoise = PerlinNoise.fromRange(hexId(), -7, 0) - this.depthNoise = PerlinNoise.fromRange(hexId(), -15, 0) - } + private offsetX: number = 0 + private debug: boolean = false getName() { return 'noise-settings' @@ -87,13 +71,11 @@ export class NoiseSettingsPreview extends Preview { } draw(model: DataModel, img: ImageData) { - this.chunkWidth = this.state.size_horizontal * 4 - this.chunkHeight = this.state.size_vertical * 4 - this.chunkCountY = Math.floor(this.state.height / this.chunkHeight) + const generator = new NoiseChunkGenerator(this.state, this.depth, this.scale) const data = img.data for (let x = 0; x < this.width; x += 1) { - const noise = this.iterateNoiseColumn(x - this.offsetX).reverse() + const noise = generator.iterateNoiseColumn(x - this.offsetX).reverse() for (let y = 0; y < this.state.height; y += 1) { const i = (y * (img.width * 4)) + (x * 4) const color = this.getColor(noise, y) @@ -121,107 +103,4 @@ export class NoiseSettingsPreview extends Preview { } return 255 } - - private iterateNoiseColumn(x: number): number[] { - const data = Array(this.chunkCountY * this.chunkHeight) - const cx = Math.floor(x / this.chunkWidth) - const ox = Math.floor(x % this.chunkWidth) / this.chunkWidth - const noise1 = this.fillNoiseColumn(cx) - const noise2 = this.fillNoiseColumn(cx + 1) - - for (let y = this.chunkCountY - 1; y >= 0; y -= 1) { - for (let yy = this.chunkHeight; yy >= 0; yy -= 1) { - const oy = yy / this.chunkHeight - const i = y * this.chunkHeight + yy - data[i] = lerp2(oy, ox, noise1[y], noise1[y+1], noise2[y], noise2[y+1]); - } - } - return data - } - - private fillNoiseColumn(x: number): number[] { - const data = Array(this.chunkCountY + 1) - - let scaledDepth = 0.265625 * this.depth - let scaledScale = 96 / this.scale - const xzScale = 684.412 * this.state.sampling.xz_scale - const yScale = 684.412 * this.state.sampling.y_scale - const xzFactor = xzScale / this.state.sampling.xz_factor - const yFactor = yScale / this.state.sampling.y_factor - const randomDensity = this.state.random_density_offset ? this.getRandomDensity(x) : 0 - - for (let y = 0; y <= this.chunkCountY; y += 1) { - let noise = this.sampleAndClampNoise(x, y, this.mainPerlinNoise.getOctaveNoise(0).zo, xzScale, yScale, xzFactor, yFactor) - const yOffset = 1 - y * 2 / this.chunkCountY + randomDensity - const density = yOffset * this.state.density_factor + this.state.density_offset - const falloff = (density + scaledDepth) * scaledScale - noise += falloff * (falloff > 0 ? 4 : 1) - - if (this.state.top_slide.size > 0) { - noise = clampedLerp( - this.state.top_slide.target, - noise, - (this.chunkCountY - y - (this.state.top_slide.offset)) / (this.state.top_slide.size) - ) - } - - if (this.state.bottom_slide.size > 0) { - noise = clampedLerp( - this.state.bottom_slide.target, - noise, - (y - (this.state.bottom_slide.offset)) / (this.state.bottom_slide.size) - ) - } - data[y] = noise - } - return data - } - - private getRandomDensity(x: number): number { - const noise = this.depthNoise.getValue(x * 200, 10, this.depthNoise.getOctaveNoise(0).zo, 1, 0, true) - const a = (noise < 0) ? -noise * 0.3 : noise - const b = a * 24.575625 - 2 - return (b < 0) ? b * 0.009486607142857142 : Math.min(b, 1) * 0.006640625 - } - - private sampleAndClampNoise(x: number, y: number, z: number, xzScale: number, yScale: number, xzFactor: number, yFactor: number): number { - let a = 0 - let b = 0 - let c = 0 - let d = 1 - - for (let i = 0; i < 16; i += 1) { - const x2 = PerlinNoise.wrap(x * xzScale * d) - const y2 = PerlinNoise.wrap(y * yScale * d) - const z2 = PerlinNoise.wrap(z * xzScale * d) - const e = yScale * d - - const minLimitNoise = this.minLimitPerlinNoise.getOctaveNoise(i) - if (minLimitNoise) { - a += minLimitNoise.noise(x2, y2, z2, e, y * e) / d - } - - const maxLimitNoise = this.maxLimitPerlinNoise.getOctaveNoise(i) - if (maxLimitNoise) { - b += maxLimitNoise.noise(x2, y2, z2, e, y * e) / d - } - - if (i < 8) { - const mainNoise = this.mainPerlinNoise.getOctaveNoise(i) - if (mainNoise) { - c += mainNoise.noise( - PerlinNoise.wrap(x * xzFactor * d), - PerlinNoise.wrap(y * yFactor * d), - PerlinNoise.wrap(z * xzFactor * d), - yFactor * d, - y * yFactor * d - ) / d - } - } - - d /= 2 - } - - return clampedLerp(a / 512, b / 512, (c / 10 + 1) / 2) - } } diff --git a/src/app/preview/ImprovedNoise.ts b/src/app/preview/math/ImprovedNoise.ts similarity index 98% rename from src/app/preview/ImprovedNoise.ts rename to src/app/preview/math/ImprovedNoise.ts index 357b7138..f1f11657 100644 --- a/src/app/preview/ImprovedNoise.ts +++ b/src/app/preview/math/ImprovedNoise.ts @@ -1,5 +1,5 @@ import seedrandom from 'seedrandom' -import { lerp3, smoothstep } from '../Utils' +import { lerp3, smoothstep } from '../../Utils' export class ImprovedNoise { private static readonly GRADIENT = [[1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0], [1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1], [0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1], [1, 1, 0], [0, -1, 1], [-1, 1, 0], [0, -1, -1]] diff --git a/src/app/preview/NormalNoise.ts b/src/app/preview/math/NormalNoise.ts similarity index 100% rename from src/app/preview/NormalNoise.ts rename to src/app/preview/math/NormalNoise.ts diff --git a/src/app/preview/PerlinNoise.ts b/src/app/preview/math/PerlinNoise.ts similarity index 100% rename from src/app/preview/PerlinNoise.ts rename to src/app/preview/math/PerlinNoise.ts