mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Perlin noise without libm floor or int-to-float conversions, same values
floor_i32 is now Java's Mth.floor (truncate, step down for negative fractions): f64::floor is a libm call on baseline x86-64, made for every Perlin sample. Perlin's gradient table is stored as floats (every component is -1, 0 or 1, exact either way), so the corner dot products skip three conversions each. About 10% fewer instructions generating terrain; values unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01M3pNrSHA2ifNrH7SAMHgSW
This commit is contained in:
3 files changed
+60
-12
No files matched your search
@@ -602,8 +602,16 @@ pub(crate) fn clamp(value: f64, min: f64, max: f64) -> f64 {
|
||||
java_min(value, max)
|
||||
}
|
||||
}
|
||||
/// `Mth.floor`: truncate, then step down for negative fractions. A truncating
|
||||
/// conversion and a compare, where `f64::floor` is a libm call on baseline x86-64.
|
||||
#[inline]
|
||||
pub(crate) fn floor_i32(value: f64) -> i32 {
|
||||
value.floor() as i32
|
||||
let truncated = value as i32;
|
||||
if value < f64::from(truncated) {
|
||||
truncated.wrapping_sub(1)
|
||||
} else {
|
||||
truncated
|
||||
}
|
||||
}
|
||||
fn lerp(amount: f64, start: f64, end: f64) -> f64 {
|
||||
start + amount * (end - start)
|
||||
@@ -635,6 +643,30 @@ fn next_down(value: f64) -> f64 {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn floor_i32_is_mths_floor() {
|
||||
for v in [
|
||||
0.0,
|
||||
-0.0,
|
||||
0.5,
|
||||
-0.5,
|
||||
1.0,
|
||||
-1.0,
|
||||
2.999,
|
||||
-2.999,
|
||||
1e-300,
|
||||
-1e-300,
|
||||
16_777_215.5,
|
||||
-16_777_215.5,
|
||||
] {
|
||||
assert_eq!(floor_i32(v), v.floor() as i32, "{v}");
|
||||
}
|
||||
assert_eq!(floor_i32(f64::NAN), 0);
|
||||
assert_eq!(floor_i32(3e9), i32::MAX);
|
||||
// Java's (int) cast saturates, then the step down wraps.
|
||||
assert_eq!(floor_i32(-3e9), i32::MAX);
|
||||
}
|
||||
#[test]
|
||||
fn ray_faces_and_half_open_bounds() {
|
||||
let b = Aabb::block(BlockPos::new(0, 0, 0));
|
||||
|
||||
@@ -61,8 +61,8 @@ impl GradientTable {
|
||||
i32::from(self.permutations[(index & 255) as usize])
|
||||
}
|
||||
|
||||
fn gradient(&self, index: i32) -> [i32; 3] {
|
||||
GRADIENTS[(self.permute(index) & 15) as usize]
|
||||
fn gradient(&self, index: i32) -> [f32; 3] {
|
||||
PERLIN_GRADIENTS[(self.permute(index) & 15) as usize]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,24 @@ const GRADIENTS: [[i32; 3]; 16] = [
|
||||
[0, -1, -1],
|
||||
];
|
||||
|
||||
fn dot(gradient: [i32; 3], point: [f32; 3]) -> f32 {
|
||||
gradient[0] as f32 * point[0] + gradient[1] as f32 * point[1] + gradient[2] as f32 * point[2]
|
||||
/// [`GRADIENTS`] as floats, so a dot product needs no conversions: every
|
||||
/// component is -1, 0 or 1, which a float holds exactly.
|
||||
const PERLIN_GRADIENTS: [[f32; 3]; 16] = {
|
||||
let mut out = [[0.0; 3]; 16];
|
||||
let mut i = 0;
|
||||
while i < 16 {
|
||||
out[i] = [
|
||||
GRADIENTS[i][0] as f32,
|
||||
GRADIENTS[i][1] as f32,
|
||||
GRADIENTS[i][2] as f32,
|
||||
];
|
||||
i += 1;
|
||||
}
|
||||
out
|
||||
};
|
||||
|
||||
fn dot(gradient: [f32; 3], point: [f32; 3]) -> f32 {
|
||||
gradient[0] * point[0] + gradient[1] * point[1] + gradient[2] * point[2]
|
||||
}
|
||||
|
||||
/// Vanilla's periodic coordinate reduction with the normal Far Lands flag off.
|
||||
|
||||
@@ -39,7 +39,7 @@ impl PerlinNoise {
|
||||
)
|
||||
}
|
||||
|
||||
fn gradients(&self, cell: [i32; 3]) -> [[i32; 3]; 8] {
|
||||
fn gradients(&self, cell: [i32; 3]) -> [[f32; 3]; 8] {
|
||||
self.gradients_at(
|
||||
self.table.permute(cell[0]),
|
||||
self.table.permute(cell[0].wrapping_add(1)),
|
||||
@@ -52,7 +52,7 @@ impl PerlinNoise {
|
||||
/// resolved. `add_volume` reuses one X column across every Y, so hoisting
|
||||
/// the two X permutes out of that loop avoids repeating them per Y.
|
||||
#[inline]
|
||||
fn gradients_at(&self, p: i32, q: i32, cell_y: i32, cell_z: i32) -> [[i32; 3]; 8] {
|
||||
fn gradients_at(&self, p: i32, q: i32, cell_y: i32, cell_z: i32) -> [[f32; 3]; 8] {
|
||||
let lower = self.table.permute(p.wrapping_add(cell_y));
|
||||
let upper = self.table.permute(p.wrapping_add(cell_y).wrapping_add(1));
|
||||
let lower_x = self.table.permute(q.wrapping_add(cell_y));
|
||||
@@ -66,7 +66,7 @@ impl PerlinNoise {
|
||||
})
|
||||
}
|
||||
|
||||
fn corner_values(gradients: [[i32; 3]; 8], point: [f32; 3]) -> [f32; 8] {
|
||||
fn corner_values(gradients: [[f32; 3]; 8], point: [f32; 3]) -> [f32; 8] {
|
||||
std::array::from_fn(|i| {
|
||||
dot(
|
||||
gradients[i],
|
||||
@@ -99,7 +99,7 @@ impl PerlinNoise {
|
||||
let values = Self::corner_values(gradients, point);
|
||||
let fade = point.map(smoothstep);
|
||||
let gradient =
|
||||
std::array::from_fn::<_, 3, _>(|axis| lerp3(fade, gradients.map(|g| g[axis] as f32)));
|
||||
std::array::from_fn::<_, 3, _>(|axis| lerp3(fade, gradients.map(|g| g[axis])));
|
||||
let changes = [
|
||||
lerp2(
|
||||
fade[1],
|
||||
@@ -191,9 +191,9 @@ impl PerlinNoise {
|
||||
if previous_y != cell_y {
|
||||
let gradients = self.gradients_at(p, q, cell_y, cell_z);
|
||||
for i in 0..8 {
|
||||
xz_dots[i] = gradients[i][0] as f32 * (dx - (i & 1) as f32)
|
||||
+ gradients[i][2] as f32 * (dz - ((i >> 2) & 1) as f32);
|
||||
y_gradients[i] = gradients[i][1] as f32;
|
||||
xz_dots[i] = gradients[i][0] * (dx - (i & 1) as f32)
|
||||
+ gradients[i][2] * (dz - ((i >> 2) & 1) as f32);
|
||||
y_gradients[i] = gradients[i][1];
|
||||
}
|
||||
previous_y = cell_y;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user