Switch to vite and preact

This commit is contained in:
Misode
2021-06-23 20:44:28 +02:00
parent e551b7ef75
commit 09c851914f
89 changed files with 6398 additions and 15531 deletions

View File

@@ -0,0 +1,152 @@
import { stringToColor } from '../Utils'
import { NormalNoise } from './noise/NormalNoise'
export type BiomeColors =Record<string, number[]>
export type BiomeSourceOptions = {
biomeColors: BiomeColors,
offset: [number, number],
scale: number,
res: number,
seed: string,
}
export const NoiseMaps = ['altitude', 'temperature', 'humidity', 'weirdness']
export function biomeSource(state: any, img: ImageData, options: BiomeSourceOptions) {
switch (state?.type?.replace(/^minecraft:/, '')) {
case 'multi_noise': return multiNoise(state, img, options)
case 'fixed': return fixed(state, img, options)
case 'checkerboard': return checkerboard(state, img, options)
}
}
function fixed(state: any, img: ImageData, options: BiomeSourceOptions) {
const data = img.data
const color = getBiomeColor(state.biome, options.biomeColors)
const row = img.width * 4 / options.res
const col = 4 / options.res
for (let x = 0; x < 200; x += options.res) {
for (let y = 0; y < 200; y += options.res) {
const i = y * row + x * col
data[i] = color[0]
data[i + 1] = color[1]
data[i + 2] = color[2]
data[i + 3] = 255
}
}
}
function checkerboard(state: any, img: ImageData, options: BiomeSourceOptions) {
const biomeColorCache: BiomeColors = {}
state.biomes?.forEach((b: string) => {
biomeColorCache[b] = getBiomeColor(b, options.biomeColors)
})
const data = img.data
const ox = -options.offset[0] - 100 + options.res / 2
const oy = -options.offset[1] - 100 + options.res / 2
const row = img.width * 4 / options.res
const col = 4 / options.res
const shift = (state.scale ?? 2) + 2
const numBiomes = state.biomes?.length ?? 0
for (let x = 0; x < 200; x += options.res) {
for (let y = 0; y < 200; y += options.res) {
const i = y * row + x * col
const xx = (x + ox) * options.scale
const yy = (y + oy) * options.scale
const j = (((xx >> shift) + (yy >> shift)) % numBiomes + numBiomes) % numBiomes
const b = state.biomes?.[j]
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
}
}
}
function multiNoise(state: any, img: ImageData, options: BiomeSourceOptions) {
const noise = NoiseMaps.map((id, i) => {
const config = state[`${id}_noise`]
return new NormalNoise(options.seed + i, config.firstOctave, config.amplitudes)
})
const biomeColorCache: BiomeColors = {}
state.biomes.forEach((b: any) => {
biomeColorCache[b.biome] = getBiomeColor(b.biome, options.biomeColors)
})
const data = img.data
const ox = -options.offset[0] - 100 + options.res / 2
const oy = -options.offset[1] - 100 + options.res / 2
const row = img.width * 4 / options.res
const col = 4 / options.res
for (let x = 0; x < 200; x += options.res) {
for (let y = 0; y < 200; y += options.res) {
const i = y * row + x * col
const xx = (x + ox) * options.scale
const yy = (y + oy) * options.scale
const b = closestBiome(noise, state.biomes, 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
}
}
}
export function getBiome(state: any, x: number, y: number, options: BiomeSourceOptions): string | undefined {
const [xx, yy] = toWorld([x, y], options)
switch (state?.type?.replace(/^minecraft:/, '')) {
case 'multi_noise':
const noise = NoiseMaps.map((id, i) => {
const config = state[`${id}_noise`]
return new NormalNoise(options.seed + i, config.firstOctave, config.amplitudes)
})
return closestBiome(noise, state.biomes, xx, yy)
case 'fixed': return state.biome
case 'checkerboard':
const shift = (state.scale ?? 2) + 2
const numBiomes = state.biomes?.length ?? 0
const j = (((xx >> shift) + (yy >> shift)) % numBiomes + numBiomes) % numBiomes
return state.biomes?.[j]
}
return undefined
}
export function getBiomeColor(biome: string, biomeColors: BiomeColors) {
if (!biome) {
return [128, 128, 128]
}
const color = biomeColors[biome]
if (color === undefined) {
return stringToColor(biome)
}
return color
}
function toWorld([x, y]: [number, number], options: BiomeSourceOptions) {
const xx = (x - options.offset[0] - 100 + options.res / 2) * options.scale
const yy = (y - options.offset[1] - 100 + options.res / 2) * options.scale
return [xx, yy]
}
function closestBiome(noise: NormalNoise[], biomes: any[], x: number, y: number): string {
if (!Array.isArray(biomes) || biomes.length === 0) return ''
const n = noise.map(n => n.getValue(x, y, 0))
let minDist = Infinity
let minBiome = ''
for (const b of biomes) {
const dist = fitness(b.parameters, {altitude: n[0], temperature: n[1], humidity: n[2], weirdness: n[3], offset: 0})
if (dist < minDist) {
minDist = dist
minBiome = b.biome
}
}
return minBiome
}
function 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)
}

View File

@@ -0,0 +1,309 @@
import seedrandom from 'seedrandom'
import type { VersionId } from '../Schemas'
import { clamp, stringToColor } from '../Utils'
import { PerlinNoise } from './noise/PerlinNoise'
type BlockPos = [number, number, number]
type Placement = [BlockPos, number]
type PlacementContext = {
placements: Placement[],
features: string[],
random: seedrandom.prng,
biomeInfoNoise: PerlinNoise,
seaLevel: number,
version: VersionId,
}
const terrain = [50, 50, 51, 51, 52, 52, 53, 54, 56, 57, 57, 58, 58, 59, 60, 60, 60, 59, 59, 59, 60, 61, 61, 62, 63, 63, 64, 64, 64, 65, 65, 66, 66, 65, 65, 66, 66, 67, 67, 67, 68, 69, 71, 73, 74, 76, 79, 80, 81, 81, 82, 83, 83, 82, 82, 81, 81, 80, 80, 80, 81, 81, 82, 82]
const featureColors = [
[255, 77, 54], // red
[59, 118, 255], // blue
[91, 207, 25], // green
[217, 32, 245], // magenta
[255, 209, 41], // yellow
[52, 204, 209], // cyan
]
export type DecoratorOptions = {
size: [number, number, number],
seed: string,
version: VersionId,
}
export function decorator(state: any, img: ImageData, options: DecoratorOptions) {
const random = seedrandom(options.seed)
const ctx: PlacementContext = {
placements: [],
features: [],
random,
biomeInfoNoise: new PerlinNoise(options.seed + 'frwynup', 0, [1]),
seaLevel: 63,
version: options.version,
}
for (let x = 0; x < options.size[0] / 16; x += 1) {
for (let z = 0; z < options.size[2] / 16; z += 1) {
getPlacements([x * 16, 0, z * 16], state, ctx)
}
}
const data = img.data
img.data.fill(255)
for (const [pos, feature] of ctx.placements) {
if (pos[0] < 0 || pos[1] < 0 || pos[2] < 0 || pos[0] >= options.size[0] || pos[1] >= options.size[1] || pos[2] >= options.size[2]) continue
const i = (pos[2] * (img.width * 4)) + (pos[0] * 4)
const color = feature < featureColors.length ? featureColors[feature] : stringToColor(ctx.features[feature])
data[i] = clamp(50, 205, color[0])
data[i + 1] = clamp(50, 205, color[1])
data[i + 2] = clamp(50, 205, color[2])
data[i + 3] = 255
}
for (let x = 0; x < options.size[0]; x += 1) {
for (let y = 0; y < options.size[2]; y += 1) {
if ((Math.floor(x / 16) + Math.floor(y / 16)) % 2 === 0) continue
const i = (y * (img.width * 4)) + (x * 4)
for (let j = 0; j < 3; j += 1) {
data[i + j] = 0.85 * data[i + j]
}
}
}
}
function normalize(id: string) {
return id.startsWith('minecraft:') ? id.slice(10) : id
}
function decorateY(pos: BlockPos, y: number): BlockPos[] {
return [[ pos[0], y, pos[2] ]]
}
function nextInt(max: number, ctx: PlacementContext): number {
return Math.floor(ctx.random() * max)
}
function sampleInt(value: any, ctx: PlacementContext): number {
if (typeof value === 'number') {
return value
} else if (value.base) {
return value.base ?? 1 + nextInt(1 + (value.spread ?? 0), ctx)
} else {
switch (normalize(value.type)) {
case 'constant': return value.value
case 'uniform': return value.value.min_inclusive + nextInt(value.value.max_inclusive - value.value.min_inclusive + 1, ctx)
case 'biased_to_bottom': return value.value.min_inclusive + nextInt(nextInt(value.value.max_inclusive - value.value.min_inclusive + 1, ctx) + 1, ctx)
case 'clamped': return Math.max(value.value.min_inclusive, Math.min(value.value.max_inclusive, sampleInt(value.value.source, ctx)))
}
return 1
}
}
function useFeature(s: string, ctx: PlacementContext) {
const i = ctx.features.indexOf(s)
if (i != -1) return i
ctx.features.push(s)
return ctx.features.length - 1
}
function getPlacements(pos: BlockPos, feature: any, ctx: PlacementContext): void {
if (typeof feature === 'string') {
ctx.placements.push([pos, useFeature(feature, ctx)])
return
}
const type = normalize(feature?.type ?? 'no_op')
const featureFn = Features[type]
if (featureFn) {
featureFn(feature.config, pos, ctx)
} else {
ctx.placements.push([pos, useFeature(JSON.stringify(feature), ctx)])
}
}
function getPositions(pos: BlockPos, decorator: any, ctx: PlacementContext): BlockPos[] {
const type = normalize(decorator?.type ?? 'nope')
const decoratorFn = Decorators[type]
if (!decoratorFn) {
return [pos]
}
return decoratorFn(decorator?.config, pos, ctx)
}
const Features: {
[key: string]: (config: any, pos: BlockPos, ctx: PlacementContext) => void,
} = {
decorated: (config, pos, ctx) => {
const positions = getPositions(pos, config?.decorator, ctx)
positions.forEach(p => getPlacements(p, config?.feature, ctx))
},
random_boolean_selector: (config, pos, ctx) => {
const feature = ctx.random() < 0.5 ? config?.feature_true : config?.feature_false
getPlacements(pos, feature, ctx)
},
random_selector: (config, pos, ctx) => {
for (const f of config?.features ?? []) {
if (ctx.random() < (f?.chance ?? 0)) {
getPlacements(pos, f.feature, ctx)
return
}
}
getPlacements(pos, config?.default, ctx)
},
simple_random_selector: (config, pos, ctx) => {
const feature = config?.features?.[nextInt(config?.features?.length ?? 0, ctx)]
getPlacements(pos, feature, ctx)
},
}
const Decorators: {
[key: string]: (config: any, pos: BlockPos, ctx: PlacementContext) => BlockPos[],
} = {
chance: (config, pos, ctx) => {
return ctx.random() < 1 / (config?.chance ?? 1) ? [pos] : []
},
count: (config, pos, ctx) => {
return new Array(sampleInt(config?.count ?? 1, ctx)).fill(pos)
},
count_extra: (config, pos, ctx) => {
let count = config?.count ?? 1
if (ctx.random() < config.extra_chance ?? 0){
count += config.extra_count ?? 0
}
return new Array(count).fill(pos)
},
count_multilayer: (config, pos, ctx) => {
return new Array(sampleInt(config?.count ?? 1, ctx)).fill(pos)
.map(p => [
p[0] + nextInt(16, ctx),
p[1],
p[2] + nextInt(16, ctx),
])
},
count_noise: (config, pos, ctx) => {
const noise = ctx.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, pos, ctx) => {
const factor = Math.max(1, config.noise_factor)
const noise = ctx.biomeInfoNoise.getValue(pos[0] / factor, 0, pos[2] / factor)
const count = Math.max(0, Math.ceil((noise + (config.noise_offset ?? 0)) * config.noise_to_count_ratio))
return new Array(count).fill(pos)
},
dark_oak_tree: (_config, pos, ctx) => {
return [...new Array(16)].map((_, i) => {
const x = Math.floor(i / 4) * 4 + 1 + nextInt(3, ctx) + pos[0]
const y = Math.max(ctx.seaLevel, terrain[clamp(0, 63, x)])
const z = Math.floor(i % 4) * 4 + 1 + nextInt(3, ctx) + pos[2]
return [x, y, z]
})
},
decorated: (config, pos, ctx) => {
return getPositions(pos, config?.outer, ctx).flatMap(p => {
return getPositions(p, config?.inner, ctx)
})
},
depth_average: (config, pos, ctx) => {
const y = nextInt(config?.spread ?? 0, ctx) + nextInt(config?.spread ?? 0, ctx) - (config.spread ?? 0) + (config?.baseline ?? 0)
return decorateY(pos, y)
},
emerald_ore: (_config, pos, ctx) => {
const count = 3 + nextInt(6, ctx)
return [...new Array(count)].map(() => [
pos[0] + nextInt(16, ctx),
4 + nextInt(28, ctx),
pos[2] + nextInt(16, ctx),
])
},
fire: (config, pos, ctx) => {
const count = 1 + nextInt(nextInt(sampleInt(config?.count, ctx), ctx), ctx)
return [...new Array(count)].map(() => [
pos[0] + nextInt(16, ctx),
nextInt(128, ctx),
pos[2] + nextInt(16, ctx),
])
},
glowstone: (config, pos, ctx) => {
const count = nextInt(1 + nextInt(sampleInt(config?.count, ctx), ctx), ctx)
return [...new Array(count)].map(() => [
pos[0] + nextInt(16, ctx),
nextInt(128, ctx),
pos[2] + nextInt(16, ctx),
])
},
heightmap: (_config, pos, ctx) => {
const y = Math.max(ctx.seaLevel, terrain[clamp(0, 63, pos[0])])
return decorateY(pos, y)
},
heightmap_spread_double: (_config, pos, ctx) => {
const y = Math.max(ctx.seaLevel, terrain[clamp(0, 63, pos[0])])
return decorateY(pos, nextInt(y * 2, ctx))
},
heightmap_world_surface: (_config, pos, ctx) => {
const y = Math.max(ctx.seaLevel, terrain[clamp(0, 63, pos[0])])
return decorateY(pos, y)
},
iceberg: (_config, pos, ctx) => {
return [[
pos[0] + 4 + nextInt(8, ctx),
pos[1],
pos[2] + 4 + nextInt(8, ctx),
]]
},
lava_lake: (config, pos, ctx) => {
if (nextInt((config.chance ?? 1) / 10, ctx) === 0) {
const y = nextInt(nextInt(256 - 8, ctx) + 8, ctx)
if (y < ctx.seaLevel || nextInt((config?.chance ?? 1) / 8, ctx) == 0) {
const x = nextInt(16, ctx) + pos[0]
const z = nextInt(16, ctx) + pos[2]
return [[x, y, z]]
}
}
return []
},
nope: (_config, pos) => {
return [pos]
},
range: (config, pos, ctx) => {
const y = nextInt((config?.maximum ?? 1) - (config?.top_offset ?? 0), ctx) + (config?.bottom_offset ?? 0)
return decorateY(pos, y)
},
range_biased: (config, pos, ctx) => {
const y = nextInt(nextInt((config?.maximum ?? 1) - (config?.top_offset ?? 0), ctx) + (config?.bottom_offset ?? 0), ctx)
return decorateY(pos, y)
},
range_very_biased: (config, pos, ctx) => {
const y = nextInt(nextInt(nextInt((config?.maximum ?? 1) - (config?.top_offset ?? 0), ctx) + (config?.bottom_offset ?? 0), ctx) + (config?.bottom_offset ?? 0), ctx)
return decorateY(pos, y)
},
spread_32_above: (_config, pos, ctx) => {
const y = nextInt(pos[1] + 32, ctx)
return decorateY(pos, y)
},
top_solid_heightmap: (_config, pos) => {
const y = terrain[clamp(0, 63, pos[0])]
return decorateY(pos, y)
},
magma: (_config, pos, ctx) => {
const y = nextInt(pos[1] + 32, ctx)
return decorateY(pos, y)
},
square: (_config, pos, ctx) => {
return [[
pos[0] + nextInt(16, ctx),
pos[1],
pos[2] + nextInt(16, ctx),
]]
},
water_lake: (config, pos, ctx) => {
if (nextInt(config.chance ?? 1, ctx) === 0) {
return [[
pos[0] + nextInt(16, ctx),
nextInt(256, ctx),
pos[2] + nextInt(16, ctx),
]]
}
return []
},
}

View File

@@ -0,0 +1,37 @@
import { NoiseChunkGenerator } from './noise/NoiseChunkGenerator'
export type NoiseSettingsOptions = {
biomeScale: number,
biomeDepth: number,
offset: number,
width: number,
seed: string,
}
export function noiseSettings(state: any, img: ImageData, options: NoiseSettingsOptions) {
const generator = new NoiseChunkGenerator(options.seed)
generator.reset(state, options.biomeDepth, options.biomeScale, options.offset, 200)
const data = img.data
const row = img.width * 4
for (let x = 0; x < options.width; x += 1) {
const noise = generator.iterateNoiseColumn(x - options.offset).reverse()
for (let y = 0; y < state.height; y += 1) {
const i = y * row + x * 4
const color = getColor(noise, y)
data[i] = color
data[i + 1] = color
data[i + 2] = color
data[i + 3] = 255
}
}
}
function getColor(noise: number[], y: number): number {
if (noise[y] > 0) {
return 0
}
if (noise[y+1] > 0) {
return 150
}
return 255
}

View File

@@ -0,0 +1,3 @@
export * from './BiomeSource'
export * from './Decorator'
export * from './NoiseSettings'

View File

@@ -0,0 +1,79 @@
import type seedrandom from 'seedrandom'
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]]
private readonly p: number[]
public readonly xo: number
public readonly yo: number
public readonly zo: number
constructor(random: seedrandom.prng) {
this.xo = random() * 256
this.yo = random() * 256
this.zo = random() * 256
this.p = Array(256)
for (let i = 0; i < 256; i += 1) {
this.p[i] = i
}
for (let i = 0; i < 256; i += 1) {
const n = random.int32() % (256 - i)
const b = this.p[i]
this.p[i] = this.p[i + n]
this.p[i + n] = b
}
}
public noise(x: number, y: number, z: number, a: number, b: number) {
const x2 = x + this.xo
const y2 = y + this.yo
const z2 = z + this.zo
const x3 = Math.floor(x2)
const y3 = Math.floor(y2)
const z3 = Math.floor(z2)
const x4 = x2 - x3
const y4 = y2 - y3
const z4 = z2 - z3
const x5 = smoothstep(x4)
const y5 = smoothstep(y4)
const z5 = smoothstep(z4)
let y6 = 0
if (a !== 0) {
y6 = Math.floor(Math.min(b, y4) / a) * a
}
return this.sampleAndLerp(x3, y3, z3, x4, y4 - y6, z4, x5, y5, z5)
}
private gradDot(a: number, b: number, c: number, d: number) {
const grad = ImprovedNoise.GRADIENT[a & 15]
return grad[0] * b + grad[1] * c + grad[2] * d
}
private P(i: number) {
return this.p[i & 255] & 255
}
public sampleAndLerp(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) {
const j = this.P(a) + b
const k = this.P(j) + c
const l = this.P(j + 1) + c
const m = this.P(a + 1) + b
const n = this.P(m) + c
const o = this.P(m + 1) + c
const p = this.gradDot(this.P(k), d, e, f)
const q = this.gradDot(this.P(n), d - 1, e, f)
const r = this.gradDot(this.P(l), d, e - 1, f)
const s = this.gradDot(this.P(o), d - 1, e - 1, f)
const t = this.gradDot(this.P(k + 1), d, e, f - 1)
const u = this.gradDot(this.P(n + 1), d - 1, e, f - 1)
const v = this.gradDot(this.P(l + 1), d, e - 1, f - 1)
const w = this.gradDot(this.P(o + 1), d - 1, e - 1, f - 1)
return lerp3(g, h, i, p, q, r, s, t, u, v, w)
}
}

View File

@@ -0,0 +1,149 @@
import { clampedLerp, lerp2 } from '../../Utils'
import { PerlinNoise } from './PerlinNoise'
export class NoiseChunkGenerator {
private readonly minLimitPerlinNoise: PerlinNoise
private readonly maxLimitPerlinNoise: PerlinNoise
private readonly mainPerlinNoise: PerlinNoise
private readonly depthNoise: PerlinNoise
private settings: any = {}
private chunkWidth: number = 4
private chunkHeight: number = 4
private chunkCountY: number = 32
private biomeDepth: number = 0.1
private biomeScale: number = 0.2
private noiseColumnCache: (number[] | null)[] = []
private xOffset: number = 0
constructor(seed: string) {
this.minLimitPerlinNoise = PerlinNoise.fromRange(seed + 'djfqnqd', -15, 0)
this.maxLimitPerlinNoise = PerlinNoise.fromRange(seed + 'gowdnqs', -15, 0)
this.mainPerlinNoise = PerlinNoise.fromRange(seed + 'afiwmco', -7, 0)
this.depthNoise = PerlinNoise.fromRange(seed + 'qphnmeo', -15, 0)
}
public reset(settings: any, depth: number, scale: number, xOffset: number, width: number) {
this.settings = settings
this.chunkWidth = settings.size_horizontal * 4
this.chunkHeight = settings.size_vertical * 4
this.chunkCountY = Math.floor(settings.height / this.chunkHeight)
if (settings.amplified && depth > 0) {
depth = 1 + depth * 2
scale = 1 + scale * 4
}
this.biomeDepth = 0.265625 * (depth * 0.5 - 0.125)
this.biomeScale = 96.0 / (scale * 0.9 + 0.1)
this.noiseColumnCache = Array(width).fill(null)
this.xOffset = xOffset
}
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 cachedColumn = this.noiseColumnCache[x - this.xOffset]
if (cachedColumn) return cachedColumn
const data = Array(this.chunkCountY + 1)
const xzScale = 684.412 * this.settings.sampling.xz_scale
const yScale = 684.412 * this.settings.sampling.y_scale
const xzFactor = xzScale / this.settings.sampling.xz_factor
const yFactor = yScale / this.settings.sampling.y_factor
const randomDensity = this.settings.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.settings.density_factor + this.settings.density_offset
const falloff = (density + this.biomeDepth) * this.biomeScale
noise += falloff * (falloff > 0 ? 4 : 1)
if (this.settings.top_slide.size > 0) {
noise = clampedLerp(
this.settings.top_slide.target,
noise,
(this.chunkCountY - y - (this.settings.top_slide.offset)) / (this.settings.top_slide.size)
)
}
if (this.settings.bottom_slide.size > 0) {
noise = clampedLerp(
this.settings.bottom_slide.target,
noise,
(y - (this.settings.bottom_slide.offset)) / (this.settings.bottom_slide.size)
)
}
data[y] = noise
}
this.noiseColumnCache[x - this.xOffset] = data
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)
}
}

View File

@@ -0,0 +1,31 @@
import { PerlinNoise } from './PerlinNoise'
export class NormalNoise {
private readonly valueFactor: number
private readonly first: PerlinNoise
private readonly second: PerlinNoise
constructor(seed: string, firstOctave: number, amplitudes: number[]) {
this.first = new PerlinNoise(seed, firstOctave, amplitudes)
this.second = new PerlinNoise(seed + 'a', firstOctave, amplitudes)
let min = +Infinity
let max = -Infinity
for (let i = 0; i < amplitudes.length; i += 1) {
if (amplitudes[i] !== 0) {
min = Math.min(min, i)
max = Math.max(max, i)
}
}
const expectedDeviation = 0.1 * (1 + 1 / (max - min + 1))
this.valueFactor = (1/6) / expectedDeviation
}
getValue(x: number, y: number, z: number) {
const x2 = x * 1.0181268882175227
const y2 = y * 1.0181268882175227
const z2 = z * 1.0181268882175227
return (this.first.getValue(x, y, z) + this.second.getValue(x2, y2, z2)) * this.valueFactor
}
}

View File

@@ -0,0 +1,54 @@
import seedrandom from 'seedrandom'
import { ImprovedNoise } from './ImprovedNoise'
export class PerlinNoise {
private readonly noiseLevels: ImprovedNoise[]
private readonly amplitudes: number[]
private readonly lowestFreqValueFactor: number
private readonly lowestFreqInputFactor: number
constructor(seed: string, firstOctave: number, amplitudes: number[]) {
this.amplitudes = amplitudes
this.noiseLevels = Array(this.amplitudes.length)
for (let i = 0; i < this.amplitudes.length; i += 1) {
this.noiseLevels[i] = new ImprovedNoise(seedrandom(seed))
}
this.lowestFreqInputFactor = Math.pow(2, firstOctave)
this.lowestFreqValueFactor = Math.pow(2, (amplitudes.length - 1)) / (Math.pow(2, amplitudes.length) - 1)
}
public static fromRange(seed: string, min: number, max: number) {
return new PerlinNoise(seed, min, Array(max - min + 1).fill(1))
}
public getValue(x: number, y: number, z: number, a = 0, b = 0, fixY = false) {
let value = 0
let inputF = this.lowestFreqInputFactor
let valueF = this.lowestFreqValueFactor
for (let i = 0; i < this.noiseLevels.length; i += 1) {
const noise = this.noiseLevels[i]
if (noise) {
value += this.amplitudes[i] * noise.noise(
PerlinNoise.wrap(x * inputF),
fixY ? -noise.yo : PerlinNoise.wrap(y * inputF),
PerlinNoise.wrap(z * inputF),
a * inputF,
b * inputF
) * valueF
}
inputF *= 2
valueF /= 2
}
return value
}
public getOctaveNoise(i: number) {
return this.noiseLevels[this.noiseLevels.length - 1 - i]
}
public static wrap(value: number) {
return value - Math.floor(value / 3.3554432E7 + 0.5) * 3.3554432E7
}
}