Current section
Files
Jump to
Current section
Files
native/noise/src/lib.rs
// Vendored upstream code, kept as close to the original as possible; it exposes
// helpers Isotope does not call.
#[allow(dead_code)]
mod fastnoise;
#[cfg(test)]
mod golden;
use fastnoise::{
CellularDistanceFunction, CellularReturnType, FastNoise, FractalType, Interp, NoiseType,
};
use rayon::prelude::*;
use rustler::types::binary::NewBinary;
use rustler::ResourceArc;
use rustler::{Atom, Binary, Env, NifStruct, Term};
rustler::atoms! {
// Noise types
simplex, simplex_fractal, perlin, perlin_fractal,
white, cubic, cubic_fractal, value, value_fractal,
cellular,
// Fractal types
fbm, rigid_multi, billow,
// Interpolation types
linear, hermite, quintic,
// Cellular distance functions
euclidean, manhattan, natural,
// Cellular return type
cell_value, distance, distance2, distance2add,
distance2sub, distance2mul, distance2div,
// Others
error, ok, wrote
}
type NifNoiseType = Atom;
type NifFractalType = Atom;
type NifInterpolationType = Atom;
struct NoiseWrapper {
noise: FastNoise,
}
#[derive(NifStruct)]
#[module = "Isotope.Options"]
struct NoiseOptions {
noise_type: NifNoiseType,
interpolation: NifInterpolationType,
cellular_options: CellularOptions,
fractal_options: FractalOption,
frequency: f32,
seed: u64,
}
#[derive(NifStruct)]
#[module = "Isotope.Options.Cellular"]
struct CellularOptions {
distance_function: Atom,
return_type: Atom,
distance_indices: (i32, i32),
jitter: f32,
}
#[derive(NifStruct)]
#[module = "Isotope.Options.Fractal"]
struct FractalOption {
fractal_type: NifFractalType,
lacunarity: f32,
octaves: i32,
gain: f32,
}
#[rustler::nif]
fn get_noise(noise: ResourceArc<NoiseWrapper>, x: f32, y: f32) -> f32 {
noise.noise.get_noise(x, y)
}
#[rustler::nif]
fn get_noise3d(noise: ResourceArc<NoiseWrapper>, x: f32, y: f32, z: f32) -> f32 {
noise.noise.get_noise3d(x, y, z)
}
#[rustler::nif(schedule = "DirtyCpu")]
fn noise_map<'a>(
env: Env<'a>,
noise: ResourceArc<NoiseWrapper>,
width: u64,
height: u64,
) -> (Binary<'a>, u64, u64) {
internal_chunk(env, noise, 0, 0, width, height)
}
#[rustler::nif]
fn new(options: NoiseOptions) -> (Atom, ResourceArc<NoiseWrapper>) {
let mut noise = FastNoise::seeded(options.seed);
let cellular_opt = options.cellular_options;
let fractal_opt = options.fractal_options;
let noise_type = get_noise_type(options.noise_type);
let interpolation = get_interpolation(options.interpolation);
noise.set_interp(interpolation);
noise.set_noise_type(noise_type);
noise.set_frequency(options.frequency);
let fractal_type = get_fractal_type(fractal_opt.fractal_type);
noise.set_fractal_gain(fractal_opt.gain);
noise.set_fractal_type(fractal_type);
noise.set_fractal_octaves(fractal_opt.octaves);
noise.set_fractal_lacunarity(fractal_opt.lacunarity);
let distance_function = get_distance_function(cellular_opt.distance_function);
let return_type = get_cellular_return_type(cellular_opt.return_type);
noise.set_cellular_jitter(cellular_opt.jitter);
noise.set_cellular_return_type(return_type);
noise.set_cellular_distance_indices(
cellular_opt.distance_indices.0,
cellular_opt.distance_indices.1,
);
noise.set_cellular_distance_function(distance_function);
let noise_struct = NoiseWrapper { noise };
(ok(), ResourceArc::new(noise_struct))
}
impl ::rustler::Resource for NoiseWrapper {}
fn load(env: Env, _info: Term) -> bool {
let _ = env.register::<NoiseWrapper>().is_ok();
true
}
fn get_fractal_type(atom: Atom) -> FractalType {
match atom {
_ if atom == fbm() => FractalType::FBM,
_ if atom == billow() => FractalType::Billow,
_ if atom == rigid_multi() => FractalType::RigidMulti,
_ => FractalType::FBM,
}
}
fn get_distance_function(atom: Atom) -> CellularDistanceFunction {
match atom {
_ if atom == euclidean() => CellularDistanceFunction::Euclidean,
_ if atom == manhattan() => CellularDistanceFunction::Manhattan,
_ if atom == natural() => CellularDistanceFunction::Natural,
_ => CellularDistanceFunction::Euclidean,
}
}
fn get_cellular_return_type(atom: Atom) -> CellularReturnType {
match atom {
_ if atom == cell_value() => CellularReturnType::CellValue,
_ if atom == distance() => CellularReturnType::Distance,
_ if atom == distance2() => CellularReturnType::Distance2,
_ if atom == distance2add() => CellularReturnType::Distance2Add,
_ if atom == distance2sub() => CellularReturnType::Distance2Sub,
_ if atom == distance2mul() => CellularReturnType::Distance2Mul,
_ if atom == distance2div() => CellularReturnType::Distance2Div,
_ => CellularReturnType::CellValue,
}
}
fn get_interpolation(atom: NifInterpolationType) -> Interp {
match atom {
_ if atom == linear() => Interp::Linear,
_ if atom == hermite() => Interp::Hermite,
_ if atom == quintic() => Interp::Quintic,
_ => Interp::Quintic,
}
}
fn get_noise_type(atom: Atom) -> NoiseType {
match atom {
_ if atom == value() => NoiseType::Value,
_ if atom == value_fractal() => NoiseType::ValueFractal,
_ if atom == cubic() => NoiseType::Cubic,
_ if atom == cubic_fractal() => NoiseType::CubicFractal,
_ if atom == cellular() => NoiseType::Cellular,
_ if atom == white() => NoiseType::WhiteNoise,
_ if atom == perlin() => NoiseType::Perlin,
_ if atom == perlin_fractal() => NoiseType::PerlinFractal,
_ if atom == simplex() => NoiseType::Simplex,
_ if atom == simplex_fractal() => NoiseType::SimplexFractal,
_ => NoiseType::Simplex,
}
}
fn internal_chunk<'a>(
env: Env<'a>,
noise: ResourceArc<NoiseWrapper>,
px: i64,
py: i64,
width: u64,
height: u64,
) -> (Binary<'a>, u64, u64) {
let w = width as usize;
let h = height as usize;
let byte_size = w * h * std::mem::size_of::<f32>();
let row_bytes = w * 4;
// Compute noise in parallel — each thread owns one row's slice
let mut buf = vec![0u8; byte_size];
buf.par_chunks_mut(row_bytes)
.enumerate()
.for_each(|(y, row_buf)| {
for x in 0..w {
let point = noise.noise.get_noise(
(px + x as i64) as f32,
(py + y as i64) as f32,
);
let offset = x * 4;
row_buf[offset..offset + 4].copy_from_slice(&point.to_le_bytes());
}
});
let mut binary = NewBinary::new(env, byte_size);
binary.as_mut_slice().copy_from_slice(&buf);
(binary.into(), width, height)
}
#[rustler::nif(schedule = "DirtyCpu")]
fn chunk<'a>(
env: Env<'a>,
noise: ResourceArc<NoiseWrapper>,
px: i64,
py: i64,
width: u64,
height: u64,
) -> (Binary<'a>, u64, u64) {
internal_chunk(env, noise, px, py, width, height)
}
rustler::init!("Elixir.Isotope.NIF", load = load);