Skip to content

Commit

Permalink
"populate noise" step of chunk generation (#319)
Browse files Browse the repository at this point in the history
* implement populate noise step of chunk generation

* fix gitignore

* pass thru seed

* add counter for edge case

* add entire chunk test case

* remove eclipse file

* remove packet timing

* precompute float divisions

* unsafe speed up

* slight aquifer optimization

* tweak block pos packing

* optimize perlin map

* precompute aquifer random positions

* fix aquifer regression

* refactor samplers, add tests to aquifer, and move chunk extractor to the extractors

* fix base density function error and add a bunch of test cases

* ignore expensive tests by default

* remove un-needed lifetimes

* remove minecraft prefix from block lookups

* fix populate noise bench

* slight optimizations

* read files at runtime to speed up test compilation speed and remove ignores

* remove new lines from test data so people stop freaking out about line count

* make extractor the same

* leverage branch prediction

---------

Co-authored-by: Alexander Medvedev <[email protected]>
  • Loading branch information
kralverde and Snowiiii authored Dec 2, 2024
1 parent 5742bdd commit 979877f
Show file tree
Hide file tree
Showing 70 changed files with 29,693 additions and 3,156 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,7 @@ node_modules

run/

# Benchmarking
*perf.data*
*flamegraph.svg

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ opt-level = 1
lto = true
codegen-units = 1

[profile.bench]
debug = true

[profile.profiling]
inherits = "release"
debug = true
Expand Down
3 changes: 3 additions & 0 deletions extractor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ dependencies {
modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}")

modImplementation("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_version")}")

// To allow for reflection
implementation(kotlin("reflect"))
}

tasks.processResources {
Expand Down
2 changes: 2 additions & 0 deletions extractor/src/main/kotlin/de/snowii/extractor/Extractor.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.snowii.extractor

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import de.snowii.extractor.extractors.*
Expand Down Expand Up @@ -32,6 +33,7 @@ class Extractor : ModInitializer {
Tags(),
Items(),
Blocks(),
Tests(),
)

val outputDirectory: Path
Expand Down
142 changes: 142 additions & 0 deletions extractor/src/main/kotlin/de/snowii/extractor/extractors/Tests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@

package de.snowii.extractor.extractors

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import de.snowii.extractor.Extractor
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.registry.BuiltinRegistries
import net.minecraft.registry.RegistryKeys
import net.minecraft.registry.RegistryWrapper
import net.minecraft.registry.RegistryWrapper.WrapperLookup
import net.minecraft.registry.entry.RegistryEntry.Reference
import net.minecraft.server.MinecraftServer
import net.minecraft.util.math.noise.DoublePerlinNoiseSampler.NoiseParameters
import net.minecraft.util.math.ChunkPos
import net.minecraft.world.gen.chunk.AquiferSampler
import net.minecraft.world.gen.chunk.Blender
import net.minecraft.world.gen.chunk.ChunkGeneratorSettings
import net.minecraft.world.gen.chunk.ChunkNoiseSampler
import net.minecraft.world.gen.chunk.GenerationShapeConfig
import net.minecraft.world.gen.densityfunction.DensityFunction.NoisePos;
import net.minecraft.world.gen.densityfunction.DensityFunction.EachApplier;
import net.minecraft.world.gen.densityfunction.DensityFunctionTypes
import net.minecraft.world.gen.noise.NoiseConfig

import java.lang.reflect.Method
import java.util.Arrays
import kotlin.reflect.full.createType
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.jvm.javaMethod
import kotlin.reflect.KFunction

class Tests : Extractor.Extractor {
override fun fileName(): String = "chunk.json"

private fun createFluidLevelSampler(settings: ChunkGeneratorSettings): AquiferSampler.FluidLevelSampler {
val fluidLevel = AquiferSampler.FluidLevel(-54, Blocks.LAVA.getDefaultState());
val i = settings.seaLevel();
val fluidLevel2 = AquiferSampler.FluidLevel(i, settings.defaultFluid());
return AquiferSampler.FluidLevelSampler {_, y, _ -> if (y < Math.min(-54, i)) fluidLevel else fluidLevel2};
}

private fun get_index(config: GenerationShapeConfig, x: Int, y: Int, z: Int): Int {
if (x < 0 || y < 0 || z < 0) {
System.err.println("Bad local pos");
System.exit(1);
}
return config.height() * 16 * x + 16 * y + z
}

// This is basically just what NoiseChunkGenerator is doing
private fun populate_noise(start_x: Int, start_z: Int, sampler: ChunkNoiseSampler, config: GenerationShapeConfig, settings: ChunkGeneratorSettings): IntArray? {
val result = IntArray(16 * 16 * config.height())

for (method: KFunction<*> in sampler::class.declaredFunctions) {
if (method.name.equals("sampleBlockState")) {
sampler.sampleStartDensity()
val k = config.horizontalCellBlockCount()
val l = config.verticalCellBlockCount()

val m = 16 / k
val n = 16 / k

val cellHeight = config.height() / l
val minimumCellY = config.minimumY() / l

for (o in 0..<m) {
sampler.sampleEndDensity(o)
for (p in 0..<n) {
for (r in (0..<cellHeight).reversed()) {
sampler.onSampledCellCorners(r, p)
for (s in (0..<l).reversed()) {
val t = (minimumCellY + r) * l + s
val d = s.toDouble() / l.toDouble()
sampler.interpolateY(t, d)
for (w in 0..<k) {
val x = start_x + o * k + w
val y = x and 15
val e = w.toDouble() / k.toDouble()
sampler.interpolateX(x, e)
for (z in 0..<k) {
val aa = start_z + p * k + z
val ab = aa and 15
val f = z.toDouble() / k.toDouble()
sampler.interpolateZ(aa, f)
var blockstate = method.call(sampler) as BlockState?
if (blockstate == null) {
blockstate = settings.defaultBlock()
}
val index = this.get_index(config, y, t - config.minimumY(), ab)
result[index] = Block.getRawIdFromState(blockstate)
}
}
}
}
}
sampler.swapBuffers()
}
sampler.stopInterpolation()
return result
}
}
System.err.println("No valid method found for block state sampler!");
return null;
}

// Dumps a chunk to an array of block state ids
override fun extract(server: MinecraftServer): JsonElement {
val topLevelJson = JsonArray()
val seed = 0L
val chunk_pos = ChunkPos(7, 4)

val lookup = BuiltinRegistries.createWrapperLookup()
val wrapper = lookup.getOrThrow(RegistryKeys.CHUNK_GENERATOR_SETTINGS)
val noise_params = lookup.getOrThrow(RegistryKeys.NOISE_PARAMETERS)

val ref = wrapper.getOrThrow(ChunkGeneratorSettings.OVERWORLD)
val settings = ref.value()
val config = NoiseConfig.create(settings, noise_params, seed)

// Overworld shape config
val shape = GenerationShapeConfig(-64, 384, 1, 2)
val test_sampler = ChunkNoiseSampler(16 / shape.horizontalCellBlockCount(), config, chunk_pos.startX, chunk_pos.startZ,
shape, object: DensityFunctionTypes.Beardifying{
override fun maxValue(): Double = 0.0
override fun minValue(): Double = 0.0
override fun sample(pos: NoisePos): Double = 0.0
override fun fill(densities: DoubleArray, applier: EachApplier) {densities.fill(0.0)}
}, settings, createFluidLevelSampler(settings), Blender.getNoBlending())

val data = populate_noise(chunk_pos.startX, chunk_pos.startZ, test_sampler, shape, settings)
data?.forEach { state ->
topLevelJson.add(state)
}

return topLevelJson
}
}
1 change: 1 addition & 0 deletions pumpkin-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ num-derive.workspace = true
colored = "2"
md5 = "0.7.0"

enum_dispatch = "0.3.13"
9 changes: 9 additions & 0 deletions pumpkin-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@ pub enum ProfileAction {
ForcedNameChange,
UsingBannedSkin,
}

#[macro_export]
macro_rules! assert_eq_delta {
($x:expr, $y:expr, $d:expr) => {
if !(2f64 * ($x - $y).abs() <= $d * ($x.abs() + $y.abs())) {
panic!("{} vs {} ({} vs {})", $x, $y, ($x - $y).abs(), $d);
}
};
}
59 changes: 59 additions & 0 deletions pumpkin-core/src/math/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use num_traits::PrimInt;

pub mod boundingbox;
pub mod position;
pub mod vector2;
Expand Down Expand Up @@ -30,3 +32,60 @@ pub fn magnitude(a: f64, b: f64, c: f64) -> f64 {
pub const fn get_section_cord(coord: i32) -> i32 {
coord >> 4
}

const MULTIPLY_DE_BRUIJN_BIT_POSITION: [u8; 32] = [
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26,
12, 18, 6, 11, 5, 10, 9,
];

/// Maximum return value: 31
pub const fn ceil_log2(value: u32) -> u8 {
let value = if value.is_power_of_two() {
value
} else {
smallest_encompassing_power_of_two(value)
};

MULTIPLY_DE_BRUIJN_BIT_POSITION[(((value as usize) * 125613361) >> 27) & 31]
}

/// Maximum return value: 30
pub const fn floor_log2(value: u32) -> u8 {
ceil_log2(value) - if value.is_power_of_two() { 0 } else { 1 }
}

pub const fn smallest_encompassing_power_of_two(value: u32) -> u32 {
let mut i = value - 1;
i |= i >> 1;
i |= i >> 2;
i |= i >> 4;
i |= i >> 8;
i |= i >> 16;
i + 1
}

#[inline]
pub fn floor_div<T>(x: T, y: T) -> T
where
T: PrimInt + From<i8>,
{
let div = x / y;
if (x ^ y) < 0.into() && div * y != x {
div - 1.into()
} else {
div
}
}

#[inline]
pub fn floor_mod<T>(x: T, y: T) -> T
where
T: PrimInt + From<i8>,
{
let rem = x % y;
if (x ^ y) < 0.into() && rem != 0.into() {
rem + y
} else {
rem
}
}
5 changes: 3 additions & 2 deletions pumpkin-core/src/math/vector2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ use num_traits::Float;

use super::vector3::Vector3;

#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Default)]
pub struct Vector2<T> {
pub x: T,
pub z: T,
}

impl<T: Math + Copy> Vector2<T> {
pub fn new(x: T, z: T) -> Self {
pub const fn new(x: T, z: T) -> Self {
Vector2 { x, z }
}

Expand Down Expand Up @@ -114,3 +114,4 @@ impl Math for f64 {}
impl Math for f32 {}
impl Math for i32 {}
impl Math for i64 {}
impl Math for i8 {}
9 changes: 6 additions & 3 deletions pumpkin-core/src/math/vector3.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::ops::{Add, Div, Mul, Neg, Sub};
use std::ops::{Add, Div, Mul, Sub};

use num_traits::Float;

#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Default)]
pub struct Vector3<T> {
pub x: T,
pub y: T,
Expand Down Expand Up @@ -80,6 +80,7 @@ impl<T: Math + Copy> Add for Vector3<T> {
}
}

/*
impl<T: Math + Copy> Neg for Vector3<T> {
type Output = Self;
Expand All @@ -91,6 +92,7 @@ impl<T: Math + Copy> Neg for Vector3<T> {
}
}
}
*/

impl<T> From<(T, T, T)> for Vector3<T> {
#[inline(always)]
Expand All @@ -108,7 +110,7 @@ impl<T> From<Vector3<T>> for (T, T, T) {

pub trait Math:
Mul<Output = Self>
+ Neg<Output = Self>
//+ Neg<Output = Self>
+ Add<Output = Self>
+ Div<Output = Self>
+ Sub<Output = Self>
Expand All @@ -119,3 +121,4 @@ impl Math for f64 {}
impl Math for f32 {}
impl Math for i32 {}
impl Math for i64 {}
impl Math for u8 {}
Loading

0 comments on commit 979877f

Please sign in to comment.