pub use position::*;
pub use range::*;
use crate::{enums, noise::BiomeNoise};
use bitflags::bitflags;
use cubiomes_sys::{
enums::{Dimension, MCVersion},
getMinCacheSize, mapApproxHeight,
num_traits::FromPrimitive,
};
use error::GeneratorError;
use std::{
alloc::{alloc, dealloc, Layout},
fmt::Debug,
mem::transmute,
};
pub mod error;
mod position;
mod range;
#[cfg(test)]
mod tests;
bitflags! {
pub struct GeneratorFlags: u32 {
#[allow(missing_docs)]
const LargeBiomes = 0x1;
#[allow(missing_docs)]
const NoBetaOcean = 0x2;
#[allow(missing_docs)]
const ForceOceanVariants = 0x4;
#[allow(missing_docs)]
const _ = !0;
}
}
#[derive(Debug)]
pub struct Generator {
generator: *mut cubiomes_sys::Generator,
}
impl Drop for Generator {
fn drop(&mut self) {
unsafe {
dealloc(self.generator as *mut u8, Layout::new::<Generator>());
}
}
}
unsafe impl Send for Generator {}
unsafe impl Sync for Generator {}
impl Generator {
#[must_use]
pub fn new(
mc_version: MCVersion,
seed: i64,
dimension: enums::Dimension,
flags: GeneratorFlags,
) -> Self {
unsafe {
let mut generator = Generator::new_without_seed(mc_version, flags);
generator.apply_seed(dimension, seed);
generator
}
}
#[must_use]
pub unsafe fn new_without_seed(version: MCVersion, flags: GeneratorFlags) -> Self {
unsafe {
let generator =
alloc(Layout::new::<cubiomes_sys::Generator>()) as *mut cubiomes_sys::Generator;
cubiomes_sys::setupGenerator(generator, version as i32, flags.bits());
Self { generator }
}
}
pub fn apply_seed(&mut self, dimension: enums::Dimension, seed: i64) {
unsafe {
cubiomes_sys::applySeed(
self.generator,
dimension as i32,
transmute::<i64, u64>(seed),
);
}
}
pub fn get_biome_at(&self, x: i32, y: i32, z: i32) -> Result<enums::BiomeID, GeneratorError> {
unsafe {
match cubiomes_sys::getBiomeAt(self.generator, Scale::Block as i32, x, y, z) {
-1 => Err(GeneratorError::GetBiomeAtFailure),
n => FromPrimitive::from_i32(n).ok_or(GeneratorError::BiomeIDOutOfRange(n)),
}
}
}
#[must_use]
pub fn seed(&self) -> i64 {
unsafe { transmute::<u64, i64>((*self.generator).seed) }
}
pub fn dimension(&self) -> enums::Dimension {
unsafe { Dimension::from_i32((*self.as_ptr()).dim).expect("dimension not valid") }
}
#[must_use]
pub fn minecraft_version(&self) -> MCVersion {
MCVersion::from_i32(unsafe { *self.generator }.mc)
.expect("Cubiomes generator has an invalid mc version")
}
pub fn approx_surface_noise(
&self,
x: i32,
z: i32,
size_x: u32,
size_z: u32,
surface_noise: &BiomeNoise,
) -> Option<Vec<f32>> {
if self.minecraft_version() == MCVersion::MC_B1_7
|| self.minecraft_version() == MCVersion::MC_B1_8
{
panic!("Surface height approximation not currently supported for beta minecraft")
} else {
let capacity = (size_x * size_z) as usize;
let mut buff = Vec::with_capacity(capacity);
let BiomeNoise::Release(surface_noise) = surface_noise else {
panic!("Tried to use beta noise with non beta generator")
};
let res = unsafe {
mapApproxHeight(
buff.as_mut_ptr(),
std::ptr::null_mut(),
self.as_ptr(),
surface_noise.as_ptr(),
x,
z,
size_x as i32,
size_z as i32,
)
};
if res != 0 {
return None;
}
unsafe {
buff.set_len(capacity);
}
Some(buff)
}
}
pub unsafe fn as_mut_ptr(&mut self) -> *mut cubiomes_sys::Generator {
self.generator
}
#[must_use]
pub unsafe fn as_ptr(&self) -> *const cubiomes_sys::Generator {
self.generator
}
fn min_cache_size_from_range(&self, range: Range) -> usize {
#[allow(clippy::unwrap_used)]
let raw_range: cubiomes_sys::Range = range.try_into().unwrap();
unsafe {
self.unchecked_min_cache_size(raw_range.scale, raw_range.sx, raw_range.sy, raw_range.sz)
}
}
unsafe fn unchecked_min_cache_size(
&self,
scale: i32,
size_x: i32,
size_y: i32,
size_z: i32,
) -> usize {
unsafe { getMinCacheSize(self.generator, scale, size_x, size_y, size_z) }
}
unsafe fn unchecked_generate_biomes_to_cache(
&self,
cache: &mut Cache,
) -> Result<(), GeneratorError> {
let result_num = cubiomes_sys::genBiomes(
self.generator,
cache.buffer.as_mut_ptr(),
cache.range.try_into()?,
);
if result_num != 0 {
return Err(GeneratorError::GenBiomeToCacheFailure(result_num));
}
cache
.buffer
.set_len(cache.calculate_readable_cache_length());
Ok(())
}
#[allow(unused)]
unsafe fn seed_for_cubiomes(&self) -> u64 {
unsafe { transmute::<i64, u64>(self.seed()) }
}
#[doc = include_str!("../../examples/generate_heightmap.rs")]
#[cfg(feature = "image")]
#[allow(clippy::too_many_arguments)]
pub fn generate_heightmap_image(
&self,
x: i32,
z: i32,
size_x: u32,
size_z: u32,
bottom: f32,
top: f32,
surface_noise: &BiomeNoise,
) -> Option<image::GrayImage> {
use image::GrayImage;
let buf = self.approx_surface_noise(x, z, size_x, size_z, surface_noise)?;
Some(GrayImage::from_fn(size_x, size_z, |img_x, img_z| {
[float_between(
buf[(img_z * size_z + img_x) as usize],
bottom,
top,
)]
.into()
}))
}
}
fn float_between(n: f32, bottom: f32, top: f32) -> u8 {
let range = top - bottom;
((n - bottom) * ((u8::MAX as f32) / range)).clamp(u8::MIN as f32, u8::MAX as f32) as u8
}
#[derive(Clone)]
pub struct Cache<'generator> {
buffer: Vec<i32>,
range: Range,
generator: &'generator Generator,
}
impl Debug for Cache<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, ":")?;
writeln!(f, "Range: {:?}", &self.range)?;
writeln!(f, "Cache: ")?;
for line in self.buffer.chunks(self.range.size_x as usize) {
writeln!(f, "{line:?}")?;
}
Ok(())
}
}
impl Cache<'_> {
pub fn new(generator: &Generator, range: Range) -> Result<Cache<'_>, GeneratorError> {
let cache_size = generator.min_cache_size_from_range(range);
let cache = Vec::with_capacity(cache_size);
let mut cache = Cache {
buffer: cache,
range,
generator,
};
cache.fill_cache().and(Ok(cache))
}
fn fill_cache(&mut self) -> Result<(), GeneratorError> {
unsafe { self.generator.unchecked_generate_biomes_to_cache(self) }
}
#[inline]
#[must_use]
pub fn as_vec(&self) -> &Vec<i32> {
&self.buffer
}
#[inline]
#[must_use]
pub fn range(&self) -> &Range {
&self.range
}
pub fn biome_at(&self, x: u32, y: u32, z: u32) -> Result<enums::BiomeID, GeneratorError> {
let raw_biomeid = *self
.buffer
.get((y * self.range.size_x * self.range.size_z + z * self.range.size_x + x) as usize)
.ok_or(GeneratorError::IndexOutOfBounds)?;
enums::BiomeID::from_i32(raw_biomeid).ok_or(GeneratorError::BiomeIDOutOfRange(raw_biomeid))
}
pub fn move_cache(&mut self, x: i32, y: i32, z: i32) -> Result<(), GeneratorError> {
let new_range = Range {
x,
z,
y,
..self.range
};
self.range = new_range;
self.fill_cache()
}
#[doc = include_str!("../../examples/generate_image.rs")]
#[cfg(feature = "image")]
pub fn to_image(&self, color_map: crate::colors::BiomeColorMap) -> image::RgbImage {
use image::RgbImage;
RgbImage::from_fn(self.range.size_x, self.range.size_z, |x, z| {
color_map[self
.biome_at(x, 0, z)
.expect("Failed to get biome within cache (cache is probably unitialized)")]
.into()
})
}
fn calculate_readable_cache_length(&self) -> usize {
let y_size = match self.range.size_y {
0 => 1,
n => n,
};
(self.range.size_x * self.range.size_z * y_size) as usize
}
}