From 2211e8d2d3507618622b619280f9c32b286bd7e7 Mon Sep 17 00:00:00 2001 From: Jochen Jacobs Date: Tue, 29 Jul 2025 23:39:16 +0200 Subject: [PATCH 1/2] 25w31a changes - add invert and find_top_surface density functions - update noise router with preliminary_surface_level --- src/worldgen/DensityFunction.ts | 57 ++++++++++++++++++++++++++- src/worldgen/NoiseChunk.ts | 17 +++----- src/worldgen/NoiseRouter.ts | 8 ++-- test/worldgen/DensityFunction.test.ts | 18 ++++++++- 4 files changed, 82 insertions(+), 18 deletions(-) diff --git a/src/worldgen/DensityFunction.ts b/src/worldgen/DensityFunction.ts index 1f0d41ec..03a930cd 100644 --- a/src/worldgen/DensityFunction.ts +++ b/src/worldgen/DensityFunction.ts @@ -120,6 +120,7 @@ export namespace DensityFunction { case 'half_negative': case 'quarter_negative': case 'squeeze': + case 'invert': return new Mapped(type, inputParser(root.argument)) case 'add': case 'mul': @@ -139,6 +140,12 @@ export namespace DensityFunction { Json.readNumber(root.from_value) ?? -4064, Json.readNumber(root.to_value) ?? 4062, ) + case 'find_top_surface': return new FindTopSurface( + inputParser(root.density), + inputParser(root.upper_bound), + Json.readInt(root.lower_bound) ?? 0, + Json.readInt(root.cell_height) ?? 1 + ) } return Constant.ZERO } @@ -621,7 +628,7 @@ export namespace DensityFunction { } } - const MappedType = ['abs', 'square', 'cube', 'half_negative', 'quarter_negative', 'squeeze'] as const + const MappedType = ['abs', 'square', 'cube', 'half_negative', 'quarter_negative', 'squeeze', 'invert'] as const export class Mapped extends Transformer { private static readonly MappedTypes: Record number> = { @@ -634,6 +641,7 @@ export namespace DensityFunction { const c = clamp(d, -1, 1) return c / 2 - c * c * c / 24 }, + invert: d => 1 / d } private readonly transformer: (density: number) => number constructor( @@ -661,7 +669,14 @@ export namespace DensityFunction { const minInput = this.input.minValue() let min = this.transformer(minInput) let max = this.transformer(this.input.maxValue()) - if (this.type === 'abs' || this.type === 'square') { + if (this.type === 'invert') { + if (min < 0 && max > 0) { + min = Number.NEGATIVE_INFINITY, + max = Number.POSITIVE_INFINITY + } else { + [min, max] = [max, min] + } + } else if (this.type === 'abs' || this.type === 'square') { max = Math.max(min, max) min = Math.max(0, minInput) } @@ -780,4 +795,42 @@ export namespace DensityFunction { return Math.max(this.fromValue, this.toValue) } } + + export class FindTopSurface extends DensityFunction { + constructor( + public readonly density: DensityFunction, + public readonly upper_bound: DensityFunction, + public readonly lower_bound: number, + public readonly cell_height: number + ) { + super() + } + + public maxValue(): number { + return Math.max(this.lower_bound, this.upper_bound.maxValue()) + } + + public minValue(): number { + return this.lower_bound + } + + public mapAll(visitor: Visitor): DensityFunction { + return visitor.map(new FindTopSurface(this.density.mapAll(visitor), this.upper_bound.mapAll(visitor), this.lower_bound, this.cell_height)) + } + + public compute(context: Context): number { + const upper_bound = Math.floor(this.upper_bound.compute(context) / this.cell_height) * this.cell_height + if (upper_bound <= this.lower_bound) + return this.lower_bound + + for (var y = upper_bound; y >= this.lower_bound; y -= this.cell_height) { + if (this.density.compute(DensityFunction.context(context.x, y, context.z)) > 0) { + return y + } + } + + return this.lower_bound + } + + } } diff --git a/src/worldgen/NoiseChunk.ts b/src/worldgen/NoiseChunk.ts index a7419f5c..560302da 100644 --- a/src/worldgen/NoiseChunk.ts +++ b/src/worldgen/NoiseChunk.ts @@ -15,10 +15,10 @@ export class NoiseChunk { public readonly firstNoiseX: number public readonly firstNoiseZ: number public readonly noiseSizeXZ: number - private readonly preliminarySurfaceLevel: Map = new Map() + private readonly preliminarySurfaceLevelCache: Map = new Map() private readonly aquifer: Aquifer private readonly materialRule: MaterialRule - private readonly initialDensity: DensityFunction + private readonly preliminarySurfaceLevel: DensityFunction constructor( public readonly cellCountXZ: number, @@ -51,7 +51,7 @@ export class NoiseChunk { this.materialRule = MaterialRule.fromList([ (context) => this.aquifer.compute(context, finalDensity.compute(context)), ]) - this.initialDensity = randomState.router.initialDensityWithoutJaggedness + this.preliminarySurfaceLevel = randomState.router.preliminarySurfaceLevel } public getFinalState(x: number, y: number, z: number): BlockState | undefined { @@ -59,16 +59,11 @@ export class NoiseChunk { } public getPreliminarySurfaceLevel(quartX: number, quartZ: number) { - return computeIfAbsent(this.preliminarySurfaceLevel, ChunkPos.asLong(quartX, quartZ), () => { + return computeIfAbsent(this.preliminarySurfaceLevelCache, ChunkPos.asLong(quartX, quartZ), () => { const x = quartX << 2 const z = quartZ << 2 - for (let y = this.settings.minY + this.settings.height; y >= this.settings.minY; y -= this.cellHeight) { - const density = this.initialDensity.compute(DensityFunction.context(x, y, z)) - if (density > 0.390625) { - return y - } - } - return Number.MAX_SAFE_INTEGER + + return Math.floor(this.preliminarySurfaceLevel.compute(DensityFunction.context(x, 0, z))) }) } } diff --git a/src/worldgen/NoiseRouter.ts b/src/worldgen/NoiseRouter.ts index 2c91b946..242ddb9a 100644 --- a/src/worldgen/NoiseRouter.ts +++ b/src/worldgen/NoiseRouter.ts @@ -16,7 +16,7 @@ export interface NoiseRouter { erosion: DensityFunction, depth: DensityFunction, ridges: DensityFunction, - initialDensityWithoutJaggedness: DensityFunction, + preliminarySurfaceLevel: DensityFunction, finalDensity: DensityFunction, veinToggle: DensityFunction, veinRidged: DensityFunction, @@ -39,7 +39,7 @@ export namespace NoiseRouter { erosion: fieldParser(root.erosion), depth: fieldParser(root.depth), ridges: fieldParser(root.ridges), - initialDensityWithoutJaggedness: fieldParser(root.initial_density_without_jaggedness), + preliminarySurfaceLevel: fieldParser(root.preliminary_surface_level), finalDensity: fieldParser(root.final_density), veinToggle: fieldParser(root.vein_toggle), veinRidged: fieldParser(root.vein_ridged), @@ -59,7 +59,7 @@ export namespace NoiseRouter { erosion: DensityFunction.Constant.ZERO, depth: DensityFunction.Constant.ZERO, ridges: DensityFunction.Constant.ZERO, - initialDensityWithoutJaggedness: DensityFunction.Constant.ZERO, + preliminarySurfaceLevel: DensityFunction.Constant.ZERO, finalDensity: DensityFunction.Constant.ZERO, veinToggle: DensityFunction.Constant.ZERO, veinRidged: DensityFunction.Constant.ZERO, @@ -80,7 +80,7 @@ export namespace NoiseRouter { erosion: router.erosion.mapAll(visitor), depth: router.depth.mapAll(visitor), ridges: router.ridges.mapAll(visitor), - initialDensityWithoutJaggedness: router.initialDensityWithoutJaggedness.mapAll(visitor), + preliminarySurfaceLevel: router.preliminarySurfaceLevel.mapAll(visitor), finalDensity: router.finalDensity.mapAll(visitor), veinToggle: router.veinToggle.mapAll(visitor), veinRidged: router.veinRidged.mapAll(visitor), diff --git a/test/worldgen/DensityFunction.test.ts b/test/worldgen/DensityFunction.test.ts index d1fcfc4e..ebeb1934 100644 --- a/test/worldgen/DensityFunction.test.ts +++ b/test/worldgen/DensityFunction.test.ts @@ -2,8 +2,8 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { Holder } from '../../src/core/Holder.js' import { Identifier } from '../../src/core/Identifier.js' import { CubicSpline, NoiseParameters } from '../../src/math/index.js' -import { DensityFunction as DF, NoiseGeneratorSettings, NoiseRouter, WorldgenRegistries } from '../../src/worldgen/index.js' import { RandomState } from '../../src/worldgen/RandomState.js' +import { DensityFunction as DF, NoiseGeneratorSettings, NoiseRouter, WorldgenRegistries } from '../../src/worldgen/index.js' describe('DensityFunction', () => { const DELTA = 1e-7 @@ -107,6 +107,13 @@ describe('DensityFunction', () => { expect(fn3.compute(ContextA)).toEqual(-0.33570833333333333) }) + it('Invert', () => { + const fn1 = wrap(new DF.Mapped('invert', new DF.Constant(2))) + expect(fn1.compute(ContextA)).toEqual(0.5) + const fn2 = wrap(new DF.Mapped('invert', new DF.Constant(0))) + expect(fn2.compute(ContextA)).toEqual(Number.POSITIVE_INFINITY) + }) + it('Add', () => { const fn1 = wrap(new DF.Ap2('add', new DF.Constant(2), new DF.Constant(3))) expect(fn1.compute(ContextA)).toEqual(5) @@ -177,4 +184,13 @@ describe('DensityFunction', () => { expect(fn2.compute(DF.context(0, 128, 0))).toEqual(0) expect(fn2.compute(DF.context(0, 129, 0))).toEqual(0) }) + + it('FindTopSurface', () => { + const fn1 = wrap(new DF.FindTopSurface(new DF.YClampedGradient(128, -128, -1, 1), new DF.Constant(128), -128, 1)) + expect(fn1.compute(ContextA)).toEqual(-1) + const fn2 = wrap(new DF.FindTopSurface(new DF.YClampedGradient(128, -128, -1, 1), new DF.Constant(-20), -128, 1)) + expect(fn2.compute(ContextA)).toEqual(-20) + const fn3 = wrap(new DF.FindTopSurface(new DF.YClampedGradient(128, -128, -1, 1), new DF.Constant(50), 128, 1)) + expect(fn3.compute(ContextA)).toEqual(128) + }) }) From 9451856693b9f8dc278f8a1e1c14f8beb6a350b8 Mon Sep 17 00:00:00 2001 From: Misode Date: Fri, 2 Jan 2026 18:44:06 +0100 Subject: [PATCH 2/2] Remove duplicate invert case --- src/worldgen/DensityFunction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/worldgen/DensityFunction.ts b/src/worldgen/DensityFunction.ts index 83b3bc86..9384e310 100644 --- a/src/worldgen/DensityFunction.ts +++ b/src/worldgen/DensityFunction.ts @@ -127,7 +127,6 @@ export namespace DensityFunction { case 'invert': case 'quarter_negative': case 'squeeze': - case 'invert': return new Mapped(type, inputParser(root.argument)) case 'add': case 'mul':