From 7916a0206a2c183c11204caeeac656007777a0a0 Mon Sep 17 00:00:00 2001 From: SuperB3333 Date: Sat, 11 Jul 2026 16:45:24 +0200 Subject: [PATCH 1/2] Copied over new algorithm and adapted the old and new code to work together --- inserter/src/algorithm/data/birth_points.rs | 139 ++ inserter/src/algorithm/data/enums.rs | 24 +- inserter/src/algorithm/data/game_desc.rs | 33 +- inserter/src/algorithm/data/macros.rs | 38 - inserter/src/algorithm/data/math.rs | 77 + inserter/src/algorithm/data/mod.rs | 14 +- inserter/src/algorithm/data/planet.rs | 1388 ++++++++++++----- .../algorithm/data/planet_algorithms/algo0.rs | 22 + .../algorithm/data/planet_algorithms/algo1.rs | 83 + .../data/planet_algorithms/algo10.rs | 243 +++ .../data/planet_algorithms/algo11.rs | 99 ++ .../data/planet_algorithms/algo12.rs | 104 ++ .../data/planet_algorithms/algo13.rs | 81 + .../data/planet_algorithms/algo14.rs | 169 ++ .../algorithm/data/planet_algorithms/algo2.rs | 72 + .../algorithm/data/planet_algorithms/algo3.rs | 140 ++ .../algorithm/data/planet_algorithms/algo4.rs | 113 ++ .../algorithm/data/planet_algorithms/algo5.rs | 120 ++ .../algorithm/data/planet_algorithms/algo6.rs | 137 ++ .../algorithm/data/planet_algorithms/algo7.rs | 85 + .../algorithm/data/planet_algorithms/algo8.rs | 61 + .../algorithm/data/planet_algorithms/algo9.rs | 135 ++ .../algorithm/data/planet_algorithms/mod.rs | 71 + inserter/src/algorithm/data/planet_grid.rs | 166 ++ .../src/algorithm/data/planet_raw_data.rs | 75 + inserter/src/algorithm/data/pose.rs | 16 + inserter/src/algorithm/data/quaternion.rs | 209 +++ inserter/src/algorithm/data/random.rs | 60 +- inserter/src/algorithm/data/random_table.rs | 63 + inserter/src/algorithm/data/rule.rs | 141 ++ inserter/src/algorithm/data/simplex_noise.rs | 253 +++ inserter/src/algorithm/data/star.rs | 700 ++++++--- inserter/src/algorithm/data/star_planets.rs | 285 ++-- inserter/src/algorithm/data/theme_proto.rs | 105 +- inserter/src/algorithm/data/vector2.rs | 4 + inserter/src/algorithm/data/vector3.rs | 12 +- inserter/src/algorithm/data/vector_f2.rs | 204 +++ inserter/src/algorithm/data/vector_f3.rs | 235 +++ inserter/src/algorithm/data/vein.rs | 22 +- inserter/src/algorithm/mod.rs | 2 +- inserter/src/algorithm/tests/mod.rs | 1 + inserter/src/algorithm/tests/worldgen_test.rs | 28 + inserter/src/algorithm/worldgen/galaxy_gen.rs | 242 +-- inserter/src/generate_csv.rs | 20 +- inserter/src/threads.rs | 2 +- inserter/src/wasm.rs | 79 + 46 files changed, 5389 insertions(+), 983 deletions(-) create mode 100644 inserter/src/algorithm/data/birth_points.rs delete mode 100644 inserter/src/algorithm/data/macros.rs create mode 100644 inserter/src/algorithm/data/math.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo0.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo1.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo10.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo11.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo12.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo13.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo14.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo2.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo3.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo4.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo5.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo6.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo7.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo8.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo9.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/mod.rs create mode 100644 inserter/src/algorithm/data/planet_grid.rs create mode 100644 inserter/src/algorithm/data/planet_raw_data.rs create mode 100644 inserter/src/algorithm/data/pose.rs create mode 100644 inserter/src/algorithm/data/quaternion.rs create mode 100644 inserter/src/algorithm/data/random_table.rs create mode 100644 inserter/src/algorithm/data/rule.rs create mode 100644 inserter/src/algorithm/data/simplex_noise.rs create mode 100644 inserter/src/algorithm/data/vector2.rs create mode 100644 inserter/src/algorithm/data/vector_f2.rs create mode 100644 inserter/src/algorithm/data/vector_f3.rs create mode 100644 inserter/src/algorithm/tests/mod.rs create mode 100644 inserter/src/algorithm/tests/worldgen_test.rs create mode 100644 inserter/src/wasm.rs diff --git a/inserter/src/algorithm/data/birth_points.rs b/inserter/src/algorithm/data/birth_points.rs new file mode 100644 index 0000000..5b2e9d7 --- /dev/null +++ b/inserter/src/algorithm/data/birth_points.rs @@ -0,0 +1,139 @@ +use super::random::DspRandom; +use super::vector_f3::VectorF3; +use crate::algorithm::data::planet_raw_data::PlanetRawData; + +/// Return value for gen_birth_points, containing the three birth positions. +#[derive(Debug, Clone)] +pub struct BirthPoints { + /// Main spawn point on the planet surface + pub birth_point: VectorF3, + /// First resource point + pub birth_resource_point0: VectorF3, + /// Second resource point + pub birth_resource_point1: VectorF3, +} + +impl BirthPoints { + pub fn new( + raw_data: &mut PlanetRawData, + birth_seed: i32, + radius: f32, + star_direction: VectorF3, + ) -> Self { + // ---- main GenBirthPoints(PlanetRawData, int) body (C# lines 761-821) -- + let mut rand = DspRandom::new(birth_seed); + + // vector3_1 is the incoming star_direction (already normalised) + let star_dir = star_direction.normalized(); + + // C#: Vector3 normalized1 = Vector3.Cross(vector3_1, Vector3.up).normalized + let mut basis1 = VectorF3::cross(&star_dir, &VectorF3::up()).normalized(); + // C#: Vector3 normalized2 = Vector3.Cross(normalized1, vector3_1).normalized + let mut basis2 = VectorF3::cross(&basis1, &star_dir).normalized(); + + let num2 = 256; + + // Outer loop – try up to 256 candidate birth directions + for _ in 0..num2 { + // C#: float num3 = (float)(dotNet35Random.NextDouble() * 2.0 - 1.0) * 0.5f + let num3 = (rand.next_f64() * 2.0 - 1.0) as f32 * 0.5; + // C#: float num4 = (float)(dotNet35Random.NextDouble() * 2.0 - 1.0) * 0.5f + let num4 = (rand.next_f64() * 2.0 - 1.0) as f32 * 0.5; + + // C#: Vector3 vector3_2 = vector3_1 + num3 * normalized1 + num4 * normalized2 + let mut candidate = star_dir + basis1 * num3 + basis2 * num4; + candidate.normalize(); + + // C#: this.birthPoint = vector3_2 * (realRadius + 0.2f + 1.45f); + // 0.2f + 1.45f = 1.65f, but keep the original constants for clarity + let birth_point = candidate * (radius + 0.2 + 1.45); + + // C#: Vector3 vector3_3 = Vector3.Cross(vector3_2, Vector3.up); + // normalized1 = vector3_3.normalized; + let cross_tmp = VectorF3::cross(&candidate, &VectorF3::up()); + basis1 = cross_tmp.normalized(); + // C#: vector3_3 = Vector3.Cross(normalized1, vector3_2); + // normalized2 = vector3_3.normalized; + let cross_tmp2 = VectorF3::cross(&basis1, &candidate); + basis2 = cross_tmp2.normalized(); + + // Inner loop – try up to 10 resource-point offsets + for _ in 0..10 { + // C#: Vector2(x, y).normalized * 0.1f + let v2x = (rand.next_f64() * 2.0 - 1.0) as f32; + let v2y = (rand.next_f64() * 2.0 - 1.0) as f32; + let v2len = (v2x * v2x + v2y * v2y).sqrt(); + let (v2x, v2y) = if v2len > 1e-10 { + (v2x / v2len * 0.1, v2y / v2len * 0.1) + } else { + (0.0, 0.0) + }; + + // C#: Vector2 vector2_2 = -vector2_1; + let v2x2 = -v2x; + let v2y2 = -v2y; + + // C#: num5 = (float)(dotNet35Random.NextDouble() * 2.0 - 1.0) * 0.06f + let num5 = (rand.next_f64() * 2.0 - 1.0) as f32 * 0.06; + // C#: num6 = (float)(dotNet35Random.NextDouble() * 2.0 - 1.0) * 0.06f + let num6 = (rand.next_f64() * 2.0 - 1.0) as f32 * 0.06; + + let v2x2 = v2x2 + num5; + let v2y2 = v2y2 + num6; + + // C#: normalized3 = (vector3_2 + vector2_1.x * normalized1 + vector2_1.y * normalized2).normalized + let rp0_dir = (candidate + basis1 * v2x + basis2 * v2y).normalized(); + + // C#: normalized4 = (vector3_2 + vector2_2.x * normalized1 + vector2_2.y * normalized2).normalized + let rp1_dir = (candidate + basis1 * v2x2 + basis2 * v2y2).normalized(); + + // height threshold + let height_threshold = radius + 0.2; + + // Use normalized variant since candidate, rp0_dir, rp1_dir are already unit-length + if raw_data.query_height_normalized(&candidate) > height_threshold + && raw_data.query_height_normalized(&rp0_dir) > height_threshold + && raw_data.query_height_normalized(&rp1_dir) > height_threshold + { + // C#: check 8 surrounding offsets + let vpos1 = rp0_dir + basis1 * 0.03; + let vpos2 = rp0_dir - basis1 * 0.03; + let vpos3 = rp0_dir + basis2 * 0.03; + let vpos4 = rp0_dir - basis2 * 0.03; + let vpos5 = rp1_dir + basis1 * 0.03; + let vpos6 = rp1_dir - basis1 * 0.03; + let vpos7 = rp1_dir + basis2 * 0.03; + let vpos8 = rp1_dir - basis2 * 0.03; + + // Offset vectors are not unit-length; use the normalising variant + if raw_data.query_height(&vpos1) > height_threshold + && raw_data.query_height(&vpos2) > height_threshold + && raw_data.query_height(&vpos3) > height_threshold + && raw_data.query_height(&vpos4) > height_threshold + && raw_data.query_height(&vpos5) > height_threshold + && raw_data.query_height(&vpos6) > height_threshold + && raw_data.query_height(&vpos7) > height_threshold + && raw_data.query_height(&vpos8) > height_threshold + { + // Re‑normalise both resource-point directions + let rp0 = rp0_dir.normalized(); + let rp1 = rp1_dir.normalized(); + + return Self { + birth_point, + birth_resource_point0: rp0, + birth_resource_point1: rp1, + }; + } + } + } + } + + // ---- fallback (C# line 820) ------------------------------------------- + Self { + birth_point: VectorF3(0.0, radius + 5.0, 0.0), + birth_resource_point0: VectorF3::up(), + birth_resource_point1: VectorF3::down(), + } + } +} diff --git a/inserter/src/algorithm/data/enums.rs b/inserter/src/algorithm/data/enums.rs index 8982942..67e0095 100644 --- a/inserter/src/algorithm/data/enums.rs +++ b/inserter/src/algorithm/data/enums.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; +#[allow(dead_code)] #[repr(i32)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)] pub enum StarType { @@ -15,6 +16,7 @@ impl Default for StarType { Self::MainSeqStar } } + #[allow(dead_code)] #[repr(i32)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)] @@ -34,7 +36,7 @@ pub enum SpectrType { #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)] pub enum PlanetType { None, - Vocano, + Volcano, Ocean, Desert, Ice, @@ -93,18 +95,18 @@ impl Default for VeinType { impl VeinType { pub fn is_rare(&self) -> bool { - match self { + matches!( + self, VeinType::Fireice - | VeinType::Diamond - | VeinType::Fractal - | VeinType::Crysrub - | VeinType::Grat - | VeinType::Bamboo => true, - _ => false, - } + | VeinType::Diamond + | VeinType::Fractal + | VeinType::Crysrub + | VeinType::Grat + | VeinType::Bamboo + | VeinType::Mag + ) } } - pub const ORES: [VeinType; 16] = [ VeinType::None, VeinType::Iron, @@ -122,4 +124,4 @@ pub const ORES: [VeinType; 16] = [ VeinType::Bamboo, VeinType::Mag, VeinType::Max, -]; +]; \ No newline at end of file diff --git a/inserter/src/algorithm/data/game_desc.rs b/inserter/src/algorithm/data/game_desc.rs index b259ac9..c075c98 100644 --- a/inserter/src/algorithm/data/game_desc.rs +++ b/inserter/src/algorithm/data/game_desc.rs @@ -1,18 +1,18 @@ -use std::cell::Cell; - use serde::{Deserialize, Serialize}; -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct GameDesc { - #[serde(default)] - pub seed: i32, #[serde(default = "GameDesc::default_star_count")] pub star_count: usize, #[serde(default = "GameDesc::default_resource_multiplier")] pub resource_multiplier: f32, - #[serde(skip)] - pub habitable_count: Cell, + #[serde(default = "GameDesc::default_hive_initial_colonize")] + pub hive_initial_colonize: f64, + #[serde(default = "GameDesc::default_hive_max_density")] + pub hive_max_density: f64, + #[serde(default)] + pub use_actual_veins: bool, } impl GameDesc { @@ -22,6 +22,12 @@ impl GameDesc { pub fn default_resource_multiplier() -> f32 { 1.0 } + pub fn default_hive_initial_colonize() -> f64 { + 1.0 + } + pub fn default_hive_max_density() -> f64 { + 1.0 + } pub fn is_infinite_resource(&self) -> bool { self.resource_multiplier >= 99.5 @@ -31,7 +37,7 @@ impl GameDesc { self.resource_multiplier <= 0.1001 } - pub fn oil_amount_multipler(&self) -> f32 { + pub fn oil_amount_multiplier(&self) -> f32 { if self.is_rare_resource() { 0.5 } else { @@ -47,3 +53,14 @@ impl GameDesc { } } } +impl Default for GameDesc { + fn default() -> Self { + Self { + star_count: 64, + resource_multiplier: 1.0, + hive_initial_colonize: 1.0, + hive_max_density: 1.0, + use_actual_veins: false + } + } +} \ No newline at end of file diff --git a/inserter/src/algorithm/data/macros.rs b/inserter/src/algorithm/data/macros.rs deleted file mode 100644 index eec6484..0000000 --- a/inserter/src/algorithm/data/macros.rs +++ /dev/null @@ -1,38 +0,0 @@ -#[macro_use] -pub mod macros { - macro_rules! lazy_getter { - ($self:ident, $name:ident, $t:ty, $b:block) => { - pub fn $name(&$self) -> $t { - if let Some(val) = unsafe { *$self.$name.get() } { - val - } else { - let val = (|| $b)(); - unsafe { - let r = $self.$name.get(); - *r = Some(val); - (&*r).unwrap_unchecked() - } - } - } - }; - } - pub(crate) use lazy_getter; - - macro_rules! lazy_getter_ref { - ($self:ident, $name:ident, $t:ty, $b:block) => { - pub fn $name(&$self) -> &$t { - if let Some(val) = unsafe { &*$self.$name.get() }.as_ref() { - val - } else { - let val = (|| $b)(); - unsafe { - let r = $self.$name.get(); - *r = Some(val); - (&*r).as_ref().unwrap_unchecked() - } - } - } - }; - } - pub(crate) use lazy_getter_ref; -} diff --git a/inserter/src/algorithm/data/math.rs b/inserter/src/algorithm/data/math.rs new file mode 100644 index 0000000..825d8e1 --- /dev/null +++ b/inserter/src/algorithm/data/math.rs @@ -0,0 +1,77 @@ +/// Mathematical utility functions ported from Maths.cs +/// These are used by PlanetAlgorithm implementations. + +/// Clamp a value between min and max (inclusive). +#[inline] +pub fn clamp(value: T, min: T, max: T) -> T { + if value < min { + min + } else if value > max { + max + } else { + value + } +} + +/// Clamp a value between 0.0 and 1.0. +#[inline] +pub fn clamp01(value: f64) -> f64 { + clamp(value, 0.0, 1.0) +} + +/// C# Levelize: smoothstep-based levelize +/// f = f / level - offset; +/// num1 = floor(f); +/// num2 = f - num1; +/// num3 = (3.0 - num2 - num2) * num2 * num2; (smoothstep) +/// f = num1 + num3; +/// f = (f + offset) * level; +#[inline] +pub fn levelize(f: f64, level: f64, offset: f64) -> f64 { + let f = f / level - offset; + let num1 = f.floor(); + let num2 = f - num1; + let num3 = (3.0 - num2 - num2) * num2 * num2; + let f = num1 + num3; + (f + offset) * level +} + +/// C# Levelize2: smoothstep applied twice (smoother steps) +#[inline] +pub fn levelize2(f: f64, level: f64, offset: f64) -> f64 { + let f = f / level - offset; + let num1 = f.floor(); + let num2 = f - num1; + let num3 = (3.0 - num2 - num2) * num2 * num2; + let num4 = (3.0 - num3 - num3) * num3 * num3; + let f = num1 + num4; + (f + offset) * level +} + +/// C# Levelize3: smoothstep applied three times (even smoother steps) +#[inline] +pub fn levelize3(f: f64, level: f64, offset: f64) -> f64 { + let f = f / level - offset; + let num1 = f.floor(); + let num2 = f - num1; + let num3 = (3.0 - num2 - num2) * num2 * num2; + let num4 = (3.0 - num3 - num3) * num3 * num3; + let num5 = (3.0 - num4 - num4) * num4 * num4; + let f = num1 + num5; + (f + offset) * level +} + +/// C# Levelize4: smoothstep applied four times (even smoother steps) +/// Uses floor() for correct negative-value handling. +#[inline] +pub fn levelize4(f: f64, level: f64, offset: f64) -> f64 { + let f = f / level - offset; + let num1 = f.floor(); + let num2 = f - num1; + let num3 = (3.0 - num2 - num2) * num2 * num2; + let num4 = (3.0 - num3 - num3) * num3 * num3; + let num5 = (3.0 - num4 - num4) * num4 * num4; + let num6 = (3.0 - num5 - num5) * num5 * num5; + let f = num1 + num6; + (f + offset) * level +} diff --git a/inserter/src/algorithm/data/mod.rs b/inserter/src/algorithm/data/mod.rs index 89b49dc..1665ad4 100644 --- a/inserter/src/algorithm/data/mod.rs +++ b/inserter/src/algorithm/data/mod.rs @@ -1,11 +1,23 @@ +pub mod birth_points; pub mod enums; pub mod galaxy; pub mod game_desc; -pub mod macros; +pub mod math; pub mod planet; +pub mod planet_algorithms; +pub mod planet_grid; +pub mod planet_raw_data; +pub mod pose; +pub mod quaternion; pub mod random; +pub mod random_table; +pub mod rule; +pub mod simplex_noise; pub mod star; pub mod star_planets; pub mod theme_proto; +pub mod vector2; pub mod vector3; +pub mod vector_f2; +pub mod vector_f3; pub mod vein; diff --git a/inserter/src/algorithm/data/planet.rs b/inserter/src/algorithm/data/planet.rs index 76b51ed..e00bec3 100644 --- a/inserter/src/algorithm/data/planet.rs +++ b/inserter/src/algorithm/data/planet.rs @@ -1,21 +1,28 @@ +use crate::algorithm::data::birth_points::BirthPoints; +use crate::algorithm::data::game_desc::GameDesc; +use crate::algorithm::data::planet_raw_data::PlanetRawData; +use crate::algorithm::data::vector_f2::VectorF2; + use super::enums::{PlanetType, SpectrType, StarType, ThemeDistribute, VeinType}; -use super::macros::macros::{lazy_getter, lazy_getter_ref}; +use super::pose::Pose; +use super::quaternion::Quaternion; use super::random::DspRandom; use super::star::Star; use super::theme_proto::{ThemeProto, THEME_PROTOS}; -use super::vein::Vein; +use super::vector_f3::VectorF3; +use super::vein::{ActualVein, EstimatedVein}; use serde::ser::{Serialize, SerializeStruct, Serializer}; -use std::cell::{RefCell, UnsafeCell}; +use std::cell::{Cell, OnceCell, RefCell}; use std::f64::consts::PI; use std::rc::Rc; -#[allow(dead_code)] #[derive(Debug)] pub struct Planet<'a> { + game_desc: &'a GameDesc, pub star: Rc>, pub index: usize, + habitable_count: &'a Cell, pub seed: i32, - pub info_seed: i32, pub theme_seed: i32, pub orbit_around: RefCell>>, pub orbit_index: usize, @@ -33,22 +40,21 @@ pub struct Planet<'a> { pub orbit_phase: f32, pub rotation_phase: f32, theme_rand1: f64, - get_orbital_radius: UnsafeCell>, - get_sun_distance: UnsafeCell>, - get_temperature_factor: UnsafeCell>, - get_habitable_bias: UnsafeCell>, - get_temperature_bias: UnsafeCell>, - get_luminosity: UnsafeCell>, - get_unmodified_planet_type: UnsafeCell>, - get_orbit_inclination: UnsafeCell>, - get_sun_orbital_period: UnsafeCell>, - get_orbital_period: UnsafeCell>, - get_obliquity: UnsafeCell>, - get_eligible_for_resonance: UnsafeCell>, - get_rotation_period: UnsafeCell>, - get_theme: UnsafeCell>, - get_gases: UnsafeCell>>, - get_veins: UnsafeCell>>, + theme_rand2: f64, + theme_rand3: f64, + theme_rand4: f64, + orbital_radius: OnceCell, + sun_distance: OnceCell, + temperature_factor: OnceCell, + orbital_period: OnceCell, + obliquity: OnceCell, + eligible_for_resonance: OnceCell, + rotation_period: OnceCell, + theme: OnceCell<&'static ThemeProto>, + gases: OnceCell>, + estimated_veins: OnceCell>, + actual_veins: OnceCell>, + theme_algo_id: OnceCell, } const ORBIT_RADIUS: &'static [f32] = &[ @@ -57,8 +63,10 @@ const ORBIT_RADIUS: &'static [f32] = &[ impl<'a> Planet<'a> { pub fn new( + game_desc: &'a GameDesc, star: Rc>, index: usize, + habitable_count: &'a Cell, orbit_index: usize, gas_giant: bool, info_seed: i32, @@ -66,26 +74,26 @@ impl<'a> Planet<'a> { ) -> Self { let mut rand = DspRandom::new(info_seed); - let num3 = rand.next_f64(); - let num4 = rand.next_f64(); - let orbit_radius_factor = num3 * (num4 - 0.5) * 0.5; + let orbit_radius_rand1 = rand.next_f64(); + let orbit_radius_rand2 = rand.next_f64(); + let orbit_radius_factor = orbit_radius_rand1 * (orbit_radius_rand2 - 0.5) * 0.5; let orbit_inclination_factor = rand.next_f64(); let orbit_longitude = (rand.next_f64() * 360.0) as f32; let orbit_phase = (rand.next_f64() * 360.0) as f32; - let num8 = rand.next_f64(); - let num9 = rand.next_f64(); - let obliquity_scale = num8 * (num9 - 0.5); - let num10 = rand.next_f64(); - let num11 = rand.next_f64(); - let rotation_scale = num10 * num11 * 1000.0 + 400.0; + let obliquity_rand1 = rand.next_f64(); + let obliquity_rand2 = rand.next_f64(); + let obliquity_scale = obliquity_rand1 * (obliquity_rand2 - 0.5); + let rotation_rand1 = rand.next_f64(); + let rotation_rand2 = rand.next_f64(); + let rotation_scale = rotation_rand1 * rotation_rand2 * 1000.0 + 400.0; let rotation_phase = (rand.next_f64() * 360.0) as f32; let habitable_factor = rand.next_f64(); let type_factor = rand.next_f64(); let theme_rand1 = rand.next_f64(); let rotation_param = rand.next_f64(); - rand.next_f64(); - rand.next_f64(); - rand.next_f64(); + let theme_rand2 = rand.next_f64(); + let theme_rand3 = rand.next_f64(); + let theme_rand4 = rand.next_f64(); let theme_seed = rand.next_seed(); let (radius, scale) = if gas_giant { @@ -95,10 +103,11 @@ impl<'a> Planet<'a> { }; Self { + game_desc, star, index, + habitable_count, seed: gen_seed, - info_seed, theme_seed, orbit_around: RefCell::new(None), orbit_index, @@ -108,6 +117,9 @@ impl<'a> Planet<'a> { orbit_phase, rotation_phase, theme_rand1, + theme_rand2, + theme_rand3, + theme_rand4, obliquity_scale, rotation_param, rotation_scale, @@ -116,22 +128,18 @@ impl<'a> Planet<'a> { habitable_factor, type_factor, gas_giant, - get_orbital_radius: UnsafeCell::new(None), - get_sun_distance: UnsafeCell::new(None), - get_temperature_factor: UnsafeCell::new(None), - get_habitable_bias: UnsafeCell::new(None), - get_temperature_bias: UnsafeCell::new(None), - get_luminosity: UnsafeCell::new(None), - get_unmodified_planet_type: UnsafeCell::new(None), - get_orbit_inclination: UnsafeCell::new(None), - get_sun_orbital_period: UnsafeCell::new(None), - get_orbital_period: UnsafeCell::new(None), - get_obliquity: UnsafeCell::new(None), - get_eligible_for_resonance: UnsafeCell::new(None), - get_rotation_period: UnsafeCell::new(None), - get_theme: UnsafeCell::new(None), - get_gases: UnsafeCell::new(None), - get_veins: UnsafeCell::new(None), + orbital_radius: OnceCell::new(), + sun_distance: OnceCell::new(), + temperature_factor: OnceCell::new(), + orbital_period: OnceCell::new(), + obliquity: OnceCell::new(), + eligible_for_resonance: OnceCell::new(), + rotation_period: OnceCell::new(), + theme: OnceCell::new(), + gases: OnceCell::new(), + estimated_veins: OnceCell::new(), + theme_algo_id: OnceCell::new(), + actual_veins: OnceCell::new(), } } @@ -151,67 +159,73 @@ impl<'a> Planet<'a> { self.orbit_around.borrow().is_some() } - lazy_getter!(self, get_orbital_radius, f32, { - let a = 1.2_f32.powf(self.orbit_radius_factor as f32); - if let Some(orbit_planet) = self.orbit_around.borrow().as_deref() { - (((1600.0 * (self.orbit_index as f64) + 200.0) - * (self.star.get_orbit_scaler().powf(0.3) as f64) - * ((a + (1.0 - a) * 0.5) as f64) - + (orbit_planet.real_radius() as f64)) - / 40000.0) as f32 - } else { - let b = ORBIT_RADIUS[self.orbit_index as usize] * self.star.get_orbit_scaler(); - let num16 = (((a - 1.0) as f64) / (b.max(1.0) as f64) + 1.0) as f32; - b * num16 - } - }); + pub fn get_orbital_radius(&self) -> f32 { + *self.orbital_radius.get_or_init(|| { + let a = 1.2_f32.powf(self.orbit_radius_factor as f32); + if let Some(orbit_planet) = self.orbit_around.borrow().as_deref() { + (((1600.0 * (self.orbit_index as f64) + 200.0) + * (self.star.get_orbit_scaler().powf(0.3) as f64) + * ((a + (1.0 - a) * 0.5) as f64) + + (orbit_planet.real_radius() as f64)) + / 40000.0) as f32 + } else { + let b = ORBIT_RADIUS[self.orbit_index] * self.star.get_orbit_scaler(); + let adjusted_orbit_radius = (((a - 1.0) as f64) / (b.max(1.0) as f64) + 1.0) as f32; + b * adjusted_orbit_radius + } + }) + } - lazy_getter!(self, get_sun_distance, f32, { - if let Some(orbit_planet) = self.orbit_around.borrow().as_deref() { - orbit_planet.get_orbital_radius() - } else { - self.get_orbital_radius() - } - }); + pub fn get_sun_distance(&self) -> f32 { + *self.sun_distance.get_or_init(|| { + if let Some(orbit_planet) = self.orbit_around.borrow().as_deref() { + orbit_planet.get_orbital_radius() + } else { + self.get_orbital_radius() + } + }) + } - lazy_getter!(self, get_temperature_factor, f32, { - if self.is_gas_giant() { - 0.0 - } else { - let habitable_radius = self.star.get_habitable_radius(); - if habitable_radius > 0.0 { - self.get_sun_distance() / habitable_radius + pub fn get_temperature_factor(&self) -> f32 { + *self.temperature_factor.get_or_init(|| { + if self.is_gas_giant() { + 0.0 } else { - 1000.0 + let habitable_radius = self.star.get_habitable_radius(); + if habitable_radius > 0.0 { + self.get_sun_distance() / habitable_radius + } else { + 1000.0 + } } - } - }); + }) + } - lazy_getter!(self, get_habitable_bias, f32, { + fn get_habitable_bias(&self) -> f32 { if self.is_gas_giant() { 1000.0 } else { let habitable_radius = self.star.get_habitable_radius(); - let num21 = if habitable_radius > 0.0 { + let distance_log_factor = if habitable_radius > 0.0 { (self.get_sun_distance() / habitable_radius).ln().abs() } else { 1000.0 }; - let num22 = habitable_radius.sqrt().clamp(1.0, 2.0) - 0.04; - num21 * num22 + let habitable_radius_sqrt_clamped = habitable_radius.sqrt().clamp(1.0, 2.0) - 0.04; + distance_log_factor * habitable_radius_sqrt_clamped } - }); + } - lazy_getter!(self, get_temperature_bias, f32, { + fn get_temperature_bias(&self) -> f32 { if self.is_gas_giant() { 0.0 } else { - let f2 = self.get_temperature_factor(); - (1.2 / ((f2 as f64) + 0.2) - 1.0) as f32 + let temperature_factor_val = self.get_temperature_factor(); + (1.2 / ((temperature_factor_val as f64) + 0.2) - 1.0) as f32 } - }); + } - lazy_getter!(self, get_luminosity, f32, { + fn get_luminosity(&self) -> f32 { let mut luminosity = (self.star.get_light_balance_radius() / (self.get_sun_distance() + 0.01)).powf(0.6); if luminosity > 1.0 { @@ -219,18 +233,14 @@ impl<'a> Planet<'a> { luminosity = luminosity.ln() + 1.0; luminosity = luminosity.ln() + 1.0; } - (luminosity * 100.0).round() / 100.0 - }); + (luminosity * 100.0).round_ties_even() / 100.0 + } fn increment_habitable_count(&self) { - self.star - .game_desc - .habitable_count - .set(self.star.game_desc.habitable_count.get() + 1); + self.habitable_count.set(self.habitable_count.get() + 1); } - lazy_getter_ref!(self, get_unmodified_planet_type, PlanetType, { - // can only be called once and in order + fn get_unmodified_planet_type(&self) -> PlanetType { if self.is_gas_giant() { PlanetType::Gas } else if self.is_birth() { @@ -239,48 +249,48 @@ impl<'a> Planet<'a> { } else { let f2 = self.get_temperature_factor(); if !self.star.is_birth() { - let star_count = self.star.game_desc.star_count; - let num18 = ((star_count as f32) * 0.29).ceil().max(11.0); - let num19 = (num18 as f64) - (self.star.game_desc.habitable_count.get() as f64); - let num20 = (star_count - self.star.index) as f32; - let num23 = num20 as f64; - let a = (num19 / num23) as f32; - let num24 = (a + (0.35 - a) * 0.5).clamp(0.08, 0.8); - let num25 = (self.get_habitable_bias() / num24) + let star_count = self.game_desc.star_count; + let habitable_ceiling = ((star_count as f32) * 0.29).ceil().max(11.0); + let remaining_habitable_slots = + (habitable_ceiling as f64) - (self.habitable_count.get() as f64); + let remaining_stars = (star_count - self.star.index) as f32; + let remaining_stars_f64 = remaining_stars as f64; + let remaining_ratio = (remaining_habitable_slots / remaining_stars_f64) as f32; + let allocation_probability = + (remaining_ratio + (0.35 - remaining_ratio) * 0.5).clamp(0.08, 0.8); + let habitable_bias_threshold = (self.get_habitable_bias() / allocation_probability) .clamp(0.0, 1.1) - .powf(num24 * 10.0); - if self.habitable_factor > (num25 as f64) { + .powf(allocation_probability * 10.0); + if self.habitable_factor > (habitable_bias_threshold as f64) { self.increment_habitable_count(); return PlanetType::Ocean; } } - if f2 < 5.0 / 6.0 { - let num26 = ((f2 as f64) * 2.5 - 0.85).max(0.15); - if self.type_factor >= num26 { - PlanetType::Vocano + let volcano_type_threshold = ((f2 as f64) * 2.5 - 0.85).max(0.15); + if self.type_factor >= volcano_type_threshold { + PlanetType::Volcano } else { PlanetType::Desert } } else if f2 < 1.2 { PlanetType::Desert } else { - let num27 = 0.9 / (f2 as f64) - 0.1; - if self.type_factor >= num27 { + let ice_type_threshold = 0.9 / (f2 as f64) - 0.1; + if self.type_factor >= ice_type_threshold { PlanetType::Ice } else { PlanetType::Desert } } } - }); + } - #[allow(dead_code)] pub fn is_tidal_locked(&self) -> bool { self.get_rotation_period() == self.get_orbital_period() } - lazy_getter!(self, get_orbit_inclination, f32, { + fn get_orbit_inclination(&self) -> f32 { let mut orbit_inclination = (self.orbit_inclination_factor * 16.0 - 8.0) as f32; if self.has_orbit_around() { orbit_inclination *= 2.2; @@ -293,357 +303,925 @@ impl<'a> Planet<'a> { } } orbit_inclination - }); + } - lazy_getter!(self, get_sun_orbital_period, f64, { + fn get_sun_orbital_period(&self) -> f64 { if let Some(orbit_planet) = self.orbit_around.borrow().as_deref() { orbit_planet.get_orbital_period() } else { self.get_orbital_period() } - }); - - lazy_getter!(self, get_orbital_period, f64, { - const FOUR_PI_SQUARE: f64 = 4.0 * PI * PI; - let f1 = self.get_orbital_radius() as f64; - (FOUR_PI_SQUARE * f1 * f1 * f1 - / (if self.has_orbit_around() { - 1.08308421068537e-08 // cannot figure out what this is, probably related to Kepler's third law - } else { - 1.35385519905204e-06 * (self.star.get_mass() as f64) - })) - .sqrt() - }); - - lazy_getter!(self, get_obliquity, f32, { - let mut obliquity: f32; - if self.rotation_param < 0.04 { - obliquity = (self.obliquity_scale * 39.9) as f32; - if obliquity < 0.0 { - obliquity -= 70.0; - } else { - obliquity += 70.0; - } - } else if self.rotation_param < 0.1 { - obliquity = (self.obliquity_scale * 80.0) as f32; - if obliquity < 0.0 { - obliquity -= 30.0; + } + + pub fn get_orbital_period(&self) -> f64 { + *self.orbital_period.get_or_init(|| { + const FOUR_PI_SQUARE: f64 = 4.0 * PI * PI; + let orbital_radius_f64 = self.get_orbital_radius() as f64; + (FOUR_PI_SQUARE * orbital_radius_f64 * orbital_radius_f64 * orbital_radius_f64 + / (if self.has_orbit_around() { + 1.08308421068537e-08 + } else { + 1.35385519905204e-06 * (self.star.get_mass() as f64) + })) + .sqrt() + }) + } + + pub fn get_obliquity(&self) -> f32 { + *self.obliquity.get_or_init(|| { + let mut obliquity: f32; + if self.rotation_param < 0.04 { + obliquity = (self.obliquity_scale * 39.9) as f32; + if obliquity < 0.0 { + obliquity -= 70.0; + } else { + obliquity += 70.0; + } + } else if self.rotation_param < 0.1 { + obliquity = (self.obliquity_scale * 80.0) as f32; + if obliquity < 0.0 { + obliquity -= 30.0; + } else { + obliquity += 30.0; + } } else { - obliquity += 30.0; + obliquity = (self.obliquity_scale * 60.0) as f32; + if self.get_eligible_for_resonance() { + if self.rotation_param > 0.96 { + obliquity *= 0.01; + } else if self.rotation_param > 0.93 { + obliquity *= 0.1; + } else if self.rotation_param > 0.9 { + obliquity *= 0.2; + } + } } - } else { - obliquity = (self.obliquity_scale * 60.0) as f32; + obliquity + }) + } + + pub fn get_eligible_for_resonance(&self) -> bool { + *self.eligible_for_resonance.get_or_init(|| { + let gas_giant = self.is_gas_giant(); + !self.has_orbit_around() && self.orbit_index <= 4 && !gas_giant + }) + } + + pub fn get_rotation_period(&self) -> f64 { + *self.rotation_period.get_or_init(|| { if self.get_eligible_for_resonance() { if self.rotation_param > 0.96 { - obliquity *= 0.01; + return self.get_orbital_period(); } else if self.rotation_param > 0.93 { - obliquity *= 0.1; + return self.get_orbital_period() * 0.5; } else if self.rotation_param > 0.9 { - obliquity *= 0.2; + return self.get_orbital_period() * 0.25; } } - } - - obliquity - }); - - lazy_getter!(self, get_eligible_for_resonance, bool, { - let gas_giant = self.is_gas_giant(); - !self.has_orbit_around() && self.orbit_index <= 4 && !gas_giant - }); - - lazy_getter!(self, get_rotation_period, f64, { - if self.get_eligible_for_resonance() { - if self.rotation_param > 0.96 { - return self.get_orbital_period(); - } else if self.rotation_param > 0.93 { - return self.get_orbital_period() * 0.5; - } else if self.rotation_param > 0.9 { - return self.get_orbital_period() * 0.25; + let gas_giant = self.is_gas_giant(); + let mut rotation_period = self.rotation_scale + * (if gas_giant { + 0.2 + } else { + match self.star.star_type { + StarType::WhiteDwarf => 0.5, + StarType::NeutronStar => 0.2, + StarType::BlackHole => 0.15, + _ => 1.0, + } + }) + * (if self.has_orbit_around() { + 1.0 + } else { + self.get_orbital_radius().powf(0.25) as f64 + }); + rotation_period = 1.0 / (1.0 / self.get_sun_orbital_period() + 1.0 / rotation_period); + if self.rotation_param > 0.85 && self.rotation_param <= 0.9 { + rotation_period = -rotation_period; } - } - - let gas_giant = self.is_gas_giant(); - let mut rotation_period = self.rotation_scale - * (if gas_giant { - 0.2 - } else { - match self.star.star_type { - StarType::WhiteDwarf => 0.5, - StarType::NeutronStar => 0.2, - StarType::BlackHole => 0.15, - _ => 1.0, - } - }) - * (if self.has_orbit_around() { - 1.0 - } else { - self.get_orbital_radius().powf(0.25) as f64 - }); - - rotation_period = 1.0 / (1.0 / self.get_sun_orbital_period() + 1.0 / rotation_period); + rotation_period + }) + } - if self.rotation_param > 0.85 && self.rotation_param <= 0.9 { - rotation_period = -rotation_period; - } - rotation_period - }); - - lazy_getter!(self, get_theme, &'static ThemeProto, { - // can only be called once and in order - let mut potential_themes: Vec<&'static ThemeProto> = vec![]; - let mut used_theme_ids = self.star.used_theme_ids.borrow_mut(); - let unused_themes: Vec<&'static ThemeProto> = THEME_PROTOS - .iter() - .filter(|&theme| !used_theme_ids.contains(&theme.id)) - .collect(); - - let planet_type = self.get_unmodified_planet_type(); - let temperature_bias = self.get_temperature_bias(); - - for theme in &unused_themes { - if self.star.is_birth() && planet_type == &PlanetType::Ocean { - if theme.distribute == ThemeDistribute::Birth { - potential_themes.push(theme); - } - } else { - let flag2 = - if theme.temperature.abs() < 0.5 && theme.planet_type == PlanetType::Desert { + pub fn get_theme(&self) -> &'static ThemeProto { + self.theme.get_or_init(|| { + let mut potential_themes: Vec<&'static ThemeProto> = Vec::new(); + let mut used_theme_ids = self.star.used_theme_ids.borrow_mut(); + let unused_themes: Vec<&'static ThemeProto> = THEME_PROTOS + .iter() + .filter(|&theme| !used_theme_ids.contains(&theme.id)) + .collect(); + let planet_type = self.get_unmodified_planet_type(); + let temperature_bias = self.get_temperature_bias(); + for theme in &unused_themes { + if self.star.is_birth() && planet_type == PlanetType::Ocean { + if theme.distribute == ThemeDistribute::Birth { + potential_themes.push(theme); + } + } else { + let temperature_matches = if theme.temperature.abs() < 0.5 + && theme.planet_type == PlanetType::Desert + { (temperature_bias.abs() as f64) < (theme.temperature.abs() as f64) + 0.1 } else { (theme.temperature as f64) * (temperature_bias as f64) >= -0.1 }; - if (theme.planet_type == *planet_type) && flag2 { - if self.star.is_birth() { - if theme.distribute == ThemeDistribute::Default { + if (theme.planet_type == planet_type) && temperature_matches { + if self.star.is_birth() { + if theme.distribute == ThemeDistribute::Default { + potential_themes.push(theme); + } + } else if theme.distribute == ThemeDistribute::Default + || theme.distribute == ThemeDistribute::Interstellar + { potential_themes.push(theme); } - } else if theme.distribute == ThemeDistribute::Default - || theme.distribute == ThemeDistribute::Interstellar - { - potential_themes.push(theme); } } } - } - - if potential_themes.is_empty() { - for theme in &unused_themes { - if theme.planet_type == PlanetType::Desert { - potential_themes.push(theme); + if potential_themes.is_empty() { + for theme in &unused_themes { + if theme.planet_type == PlanetType::Desert { + potential_themes.push(theme); + } } } - } - if potential_themes.is_empty() { - for theme in &*THEME_PROTOS { - if theme.planet_type == PlanetType::Desert { - potential_themes.push(theme); + if potential_themes.is_empty() { + for theme in &*THEME_PROTOS { + if theme.planet_type == PlanetType::Desert { + potential_themes.push(theme); + } } } - } - let theme_proto = potential_themes[((self.theme_rand1 * (potential_themes.len() as f64)) - as usize) - % potential_themes.len()]; - used_theme_ids.push(theme_proto.id); - theme_proto - }); + let theme_proto = potential_themes[((self.theme_rand1 * (potential_themes.len() as f64)) + as usize) + % potential_themes.len()]; + used_theme_ids.push(theme_proto.id); + theme_proto + }) + } - pub fn get_type(&self) -> &PlanetType { - &self.get_theme().planet_type + pub fn get_algo_id(&self) -> i32 { + *self.theme_algo_id.get_or_init(|| { + let theme = self.get_theme(); + if theme.algos.is_empty() { + 0 + } else { + *theme + .algos + .get( + (self.theme_rand2 * (theme.algos.len() as f64)) as usize + % theme.algos.len(), + ) + .unwrap() + } + }) } - lazy_getter_ref!(self, get_gases, Vec<(i32, f32)>, { - let mut gases: Vec<(i32, f32)> = vec![]; - if !self.is_gas_giant() { - return gases; + pub fn get_mod_x(&self) -> f64 { + let theme = self.get_theme(); + if theme.algos.is_empty() { + 0.0 + } else { + theme.mod_x.0 + self.theme_rand3 * (theme.mod_x.1 - theme.mod_x.0) } - let gas_coef = self.star.game_desc.gas_coef(); - let mut rand = DspRandom::new(self.theme_seed); - - let theme_proto = self.get_theme(); - let coef = self.star.get_resource_coef().powf(0.3); - - for (item, speed) in theme_proto - .gas_items - .iter() - .zip(theme_proto.gas_speeds.iter()) - { - let num2 = speed * (rand.next_f32() * 21.0 / 110.0 + 10.0 / 11.0) * gas_coef; - gases.push((*item, num2 * coef)) + } + + pub fn get_mod_y(&self) -> f64 { + let theme = self.get_theme(); + if theme.algos.is_empty() { + 0.0 + } else { + theme.mod_y.0 + self.theme_rand4 * (theme.mod_y.1 - theme.mod_y.0) } - gases - }); + } + + pub fn get_type(&self) -> &PlanetType { + &self.get_theme().planet_type + } + pub fn get_gases(&self) -> &Vec<(i32, f32)> { + self.gases.get_or_init(|| { + if !self.is_gas_giant() { + return Vec::with_capacity(0); + } + let mut gases: Vec<(i32, f32)> = Vec::with_capacity(2); + let gas_coef = self.game_desc.gas_coef(); + let mut rand = DspRandom::new(self.theme_seed); + let theme_proto = self.get_theme(); + let coef = self.star.get_resource_coef().powf(0.3); + for (item, speed) in theme_proto + .gas_items + .iter() + .zip(theme_proto.gas_speeds.iter()) + { + let num2 = speed * (rand.next_f32() * 21.0 / 110.0 + 10.0 / 11.0) * gas_coef; + gases.push((*item, num2 * coef)) + } + gases + }) + } - lazy_getter_ref!(self, get_veins, Vec, { - let mut output: Vec = vec![]; + pub fn can_have_vein(&self, vein_type: &VeinType) -> bool { + let theme = self.get_theme(); if self.is_gas_giant() { - return output; + false + } else if vein_type.is_rare() { + theme.rare_veins.contains(vein_type) + || match self.star.star_type { + StarType::BlackHole | StarType::NeutronStar => vein_type == &VeinType::Mag, + StarType::WhiteDwarf => { + matches!( + vein_type, + VeinType::Diamond | VeinType::Fractal | VeinType::Grat + ) + } + _ => false, + } + } else { + let vein_index = *vein_type as i32; + if let Some(x) = theme.vein_spot.get((vein_index - 1) as usize) { + *x != 0 + } else { + false + } } - let mut rand1 = DspRandom::new(self.seed); - rand1.next_f64(); - rand1.next_f64(); - rand1.next_f64(); - rand1.next_f64(); - rand1.next_f64(); - rand1.next_f64(); - let theme_proto = self.get_theme(); - let mut num_array_1: Vec = (0..15_i32) - .map(|i| *theme_proto.vein_spot.get((i - 1) as usize).unwrap_or(&0)) - .collect(); - let mut num_array_2: Vec = (0..15_i32) - .map(|i| *theme_proto.vein_count.get((i - 1) as usize).unwrap_or(&0.0)) - .collect(); - let mut num_array_3: Vec = (0..15_i32) - .map(|i| { - *theme_proto - .vein_opacity - .get((i - 1) as usize) - .unwrap_or(&0.0) - }) - .collect(); - - let mut add_until = |i: &mut i32, t: f64| { - for _ in 1..12 { - if rand1.next_f64() >= t { - break; + } + + pub fn get_estimated_veins(&self) -> &Vec { + self.estimated_veins.get_or_init(|| { + if self.is_gas_giant() { + return Vec::with_capacity(0); + } + let mut output: Vec = Vec::with_capacity(14); + let mut rand1 = DspRandom::new(self.seed); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + let theme_proto = self.get_theme(); + let mut vein_spots: Vec = (0..15_i32) + .map(|i| *theme_proto.vein_spot.get((i - 1) as usize).unwrap_or(&0)) + .collect(); + let mut vein_counts: Vec = (0..15_i32) + .map(|i| *theme_proto.vein_count.get((i - 1) as usize).unwrap_or(&0.0)) + .collect(); + let mut vein_opacities: Vec = (0..15_i32) + .map(|i| { + *theme_proto + .vein_opacity + .get((i - 1) as usize) + .unwrap_or(&0.0) + }) + .collect(); + + let mut random_vein_spots = |t: f64| { + for i in 0..11 { + if rand1.next_f64() >= t { + return i; + } + } + 11 + }; + + let star_type_multiplier: f32 = match self.star.star_type { + StarType::MainSeqStar => match self.star.get_spectr() { + SpectrType::M => 2.5, + SpectrType::G => 0.7, + SpectrType::F => 0.6, + SpectrType::B => 0.4, + SpectrType::O => 1.6, + _ => 1.0, + }, + StarType::GiantStar => 2.5, + StarType::WhiteDwarf => { + vein_spots[9] += 2 + random_vein_spots(0.45); + vein_counts[9] = 0.7; + vein_opacities[9] = 1.0; + vein_spots[10] += 2 + random_vein_spots(0.45); + vein_counts[10] = 0.7; + vein_opacities[10] = 1.0; + vein_spots[12] += 1 + random_vein_spots(0.5); + vein_counts[12] = 0.7; + vein_opacities[12] = 0.3; + 3.5 } - *i += 1; + StarType::NeutronStar => { + vein_spots[14] += 1 + random_vein_spots(0.65); + vein_counts[14] = 0.7; + vein_opacities[14] = 0.3; + 4.5 + } + StarType::BlackHole => { + vein_spots[14] += 1 + random_vein_spots(0.65); + vein_counts[14] = 0.7; + vein_opacities[14] = 0.3; + 5.0 + } + }; + let is_rare_resource = self.game_desc.is_rare_resource(); + let mut f = self.star.get_resource_coef(); + if theme_proto.distribute == ThemeDistribute::Birth { + f *= 2.0 / 3.0; + } else if is_rare_resource { + if f > 1.0 { + f = f.powf(0.8) + } + f *= 0.7; } - }; + for (index1, rare_vein_ref) in theme_proto.rare_veins.iter().enumerate() { + let rare_vein = *rare_vein_ref as usize; + let rare_vein_chance = theme_proto.rare_settings + [index1 * 4 + (if self.star.is_birth() { 0 } else { 1 })]; + let rare_setting_1 = theme_proto.rare_settings[index1 * 4 + 2]; + let rare_setting_2 = theme_proto.rare_settings[index1 * 4 + 3]; + let adjusted_rare_chance = + 1.0 - (1.0 - rare_vein_chance).powf(star_type_multiplier); + let adjusted_rare_count = 1.0 - (1.0 - rare_setting_2).powf(star_type_multiplier); + if rand1.next_f64() < (adjusted_rare_chance as f64) { + vein_spots[rare_vein] += 1; + vein_counts[rare_vein] = adjusted_rare_count; + vein_opacities[rare_vein] = adjusted_rare_count; + for _ in 1..12 { + if rand1.next_f64() >= (rare_setting_1 as f64) { + break; + } + vein_spots[rare_vein] += 1; + } + } + } + let is_infinite_resource = self.game_desc.is_infinite_resource(); + for index3 in 1..15 { + let vein_spot_count = vein_spots[index3 as usize]; + if vein_spot_count > 0 { + let vein_type: VeinType = unsafe { ::std::mem::transmute(index3) }; + let mut vein = EstimatedVein::new(); + vein.vein_type = vein_type; + vein.min_group = vein_spot_count - 1; + vein.max_group = vein_spot_count + 1; + if vein.vein_type == VeinType::Oil { + vein.min_patch = 1; + vein.max_patch = 1; + } else { + let vein_count_factor = vein_counts[index3 as usize]; + vein.min_patch = (vein_count_factor * 20.0).round_ties_even() as i32; + vein.max_patch = (vein_count_factor * 24.0).round_ties_even() as i32; + } + let total_amount_factor = if vein.vein_type == VeinType::Oil { + f.powf(0.5) + } else { + f + }; + if is_infinite_resource && vein.vein_type != VeinType::Oil { + vein.min_amount = 1; + vein.max_amount = 1; + } else { + let base_amount = + ((vein_opacities[index3 as usize] * 100000.0 * total_amount_factor) + .round_ties_even() as i32) + .max(20); + let amount_variance = if base_amount < 16000 { + ((base_amount as f32) * (15.0 / 16.0)).floor() as i32 + } else { + 15000 + }; + let map_amount = |amount: i32| -> i32 { + let x1 = ((amount as f32) * 1.1).round_ties_even(); + let x2 = (if vein.vein_type == VeinType::Oil { + x1 * self.game_desc.oil_amount_multiplier() + } else { + x1 * self.game_desc.resource_multiplier + }) + .round_ties_even() as i32; + x2.max(1) + }; + vein.min_amount = map_amount(base_amount - amount_variance); + vein.max_amount = map_amount(base_amount + amount_variance); + } + output.push(vein); + } + } + output + }) + } + + pub fn get_runtime_orbit_rotation(&self) -> Quaternion { + let mut rot = Quaternion::angle_axis(self.orbit_longitude, &VectorF3::up()) + * Quaternion::angle_axis(self.get_orbit_inclination(), &VectorF3::forward()); + if let Some(parent) = self.orbit_around.borrow().as_deref() { + rot = parent.get_runtime_orbit_rotation() * rot; + } + rot + } - let p: f32 = match self.star.star_type { - StarType::MainSeqStar => match self.star.get_spectr() { - SpectrType::M => 2.5, - SpectrType::G => 0.7, - SpectrType::F => 0.6, - SpectrType::B => 0.4, - SpectrType::O => 1.6, - _ => 1.0, - }, - StarType::GiantStar => 2.5, - StarType::WhiteDwarf => { - num_array_1[9] += 2; - add_until(num_array_1.get_mut(9).unwrap(), 0.45); - num_array_2[9] = 0.7; - num_array_3[9] = 1.0; - num_array_1[10] += 2; - add_until(num_array_1.get_mut(10).unwrap(), 0.45); - num_array_2[10] = 0.7; - num_array_3[10] = 1.0; - num_array_1[12] += 1; - add_until(num_array_1.get_mut(12).unwrap(), 0.5); - num_array_2[12] = 0.7; - num_array_3[12] = 0.3; - 3.5 + pub fn get_runtime_system_rotation(&self) -> Quaternion { + self.get_runtime_orbit_rotation() + * Quaternion::angle_axis(self.get_obliquity(), &VectorF3::forward()) + } + + pub fn predict_pose(&self, time: f64) -> Pose { + use std::f64::consts::PI as PI_F64; + + // Orbit angle + let orbit_phase_time = time / self.get_orbital_period() + (self.orbit_phase as f64) / 360.0; + let orbit_cycle = (orbit_phase_time + 0.1) as i32; + let orbit_fraction = orbit_phase_time - (orbit_cycle as f64); + let orbit_angle = orbit_fraction * 2.0 * PI_F64; + + let orbit_radius = self.get_orbital_radius() as f64; + let local_pos = VectorF3( + (orbit_angle.cos() * orbit_radius) as f32, + 0.0, + (orbit_angle.sin() * orbit_radius) as f32, + ); + + let orbit_rot = self.get_runtime_orbit_rotation(); + let mut position = orbit_rot.q_rotate_lf(&local_pos); + + // If this planet orbits another planet, add the parent's position + if let Some(parent) = self.orbit_around.borrow().as_deref() { + let parent_pose = parent.predict_pose(time); + position = VectorF3( + position.0 + parent_pose.position.0, + position.1 + parent_pose.position.1, + position.2 + parent_pose.position.2, + ); + } + + // Rotation angle from time + let rotation_phase_time = + time / self.get_rotation_period() + (self.rotation_phase as f64) / 360.0; + let rotation_cycle = (rotation_phase_time + 0.1) as i32; + let rotation_angle = (rotation_phase_time - (rotation_cycle as f64)) * 360.0; + + let rotation = self.get_runtime_system_rotation() + * Quaternion::angle_axis(rotation_angle as f32, &VectorF3::down()); + + Pose::new(position, rotation) + } + + /// Computes the star direction vector at time 85.0 (used in `gen_birth_points`). + /// + /// Port of C# lines 764-766: + /// ```csharp + /// Pose pose = this.PredictPose(85.0); + /// Vector3 vector3_1 = (Vector3) Maths.QInvRotateLF( + /// pose.rotation, this.star.uPosition - (VectorLF3) pose.position * 40000.0); + /// vector3_1.Normalize(); + /// ``` + /// + /// Where `star.uPosition` = `star.position * 2400000.0`. + pub fn get_star_direction(&self) -> VectorF3 { + let pose = self.predict_pose(85.0); + + // star.uPosition = star.position * 2400000.0 + let star_pos = &self.star.position; // Vector3 (f64) + let star_u_pos = VectorF3( + (star_pos.0 * 2400000.0) as f32, + (star_pos.1 * 2400000.0) as f32, + (star_pos.2 * 2400000.0) as f32, + ); + + // pose.position * 40000.0 + let pose_scaled = VectorF3( + pose.position.0 * 40000.0, + pose.position.1 * 40000.0, + pose.position.2 * 40000.0, + ); + + // star.uPosition - pose.position * 40000.0 + let delta = VectorF3( + star_u_pos.0 - pose_scaled.0, + star_u_pos.1 - pose_scaled.1, + star_u_pos.2 - pose_scaled.2, + ); + + // QInvRotateLF(pose.rotation, delta) then normalize + let mut dir = pose.rotation.q_inv_rotate_lf(&delta); + dir.normalize(); + dir + } + + fn can_place_vein( + &self, + algo_id: i32, + vein_type: &VeinType, + zero: &VectorF3, + raw_data: &mut PlanetRawData, + ) -> bool { + if algo_id == 7 && vein_type != &VeinType::Bamboo { + return true; + } + // `zero` is already normalized by the caller + let height = raw_data.query_height_normalized(zero); + match algo_id { + 7 => height <= self.radius - 4.0, + 11 => { + height >= self.radius + && match vein_type { + &VeinType::Oil => height >= self.radius + 0.5, + &VeinType::Iron | &VeinType::Copper => height <= self.radius + 0.7, + &VeinType::Silicium | &VeinType::Titanium => height > self.radius + 0.7, + _ => true, + } } - StarType::NeutronStar => { - num_array_1[14] += 1; - add_until(num_array_1.get_mut(14).unwrap(), 0.65); - num_array_2[14] = 0.7; - num_array_3[14] = 0.3; - 4.5 + 12 => { + height >= self.radius + && match vein_type { + &VeinType::Oil => height >= self.radius + 0.5, + &VeinType::Fireice => height >= self.radius + 1.2, + _ => true, + } } - StarType::BlackHole => { - num_array_1[14] += 1; - add_until(num_array_1.get_mut(14).unwrap(), 0.65); - num_array_2[14] = 0.7; - num_array_3[14] = 0.3; - 5.0 + 13 => { + height >= self.radius + && match vein_type { + &VeinType::Oil => height >= self.radius + 0.5, + &VeinType::Iron + | &VeinType::Copper + | &VeinType::Silicium + | &VeinType::Titanium => height <= self.radius + 0.7, + _ => true, + } } - }; - let is_rare_resource = self.star.game_desc.is_rare_resource(); - let mut f = self.star.get_resource_coef(); - if theme_proto.distribute == ThemeDistribute::Birth { - f *= 2.0 / 3.0; - } else if is_rare_resource { - if f > 1.0 { - f = f.powf(0.8) + _ => { + height >= self.radius + && match vein_type { + &VeinType::Oil => height >= self.radius + 0.5, + _ => true, + } } - f *= 0.7; } + } - for (index1, rare_vein_ref) in theme_proto.rare_veins.iter().enumerate() { - let rare_vein = rare_vein_ref.clone() as usize; - let num2 = - theme_proto.rare_settings[index1 * 4 + (if self.star.is_birth() { 0 } else { 1 })]; - let rare_setting_1 = theme_proto.rare_settings[index1 * 4 + 2]; - let rare_setting_2 = theme_proto.rare_settings[index1 * 4 + 3]; - let num4 = 1.0 - (1.0 - num2).powf(p); - let num5 = 1.0 - (1.0 - rare_setting_2).powf(p); - if rand1.next_f64() < (num4 as f64) { - num_array_1[rare_vein] += 1; - num_array_2[rare_vein] = num5; - num_array_3[rare_vein] = num5; - for _ in 1..12 { - if rand1.next_f64() >= (rare_setting_1 as f64) { - break; + pub fn get_actual_veins(&self) -> &Vec { + self.actual_veins.get_or_init(|| { + if self.gas_giant { + return Vec::with_capacity(0); + } + + let theme = self.get_theme(); + let mut rand1 = DspRandom::new(self.seed); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + let birth_seed = rand1.next_seed(); + let mut rand2 = DspRandom::new(rand1.next_seed()); + let mut vein_spots: Vec = (0..15_i32) + .map(|i| *theme.vein_spot.get((i - 1) as usize).unwrap_or(&0)) + .collect(); + let mut vein_counts: Vec = (0..15_i32) + .map(|i| *theme.vein_count.get((i - 1) as usize).unwrap_or(&0.0)) + .collect(); + let mut vein_opacities: Vec = (0..15_i32) + .map(|i| *theme.vein_opacity.get((i - 1) as usize).unwrap_or(&0.0)) + .collect(); + + let mut random_vein_spots = |t: f64| { + for i in 0..11 { + if rand1.next_f64() >= t { + return i; + } + } + 11 + }; + + let star_type_multiplier: f32 = match self.star.star_type { + StarType::MainSeqStar => match self.star.get_spectr() { + SpectrType::M => 2.5, + SpectrType::G => 0.7, + SpectrType::F => 0.6, + SpectrType::B => 0.4, + SpectrType::O => 1.6, + _ => 1.0, + }, + StarType::GiantStar => 2.5, + StarType::WhiteDwarf => { + vein_spots[9] += 2 + random_vein_spots(0.45); + vein_counts[9] = 0.7; + vein_opacities[9] = 1.0; + vein_spots[10] += 2 + random_vein_spots(0.45); + vein_counts[10] = 0.7; + vein_opacities[10] = 1.0; + vein_spots[12] += 1 + random_vein_spots(0.5); + vein_counts[12] = 0.7; + vein_opacities[12] = 0.3; + 3.5 + } + StarType::NeutronStar => { + vein_spots[14] += 1 + random_vein_spots(0.65); + vein_counts[14] = 0.7; + vein_opacities[14] = 0.3; + 4.5 + } + StarType::BlackHole => { + vein_spots[14] += 1 + random_vein_spots(0.65); + vein_counts[14] = 0.7; + vein_opacities[14] = 0.3; + 5.0 + } + }; + + for (index1, rare_vein_ref) in theme.rare_veins.iter().enumerate() { + let rare_vein = *rare_vein_ref as usize; + let rare_vein_chance = + theme.rare_settings[index1 * 4 + (if self.star.is_birth() { 0 } else { 1 })]; + let rare_setting_1 = theme.rare_settings[index1 * 4 + 2]; + let rare_setting_2 = theme.rare_settings[index1 * 4 + 3]; + let adjusted_rare_chance = + 1.0 - (1.0 - rare_vein_chance).powf(star_type_multiplier); + let adjust_rare_count = 1.0 - (1.0 - rare_setting_2).powf(star_type_multiplier); + if rand1.next_f64() < (adjusted_rare_chance as f64) { + vein_spots[rare_vein] += 1; + vein_counts[rare_vein] = adjust_rare_count; + vein_opacities[rare_vein] = adjust_rare_count; + for _ in 1..12 { + if rand1.next_f64() >= (rare_setting_1 as f64) { + break; + } + vein_spots[rare_vein] += 1; } - num_array_1[rare_vein] += 1; } } - } - let is_infinite_resource = self.star.game_desc.is_infinite_resource(); - for index3 in 1..15 { - let num8 = num_array_1[index3 as usize]; - if num8 > 0 { + let is_rare_resource = self.game_desc.is_rare_resource(); + let mut resource_coef = self.star.get_resource_coef(); + let is_birth_planet = theme.distribute == ThemeDistribute::Birth; + if is_birth_planet { + resource_coef *= 2.0 / 3.0; + } else if is_rare_resource { + if resource_coef > 1.0 { + resource_coef = resource_coef.powf(0.8) + } + resource_coef *= 0.7; + } + let mut vein_vectors: Vec<(VeinType, VectorF3, bool)> = Vec::with_capacity(512); + // Fetch PlanetRawData once and thread it through all query_height calls + let mut raw_data = PlanetRawData::new(&self); + + let birth_point = if is_birth_planet { + let star_direction = self.get_star_direction(); + let birth_point_data = + BirthPoints::new(&mut raw_data, birth_seed, self.radius, star_direction); + vein_vectors.push((VeinType::Iron, birth_point_data.birth_resource_point0, true)); + vein_vectors.push(( + VeinType::Copper, + birth_point_data.birth_resource_point1, + true, + )); + let mut birth_point = birth_point_data.birth_point; + birth_point.normalize(); + birth_point * 0.75 + } else { + let x = rand2.next_f64() * 2.0 - 1.0; + let y = rand2.next_f64() - 0.5; + let z = rand2.next_f64() * 2.0 - 1.0; + let mut birth_point = VectorF3::new(x as f32, y as f32, z as f32); + birth_point.normalize(); + birth_point * (rand2.next_f64() * 0.4 + 0.2) as f32 + }; + + let is_infinite_resource = self.game_desc.is_infinite_resource(); + // Fixed array indexed by VeinType discriminant (0..16) — avoids HashMap hashing overhead + let mut amount_map: [i32; 16] = [0; 16]; + + let min_vein_spacing = 2.1 / self.radius; + let min_vein_spacing_sq = (min_vein_spacing as f64) * (min_vein_spacing as f64); + let algo_id = self.get_algo_id(); + + for index3 in 1..15 { + if vein_vectors.len() >= 512 { + break; + } + let mut vein_spot_count = vein_spots[index3 as usize]; + if vein_spot_count > 1 { + vein_spot_count += rand2.next_i32(3) - 1; + } let vein_type: VeinType = unsafe { ::std::mem::transmute(index3) }; - let mut vein = Vein::new(); - vein.vein_type = vein_type; - vein.min_group = num8 - 1; - vein.max_group = num8 + 1; - if vein.vein_type == VeinType::Oil { - vein.min_patch = 1; - vein.max_patch = 1; + let min_sq_dist = min_vein_spacing_sq + * (if vein_type == VeinType::Oil { + 100_f64 + } else { + 196_f64 + }); + + for _ in 0..vein_spot_count { + for _ in 0..200 { + let x = rand2.next_f64() * 2.0 - 1.0; + let y = rand2.next_f64() * 2.0 - 1.0; + let z = rand2.next_f64() * 2.0 - 1.0; + let mut normal_dir = VectorF3(x as f32, y as f32, z as f32); + if vein_type != VeinType::Oil { + normal_dir += birth_point; + } + normal_dir.normalize(); + if self.can_place_vein(algo_id, &vein_type, &normal_dir, &mut raw_data) { + let not_too_close_to_other_vein = + vein_vectors.iter().all(|(_, pos, _)| { + (pos.distance_sq_from(&normal_dir) as f64) >= min_sq_dist + }); + if not_too_close_to_other_vein { + vein_vectors.push((vein_type, normal_dir, false)); + break; + } + } + } + if vein_vectors.len() >= 512 { + break; + } + } + } + + for (vein_type, vein_vector, is_birth_resource) in vein_vectors.iter() { + let is_oil = vein_type == &VeinType::Oil; + let normalized = vein_vector.normalized(); + let rotation = Quaternion::from_to_rotation(&VectorF3::up(), &normalized); + let right_axis = &rotation * &VectorF3::right(); + let forward_axis = &rotation * &VectorF3::forward(); + let vein_type_index = *vein_type as i32; + let target_node_count = if *is_birth_resource { + rand2.next_f64(); + 6 + } else if is_oil { + rand2.next_f64(); + 1 + } else { + (vein_counts.get(vein_type_index as usize).unwrap() + * (rand2.next_i32(5) + 20) as f32) + .round_ties_even() as usize + }; + let mut vein_nodes = Vec::with_capacity(target_node_count); + vein_nodes.push(VectorF2::zero()); + let vein_density = if *is_birth_resource { + 0.2_f32 } else { - let num12 = num_array_2[index3 as usize]; - vein.min_patch = (num12 * 20.0).round() as i32; - vein.max_patch = (num12 * 24.0).round() as i32; + *vein_opacities.get(vein_type_index as usize).unwrap() + }; + for _ in 0..20 { + if vein_nodes.len() >= target_node_count { + break; + } + for index8 in 0..vein_nodes.len() { + let existing_node = vein_nodes.get(index8).unwrap(); + if existing_node.magnitude_sq() <= 36.0 { + let random_angle_radians = rand2.next_f64() * PI * 2.0; + let mut random_dir = VectorF2::new( + random_angle_radians.cos() as f32, + random_angle_radians.sin() as f32, + ); + random_dir += existing_node * 0.2; + random_dir.normalize(); + let new_node = existing_node + &random_dir; + let not_too_close_to_other_node = vein_nodes + .iter() + .all(|v| v.distance_sq_from(&new_node) >= 0.85); + if not_too_close_to_other_node { + vein_nodes.push(new_node); + } + if vein_nodes.len() >= target_node_count { + break; + } + } + } } - let num16 = if vein.vein_type == VeinType::Oil { - f.powf(0.5) + let adjusted_resource_coef = if is_oil { + resource_coef.powf(0.5) } else { - f + resource_coef }; - if is_infinite_resource && vein.vein_type != VeinType::Oil { - vein.min_amount = 1; - vein.max_amount = 1; + let total_amount = ((vein_density * 100000.0 * adjusted_resource_coef) + .round_ties_even() as i32) + .max(20); + let amount_variance = if total_amount < 16000 { + ((total_amount as f32) * (15.0 / 16.0)) as i32 } else { - let num17 = - ((num_array_3[index3 as usize] * 100000.0 * num16).round() as i32).max(20); - let num18 = if num17 < 16000 { - ((num17 as f32) * (15.0 / 16.0)).floor() as i32 + 15000 + }; + let min_value = total_amount - amount_variance; + let value_range = amount_variance * 2 + 1; + for pos in vein_nodes.iter() { + let raw_amount = rand2.next_i32(value_range) + min_value; + let amount = if is_infinite_resource && !is_oil { + 1 } else { - 15000 - }; - - let map_amount = |amount: i32| -> i32 { - let x1 = ((amount as f32) * 1.1).round(); - let x2 = (if vein.vein_type == VeinType::Oil { - x1 * self.star.game_desc.oil_amount_multipler() + let multiplier = if is_oil { + self.game_desc.oil_amount_multiplier() } else { - x1 * self.star.game_desc.resource_multiplier - }) - .round() as i32; - x2.max(1) + self.game_desc.resource_multiplier + }; + (((raw_amount as f32) * 1.1 * multiplier).round_ties_even() as i32).max(1) }; - - vein.min_amount = map_amount(num17 - num18); - vein.max_amount = map_amount(num17 + num18); + if algo_id == 7 || theme.water_item_id == 0 { + amount_map[*vein_type as usize] += amount; + } else { + let node_offset = + ((right_axis * pos.0) + (forward_axis * pos.1)) * min_vein_spacing; + let mut pos = normalized + node_offset; + if is_oil { + pos = self.snap_to(&pos); + } + let surface_height = raw_data.query_height(&pos); + if surface_height >= self.radius { + // println!("{:?},{:?},{}", pos * surface_height, vein_type, amount); + amount_map[*vein_type as usize] += amount; + } + } } - output.push(vein); } - } - output - }); + + amount_map + .iter() + .enumerate() + .filter(|(_, &amount)| amount > 0) + .map(|(i, &amount)| ActualVein { + vein_type: unsafe { ::std::mem::transmute(i as i32) }, + amount, + }) + .collect() + }) + } + + pub fn is_acutal_veins_generated(&self) -> bool { + self.actual_veins.get().is_some() + } + + fn snap_to(&self, pos: &VectorF3) -> VectorF3 { + let segment = ((self.radius / 4.0 + 0.1) as i32 * 4) as f32; + let two_pi = PI as f32 * 2.0; + let pos = pos.normalized(); + let latitude_angle = pos.1.asin(); + let mut longitude_angle = pos.0.atan2(-pos.2); + let mut latitude_index_raw = latitude_angle / two_pi * segment; + let latitude_index = (latitude_index_raw.abs() - 0.1).max(0.0) as i32; + let longitude_segment_count = + determine_longitude_segment_count(latitude_index, segment) as f32; + let mut longitude_index_raw = longitude_angle / two_pi * longitude_segment_count; + latitude_index_raw = (latitude_index_raw * 5.0).round_ties_even() / 5.0; + longitude_index_raw = (longitude_index_raw * 5.0).round_ties_even() / 5.0; + let latitude_radians = latitude_index_raw / segment * two_pi; + longitude_angle = longitude_index_raw / longitude_segment_count * two_pi; + let latitude_sin = latitude_radians.sin(); + let latitude_cos = latitude_radians.cos(); + let longitude_sin = longitude_angle.sin(); + let longitude_cos = longitude_angle.cos(); + VectorF3( + latitude_cos * longitude_sin, + latitude_sin, + latitude_cos * (-longitude_cos), + ) + } } +fn determine_longitude_segment_count(latitude_index: i32, segment: f32) -> i32 { + let candidate_segment_count = (((latitude_index as f32) / (segment / 4.0) * PI as f32 * 0.5) + .cos() + .abs() + * segment) + .ceil() as usize; + if candidate_segment_count < 500 { + SEGMENT_TABLE[candidate_segment_count] + } else { + ((candidate_segment_count as i32) + 49) / 100 * 100 + } +} + +const SEGMENT_TABLE: [i32; 512] = [ + 1, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16, 16, 16, 20, 20, 20, 20, 20, 20, 20, 20, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, + 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, + 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, + 240, 240, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, + 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, + 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, + 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, + 300, 300, 300, 300, 300, 300, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, + 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, + 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, + 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, + 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, + 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 500, 500, 500, 500, 500, 500, 500, 500, + 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, + 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, + 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, +]; + impl Serialize for Planet<'_> { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let mut state = serializer.serialize_struct("Planet", 16)?; + let mut state = serializer.serialize_struct("Planet", 15)?; state.serialize_field("index", &self.index)?; state.serialize_field("orbitAround", &self.orbit_around.borrow().map(|p| p.index))?; state.serialize_field("orbitIndex", &self.orbit_index)?; @@ -651,15 +1229,17 @@ impl Serialize for Planet<'_> { state.serialize_field("orbitInclination", &self.get_orbit_inclination())?; state.serialize_field("orbitLongitude", &self.orbit_longitude)?; state.serialize_field("orbitalPeriod", &self.get_orbital_period())?; - state.serialize_field("orbitPhase", &self.orbit_phase)?; state.serialize_field("obliquity", &self.get_obliquity())?; state.serialize_field("rotationPeriod", &self.get_rotation_period())?; - state.serialize_field("rotationPhase", &self.rotation_phase)?; state.serialize_field("type", &self.get_type())?; state.serialize_field("luminosity", &self.get_luminosity())?; state.serialize_field("theme", &self.get_theme())?; - state.serialize_field("veins", &self.get_veins())?; state.serialize_field("gases", &self.get_gases())?; + if self.game_desc.use_actual_veins { + state.serialize_field("actualVeins", &self.get_actual_veins())?; + } else { + state.serialize_field("veins", &self.get_estimated_veins())?; + } state.end() } } diff --git a/inserter/src/algorithm/data/planet_algorithms/algo0.rs b/inserter/src/algorithm/data/planet_algorithms/algo0.rs new file mode 100644 index 0000000..12c42cb --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo0.rs @@ -0,0 +1,22 @@ +use super::super::planet::Planet; +use super::PlanetAlgorithm; + +/// PlanetAlgorithm0 - All vertices at planet radius. +/// This is the simplest algorithm. +pub struct PlanetAlgorithm0 { + radius: f64, +} + +impl PlanetAlgorithm0 { + pub fn new(planet: &Planet) -> Self { + Self { + radius: planet.radius as f64, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm0 { + fn get_height(&self, _index: usize) -> f64 { + self.radius + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo1.rs b/inserter/src/algorithm/data/planet_algorithms/algo1.rs new file mode 100644 index 0000000..0ecf336 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo1.rs @@ -0,0 +1,83 @@ +use super::super::math::*; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm1 - Two-layer FBM noise with Levelize3. +pub struct PlanetAlgorithm1 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm1 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm1 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.01; + let freq_scale_y: f64 = 0.012; + let freq_scale_z: f64 = 0.01; + let noise_amplitude: f64 = 3.0; + let noise_offset: f64 = -0.2; + let noise2_amplitude: f64 = 0.9; + let noise2_offset: f64 = 0.5; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let layer1_noise = self.noise1.noise_3d_fbm( + world_x * freq_scale_x, + world_y * freq_scale_y, + world_z * freq_scale_z, + 6, + 0.5, + 2.0, + ) * noise_amplitude + + noise_offset; + let layer2_noise = self.noise2.noise_3d_fbm( + world_x * (1.0 / 400.0), + world_y * (1.0 / 400.0), + world_z * (1.0 / 400.0), + 3, + 0.5, + 2.0, + ) * noise_amplitude + * noise2_amplitude + + noise2_offset; + let clamped_layer2 = if layer2_noise > 0.0 { + layer2_noise * 0.5 + } else { + layer2_noise + }; + let combined_noise = layer1_noise + clamped_layer2; + let f = if combined_noise > 0.0 { + combined_noise * 0.5 + } else { + combined_noise * 1.6 + }; + let shaped_height = if f > 0.0 { + levelize3(f, 0.7, 0.0) + } else { + levelize2(f, 0.5, 0.0) + }; + + self.radius + shaped_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo10.rs b/inserter/src/algorithm/data/planet_algorithms/algo10.rs new file mode 100644 index 0000000..78c8dde --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo10.rs @@ -0,0 +1,243 @@ +use super::super::math::{levelize, levelize4}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::random_table::RandomTable; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm10 - FBM noise with 10 elliptical crater features. +pub struct PlanetAlgorithm10 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, + noise3: SimplexNoise, + noise4: SimplexNoise, + ellipses: Vec<([f64; 3], f64)>, + eccentricities: Vec, + heights: Vec, +} + +#[inline] +fn remap(src_min: f64, src_max: f64, tgt_min: f64, tgt_max: f64, x: f64) -> f64 { + (x - src_min) / (src_max - src_min) * (tgt_max - tgt_min) + tgt_min +} + +impl PlanetAlgorithm10 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + let seed3 = rand.next_seed(); + let seed4 = rand.next_seed(); + + let mut seed5 = rand.next_seed(); + let mut els = Vec::with_capacity(10); + let mut eccs = Vec::with_capacity(10); + let mut hs = Vec::with_capacity(10); + + for _ in 0..10 { + let mut v = RandomTable::spheric_normal(&mut seed5, 1.0); + v.normalize(); + v *= planet.radius as f64; + let w = (rand.next_f64() * 10.0 + 40.0) as f32; + els.push(([v.0, v.1, v.2], (w * w) as f64)); + + let ecc = if rand.next_f64() <= 0.5 { + remap(0.0, 1.0, 0.2, 1.0 / 3.0, rand.next_f64()) + } else { + remap(0.0, 1.0, 3.0, 5.0, rand.next_f64()) + }; + eccs.push(ecc); + + let h = remap(0.0, 1.0, 1.0, 2.0, rand.next_f64()); + hs.push(h); + } + + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + noise3: SimplexNoise::with_seed(seed3), + noise4: SimplexNoise::with_seed(seed4), + ellipses: els, + eccentricities: eccs, + heights: hs, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm10 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.007; + let freq_scale_y: f64 = 0.007; + let freq_scale_z: f64 = 0.007; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let leveled_x = levelize(world_x * 0.007, 1.0, 0.0); + let leveled_y = levelize(world_y * 0.007, 1.0, 0.0); + let leveled_z = levelize(world_z * 0.007, 1.0, 0.0); + + let xin = leveled_x + + self + .noise3 + .noise_3d(world_x * 0.05, world_y * 0.05, world_z * 0.05) + * 0.04; + let yin = leveled_y + + self + .noise3 + .noise_3d(world_y * 0.05, world_z * 0.05, world_x * 0.05) + * 0.04; + let zin = leveled_z + + self + .noise3 + .noise_3d(world_z * 0.05, world_x * 0.05, world_y * 0.05) + * 0.04; + + let cell_noise = self.noise4.noise_3d(xin, yin, zin).abs(); + let crack_depth = (0.16 - cell_noise) * 10.0; + let crack_clamped = if crack_depth > 0.0 { + if crack_depth > 1.0 { + 1.0 + } else { + crack_depth + } + } else { + 0.0 + }; + let crack_intensity = crack_clamped * crack_clamped; + + let fluid_level = (self.noise3.noise_3d_fbm( + world_y * 0.005, + world_z * 0.005, + world_x * 0.005, + 4, + 0.5, + 2.0, + ) + 0.22) + * 5.0; + let fluid_clamped = if fluid_level > 0.0 { + if fluid_level > 1.0 { + 1.0 + } else { + fluid_level + } + } else { + 0.0 + }; + + let detail_noise = self + .noise4 + .noise_3d_fbm(xin * 1.5, yin * 1.5, zin * 1.5, 2, 0.5, 2.0) + .abs(); + let x_noise_val = self.noise2.noise_3d_fbm( + world_x * freq_scale_x * 5.0, + world_y * freq_scale_y * 5.0, + world_z * freq_scale_z * 5.0, + 4, + 0.5, + 2.0, + ); + let high_freq_amp = x_noise_val * 0.2; + + let mut max_crater = 0.0; + for j in 0..10 { + let e = &self.ellipses[j]; + let ecc = self.eccentricities[j]; + let dx = e.0[0] - world_x; + let dy = e.0[1] - world_y; + let dz = e.0[2] - world_z; + let dist_ecc = ecc * dx * dx + dy * dy + dz * dz; + let dist_scaled = remap(-1.0, 1.0, 0.2, 5.0, x_noise_val) * dist_ecc; + if dist_scaled < e.1 { + let sqrt_val = (dist_scaled / e.1).sqrt(); + let crater_t = 1.0 - (1.0 - sqrt_val); + let mut crater_shape = + 1.0 - crater_t * crater_t * crater_t * crater_t + high_freq_amp * 2.0; + if crater_shape < 0.0 { + crater_shape = 0.0; + } + let candidate = self.heights[j] * crater_shape; + if candidate > max_crater { + max_crater = candidate; + } + } + } + + let warped_x = world_x + (world_y * 0.15).sin() * 2.0; + let warped_y = world_y + (world_z * 0.15).sin() * 2.0; + let warped_z = world_z + (warped_x * 0.15).sin() * 2.0; + + let warp_x_scaled = warped_x * freq_scale_x; + let warp_y_scaled = warped_y * freq_scale_y; + let warp_z_scaled = warped_z * freq_scale_z; + + let f_val = ((self.noise1.noise_3d_fbm( + warp_x_scaled * 0.6, + warp_y_scaled * 0.6, + warp_z_scaled * 0.6, + 4, + 0.5, + 1.8, + ) + 1.0) + * 0.5) + .powf(1.3); + + let remap_noise = remap( + -1.0, + 1.0, + -0.1, + 0.15, + self.noise2.noise_3d_fbm( + warp_x_scaled * 6.0, + warp_y_scaled * 6.0, + warp_z_scaled * 6.0, + 5, + 0.5, + 2.0, + ), + ); + + let turb_base = self.noise2.noise_3d_fbm( + warp_x_scaled * 5.0 * 3.0, + warp_y_scaled * 5.0, + warp_z_scaled * 5.0, + 1, + 0.5, + 2.0, + ); + let turb_detail = self.noise2.noise_3d_fbm( + warp_x_scaled * 5.0 * 3.0 + turb_base * 0.3, + warp_y_scaled * 5.0 + turb_base * 0.3, + warp_z_scaled * 5.0 + turb_base * 0.3, + 5, + 0.5, + 2.0, + ) * 0.1; + + let mut shaped = (levelize(levelize4(f_val, 1.0, 0.0), 1.0, 0.0)).min(1.0); + if shaped <= 0.8 { + if shaped > 0.4 { + shaped += turb_detail; + } else { + shaped += remap_noise; + } + } + + let crater_blend = (shaped * 2.5 - shaped * max_crater).max(remap_noise * 2.0); + let crack_scale = (2.0 - crater_blend) / 2.0; + let mut terrain_height = crater_blend - crack_intensity * 1.2 * fluid_clamped * crack_scale; + if terrain_height >= 0.0 { + terrain_height += (cell_noise * 0.25 + detail_noise * 0.6) * crack_scale; + } + let final_height = terrain_height - 0.1; + + self.radius + final_height + 0.1 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo11.rs b/inserter/src/algorithm/data/planet_algorithms/algo11.rs new file mode 100644 index 0000000..440b1f9 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo11.rs @@ -0,0 +1,99 @@ +use super::super::math::{levelize2, levelize3}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm11 - Complex noise with Remap, Levelize2/Levelize3 and modX/modY. +pub struct PlanetAlgorithm11 { + grid: &'static PlanetGrid, + radius: f64, + mod_y: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, + noise3: SimplexNoise, + mod_freq_x: f64, + mod_freq_y: f64, + mod_freq_z: f64, +} + +#[inline] +fn remap(src_min: f64, src_max: f64, tgt_min: f64, tgt_max: f64, x: f64) -> f64 { + (x - src_min) / (src_max - src_min) * (tgt_max - tgt_min) + tgt_min +} + +impl PlanetAlgorithm11 { + pub fn new(planet: &Planet) -> Self { + let mod_x = planet.get_mod_x(); + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + let seed3 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + mod_y: planet.get_mod_y(), + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + noise3: SimplexNoise::with_seed(seed3), + mod_freq_x: 0.002 * mod_x, + mod_freq_y: 0.002 * mod_x * 4.0, + mod_freq_z: 0.002 * mod_x, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm11 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.007; + let freq_scale_y: f64 = 0.007; + let freq_scale_z: f64 = 0.007; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let detail_noise = self.noise2.noise_3d_fbm( + world_x * freq_scale_x * 4.0, + world_y * freq_scale_y * 8.0, + world_z * freq_scale_z * 4.0, + 3, + 0.5, + 2.0, + ); + let primary_freq_scale = 0.6; + + let inner = self.noise1.noise_3d_fbm( + world_x * freq_scale_x * primary_freq_scale, + world_y * freq_scale_x * 1.5 * 2.5, + world_z * freq_scale_x * primary_freq_scale, + 6, + 0.45, + 1.8, + ) * 0.95 + + detail_noise * 0.05; + + let primary_shaped = levelize2( + (remap(-1.0, 1.0, 0.0, 1.0, inner)).powf(self.mod_y) + 1.0, + 1.0, + 0.0, + ); + + let inner2 = self.noise3.noise_3d_fbm( + world_x * self.mod_freq_x, + world_y * self.mod_freq_y, + world_z * self.mod_freq_z, + 5, + 0.55, + 2.0, + ); + let secondary_shaped = + levelize3((remap(-1.0, 1.0, 0.0, 1.0, inner2)).powf(0.65), 1.0, 0.0) * primary_shaped; + + let final_height = ((secondary_shaped - 0.4) * 0.9).max(-0.3); + + self.radius + final_height + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo12.rs b/inserter/src/algorithm/data/planet_algorithms/algo12.rs new file mode 100644 index 0000000..ee1d084 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo12.rs @@ -0,0 +1,104 @@ +use super::super::math::clamp01; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm12 - Latitude-based terrain with ridged noise and modX/modY. +pub struct PlanetAlgorithm12 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, + freq_scale: f64, + mod_y: f64, +} + +#[inline] +fn curve_evaluate(t: f64) -> f64 { + let t = t / 0.6; + if t >= 1.0 { + 0.0 + } else { + (1.0 - t).powi(3) + (1.0 - t).powi(2) * 3.0 * t + } +} + +#[inline] +fn remap(src_min: f64, src_max: f64, tgt_min: f64, tgt_max: f64, x: f64) -> f64 { + (x - src_min) / (src_max - src_min) * (tgt_max - tgt_min) + tgt_min +} + +impl PlanetAlgorithm12 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + freq_scale: 1.1 * planet.get_mod_x(), + mod_y: planet.get_mod_y(), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm12 { + fn get_height(&self, index: usize) -> f64 { + let ridge_amplitude = 0.2; + let height_multiplier = 8.0; + let pi = std::f64::consts::PI; + + let v = self.grid.get_vertex(index); + let latitude_factor = ((v.1 as f64).abs().asin()) * 2.0 / pi; + let x_pos = v.0 as f64; + let y_pos_mod = (v.1 as f64) * 2.5 * self.mod_y; + let z_pos = v.2 as f64; + + let warp_offset = self.noise2.noise_3d_fbm( + x_pos * self.freq_scale, + y_pos_mod * self.freq_scale, + z_pos * self.freq_scale, + 3, + 0.4, + 2.0, + ) * 0.2; + let ridged = self.noise1.ridged_noise( + x_pos * self.freq_scale, + y_pos_mod * self.freq_scale - warp_offset, + z_pos * self.freq_scale, + 6, + 0.7, + 2.0, + 0.8, + ); + let fbm_val = self.noise1.noise_3d_fbm_initial_amp( + x_pos * self.freq_scale, + y_pos_mod * self.freq_scale - warp_offset, + z_pos * self.freq_scale, + 6, + 0.6, + 2.0, + 0.7, + ); + let combined_noise = fbm_val * (ridged + fbm_val); + + let val = ((clamp01(remap( + -8.0, + 8.0, + 0.0, + 1.0, + ridge_amplitude + height_multiplier * combined_noise * ridged + 0.5, + )) + 0.5) + .powf(1.5) + - curve_evaluate(latitude_factor * 0.9)) + * 2.0; + + let final_height = val.clamp(0.0, 2.0) * 1.1 - 0.2; + + self.radius + final_height + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo13.rs b/inserter/src/algorithm/data/planet_algorithms/algo13.rs new file mode 100644 index 0000000..900dbf8 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo13.rs @@ -0,0 +1,81 @@ +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm13 - Noise-based terrain with modX/modY and piecewise height shaping. +pub struct PlanetAlgorithm13 { + grid: &'static PlanetGrid, + radius: f64, + mod_y: f64, + noise: SimplexNoise, + freq_scale_x: f64, + freq_scale_y: f64, + freq_scale_z: f64, +} + +#[inline] +fn remap(src_min: f64, src_max: f64, tgt_min: f64, tgt_max: f64, x: f64) -> f64 { + (x - src_min) / (src_max - src_min) * (tgt_max - tgt_min) + tgt_min +} + +impl PlanetAlgorithm13 { + pub fn new(planet: &Planet) -> Self { + let mod_x = planet.get_mod_x(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + mod_y: planet.get_mod_y(), + noise: SimplexNoise::with_seed(DspRandom::new(planet.seed).next_seed()), + freq_scale_x: 0.007 * mod_x, + freq_scale_y: 0.007 * mod_x, + freq_scale_z: 0.007 * mod_x, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm13 { + fn get_height(&self, index: usize) -> f64 { + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let n = self.noise.noise_3d_fbm( + world_x * self.freq_scale_x, + world_y * self.freq_scale_y, + world_z * self.freq_scale_z, + 6, + 0.5, + 2.0, + ); + let mut raw_height = remap( + 0.0, + 2.0, + 0.0, + 4.0, + remap(-1.0, 1.0, 0.0, 1.0, n).powf(self.mod_y) * (49.0 / 16.0), + ); + + if raw_height < 1.0 { + raw_height = raw_height.powi(2); + } + + let clamped_height = (raw_height - 0.2).min(4.0); + + let final_height = if clamped_height > 2.0 { + if clamped_height <= 3.0 { + 2.0 - 1.0 * (clamped_height - 2.0) + } else if clamped_height <= 3.5 { + 1.0 + } else { + 1.0 + 2.0 * (clamped_height - 3.5) + } + } else { + clamped_height + }; + + self.radius + final_height + 0.1 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo14.rs b/inserter/src/algorithm/data/planet_algorithms/algo14.rs new file mode 100644 index 0000000..292a7cb --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo14.rs @@ -0,0 +1,169 @@ +use super::super::math::{levelize, levelize2, levelize4}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm14 - Lava terrain with domain warping, levelize shaping, and fluid dynamics. +pub struct PlanetAlgorithm14 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, + noise3: SimplexNoise, + noise4: SimplexNoise, +} + +impl PlanetAlgorithm14 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + let seed3 = rand.next_seed(); + let seed4 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + noise3: SimplexNoise::with_seed(seed3), + noise4: SimplexNoise::with_seed(seed4), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm14 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.007; + let freq_scale_y: f64 = 0.007; + let freq_scale_z: f64 = 0.007; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let leveled_x = levelize(world_x * 0.007 / 2.0, 1.0, 0.0); + let leveled_y = levelize(world_y * 0.007 / 2.0, 1.0, 0.0); + let leveled_z = levelize(world_z * 0.007 / 2.0, 1.0, 0.0); + + let xin = leveled_x + + self + .noise3 + .noise_3d(world_x * 0.05, world_y * 0.05, world_z * 0.05) + * 0.04; + let yin = leveled_y + + self + .noise3 + .noise_3d(world_y * 0.05, world_z * 0.05, world_x * 0.05) + * 0.04; + let zin = leveled_z + + self + .noise3 + .noise_3d(world_z * 0.05, world_x * 0.05, world_y * 0.05) + * 0.04; + + let crack_blend = (0.12 - self.noise4.noise_3d(xin, yin, zin).abs()) * 10.0; + let crack_clamped = if crack_blend > 0.0 { + if crack_blend > 1.0 { + 1.0 + } else { + crack_blend + } + } else { + 0.0 + }; + let crack_intensity = crack_clamped * crack_clamped; + let fluid_level = (self.noise3.noise_3d_fbm( + world_y * 0.005, + world_z * 0.005, + world_x * 0.005, + 4, + 0.5, + 2.0, + ) + 0.22) + * 5.0; + let fluid_clamped = if fluid_level > 0.0 { + if fluid_level > 1.0 { + 1.0 + } else { + fluid_level + } + } else { + 0.0 + }; + + let warped_x = world_x + (world_y * 0.15).sin() * 3.0; + let warped_y = world_y + (world_z * 0.15).sin() * 3.0; + let warped_z = world_z + (warped_x * 0.15).sin() * 3.0; + + let primary_noise = self.noise1.noise_3d_fbm( + warped_x * freq_scale_x * 1.0, + warped_y * freq_scale_y * 1.1, + warped_z * freq_scale_z * 1.0, + 6, + 0.5, + 1.8, + ); + let secondary_noise = self.noise2.noise_3d_fbm( + warped_x * freq_scale_x * 1.3 + 0.5, + warped_y * freq_scale_y * 2.8 + 0.2, + warped_z * freq_scale_z * 1.3 + 0.7, + 3, + 0.5, + 2.0, + ) * 2.0; + let reference_noise = self.noise2.noise_3d_fbm( + warped_x * freq_scale_x * 0.8, + warped_y * freq_scale_y * 0.8, + warped_z * freq_scale_z * 0.8, + 2, + 0.5, + 2.0, + ) * 2.0; + + let mut f = primary_noise * 2.0 + + 0.92 + + ((secondary_noise * (reference_noise + 0.5).abs() - 0.35) * 1.0).clamp(0.0, 1.0); + if f < 0.0 { + f = 0.0; + } + + let t = levelize2(f, 1.0, 0.0); + let terrain_base = if t > 0.0 { + let t2 = levelize2(f, 1.0, 0.0); + levelize4(t2, 1.0, 0.0) + } else { + t + }; + + let height_floor = 0.0; + let shaped_height = terrain_base; + + let mut combined_height = height_floor - crack_intensity * 1.2 * fluid_clamped; + if combined_height >= 0.0 { + combined_height = shaped_height; + } + + let mut final_height = combined_height - 0.1; + + let under_ground = -0.3 - final_height; + if under_ground > 0.0 { + let crater_noise = + self.noise2 + .noise_3d(warped_x * 0.16, warped_y * 0.16, warped_z * 0.16) + - 1.0; + let depth_clamped = if under_ground > 1.0 { + 1.0 + } else { + under_ground + }; + let depth_power = (3.0 - depth_clamped - depth_clamped) * depth_clamped * depth_clamped; + final_height = -0.3 - depth_power * 10.0 + + depth_power * depth_power * depth_power * depth_power * crater_noise * 0.5; + } + + self.radius + final_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo2.rs b/inserter/src/algorithm/data/planet_algorithms/algo2.rs new file mode 100644 index 0000000..1cfee3f --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo2.rs @@ -0,0 +1,72 @@ +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm2 - Two-layer FBM noise with frequency modulation from modX/modY. +pub struct PlanetAlgorithm2 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + scaled_freq_x: f64, + scaled_freq_y: f64, + scaled_freq_z: f64, +} + +impl PlanetAlgorithm2 { + pub fn new(planet: &Planet) -> Self { + let mod_x = planet.get_mod_x(); + let mod_y = planet.get_mod_y(); + + let mod_x_transformed = (3.0 - mod_x - mod_x) * mod_x * mod_x; + + let base_freq_x: f64 = 0.0035; + let base_freq_y: f64 = 0.025 * mod_x_transformed + 0.0035 * (1.0 - mod_x_transformed); + let base_freq_z: f64 = 0.0035; + let mod_y_scale: f64 = 1.0 + 1.3 * mod_y; + + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + scaled_freq_x: base_freq_x * mod_y_scale, + scaled_freq_y: base_freq_y * mod_y_scale, + scaled_freq_z: base_freq_z * mod_y_scale, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm2 { + fn get_height(&self, index: usize) -> f64 { + let noise_amplitude: f64 = 3.0; + + let v = self.grid.get_vertex(index); + let world_x: f64 = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let base_noise = self.noise1.noise_3d_fbm( + world_x * self.scaled_freq_x, + world_y * self.scaled_freq_y, + world_z * self.scaled_freq_z, + 6, + 0.45, + 1.8, + ); + + let shaping_factor = noise_amplitude; + let shaped_terrain = + 0.6 / ((base_noise * shaping_factor + shaping_factor * 0.4).abs() + 0.6) - 0.25; + let final_height = if shaped_terrain < 0.0 { + shaped_terrain * 0.3 + } else { + shaped_terrain + }; + + self.radius + final_height + 0.1 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo3.rs b/inserter/src/algorithm/data/planet_algorithms/algo3.rs new file mode 100644 index 0000000..529e181 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo3.rs @@ -0,0 +1,140 @@ +use super::super::math::{levelize2, levelize4}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +#[inline] +fn lerp_nc(a: f64, b: f64, t: f64) -> f64 { + a + (b - a) * t +} + +/// PlanetAlgorithm3 - Complex FBM noise with domain warping and multi-level shaping. +pub struct PlanetAlgorithm3 { + grid: &'static PlanetGrid, + radius: f64, + mod_x: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm3 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + mod_x: planet.get_mod_x(), + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm3 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.007; + let freq_scale_y: f64 = 0.007; + let freq_scale_z: f64 = 0.007; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let warped_x = world_x + (world_y * 0.15).sin() * 3.0; + let warped_y = world_y + (world_z * 0.15).sin() * 3.0; + let warped_z = world_z + (warped_x * 0.15).sin() * 3.0; + + let primary_noise = self.noise1.noise_3d_fbm( + warped_x * freq_scale_x * 1.0, + warped_y * freq_scale_y * 1.1, + warped_z * freq_scale_z * 1.0, + 6, + 0.5, + 1.8, + ); + + let secondary_noise = self.noise2.noise_3d_fbm( + warped_x * freq_scale_x * 1.3 + 0.5, + warped_y * freq_scale_y * 2.8 + 0.2, + warped_z * freq_scale_z * 1.3 + 0.7, + 3, + 0.5, + 2.0, + ) * 2.0; + + let detail_noise = self.noise2.noise_3d_fbm( + warped_x * freq_scale_x * 6.0, + warped_y * freq_scale_y * 12.0, + warped_z * freq_scale_z * 6.0, + 2, + 0.5, + 2.0, + ) * 2.0; + + let blended_detail = lerp_nc(detail_noise, detail_noise * 0.1, self.mod_x); + + let reference_noise = self.noise2.noise_3d_fbm( + warped_x * freq_scale_x * 0.8, + warped_y * freq_scale_y * 0.8, + warped_z * freq_scale_z * 0.8, + 2, + 0.5, + 2.0, + ) * 2.0; + + let mut f = primary_noise * 2.0 + + 0.92 + + ((secondary_noise * (reference_noise + 0.5).abs() - 0.35) * 1.0).clamp(0.0, 1.0); + + if f < 0.0 { + f *= 2.0; + } + + let mut t = levelize2(f, 1.0, 0.0); + if t > 0.0 { + let levelized_val = levelize2(f, 1.0, 0.0); + t = lerp_nc( + levelize4(levelized_val, 1.0, 0.0), + levelized_val, + self.mod_x, + ); + } + + let height_b = if t > 0.0 { + if t > 1.0 { + if t > 2.0 { + lerp_nc(1.2, 2.0, t - 2.0) + blended_detail * 0.12 + } else { + lerp_nc(0.3, 1.2, t - 1.0) + blended_detail * 0.12 + } + } else { + lerp_nc(0.0, 0.3, t) + blended_detail * 0.1 + } + } else { + lerp_nc(-1.0, 0.0, t + 1.0) + }; + + let height_a2 = if t > 0.0 { + if t > 1.0 { + if t > 2.0 { + lerp_nc(1.4, 2.7, t - 2.0) + blended_detail * 0.12 + } else { + lerp_nc(0.3, 1.4, t - 1.0) + blended_detail * 0.12 + } + } else { + lerp_nc(0.0, 0.3, t) + blended_detail * 0.1 + } + } else { + lerp_nc(-4.0, 0.0, t + 1.0) + }; + + let final_height = lerp_nc(height_a2, height_b, self.mod_x); + + self.radius + final_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo4.rs b/inserter/src/algorithm/data/planet_algorithms/algo4.rs new file mode 100644 index 0000000..169edce --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo4.rs @@ -0,0 +1,113 @@ +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::random_table::RandomTable; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm4 - FBM noise with 80 circular crater features. +pub struct PlanetAlgorithm4 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, + circles: Vec<([f64; 3], f64)>, + heights: Vec, +} + +impl PlanetAlgorithm4 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + + let mut seed3 = rand.next_seed(); + let mut cr = Vec::with_capacity(80); + let mut hs = Vec::with_capacity(80); + + for _ in 0..80 { + let mut v = RandomTable::spheric_normal(&mut seed3, 1.0); + let w = (v.magnitude() * 8.0 + 8.0) as f32; + v.normalize(); + v *= planet.radius as f64; + cr.push(([v.0, v.1, v.2], (w * w) as f64)); + + let h = rand.next_f64() * 0.4 + 0.2; + hs.push(h); + } + + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + circles: cr, + heights: hs, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm4 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.007; + let freq_scale_y: f64 = 0.007; + let freq_scale_z: f64 = 0.007; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let low_freq_noise = self.noise1.noise_3d_fbm( + world_x * freq_scale_x, + world_y * freq_scale_y, + world_z * freq_scale_z, + 4, + 0.45, + 1.8, + ); + let high_freq_noise = self.noise2.noise_3d_fbm( + world_x * freq_scale_x * 5.0, + world_y * freq_scale_y * 5.0, + world_z * freq_scale_z * 5.0, + 4, + 0.5, + 2.0, + ); + + let scaled_low = low_freq_noise * 1.5; + let scaled_high = high_freq_noise * 0.2; + let base_elevation = scaled_low * 0.08 + scaled_high * 2.0; + + let mut max_crater = 0.0; + for j in 0..80 { + let c = &self.circles[j]; + let dx = c.0[0] - world_x; + let dy = c.0[1] - world_y; + let dz = c.0[2] - world_z; + let dist_sq = dx * dx + dy * dy + dz * dz; + if dist_sq <= c.1 { + let mut t = dist_sq / c.1 + scaled_high * 1.2; + if t < 0.0 { + t = 0.0; + } + let t_sq = t * t; + let crater_shape = -15.0 * (t_sq * t) + (131.0 / 6.0) * t_sq - (113.0 / 15.0) * t + + 0.7 + + scaled_high; + let crater_shape = if crater_shape < 0.0 { + 0.0 + } else { + crater_shape + }; + let crater_val = crater_shape * crater_shape * self.heights[j]; + if crater_val > max_crater { + max_crater = crater_val; + } + } + } + + let final_height = max_crater + base_elevation + 0.2; + self.radius + final_height + 0.1 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo5.rs b/inserter/src/algorithm/data/planet_algorithms/algo5.rs new file mode 100644 index 0000000..524a9d4 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo5.rs @@ -0,0 +1,120 @@ +use super::super::math::levelize; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm5 - Complex noise with levelized coordinates and cell/crack patterns. +pub struct PlanetAlgorithm5 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm5 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm5 { + fn get_height(&self, index: usize) -> f64 { + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let height_base = 0.0; + let leveled_x = levelize(world_x * 0.007, 1.0, 0.0); + let leveled_y = levelize(world_y * 0.007, 1.0, 0.0); + let leveled_z = levelize(world_z * 0.007, 1.0, 0.0); + + let xin = leveled_x + + self + .noise1 + .noise_3d(world_x * 0.05, world_y * 0.05, world_z * 0.05) + * 0.04; + let yin = leveled_y + + self + .noise1 + .noise_3d(world_y * 0.05, world_z * 0.05, world_x * 0.05) + * 0.04; + let zin = leveled_z + + self + .noise1 + .noise_3d(world_z * 0.05, world_x * 0.05, world_y * 0.05) + * 0.04; + + let cell_noise = self.noise2.noise_3d(xin, yin, zin).abs(); + let crack_depth = (0.16 - cell_noise) * 10.0; + let crack_clamped = if crack_depth > 0.0 { + if crack_depth > 1.0 { + 1.0 + } else { + crack_depth + } + } else { + 0.0 + }; + let crack_intensity = crack_clamped * crack_clamped; + + let fluid_level = (self.noise1.noise_3d_fbm( + world_y * 0.005, + world_z * 0.005, + world_x * 0.005, + 4, + 0.5, + 2.0, + ) + 0.22) + * 5.0; + let fluid_clamped = if fluid_level > 0.0 { + if fluid_level > 1.0 { + 1.0 + } else { + fluid_level + } + } else { + 0.0 + }; + + let detail_noise = self + .noise2 + .noise_3d_fbm(xin * 1.5, yin * 1.5, zin * 1.5, 2, 0.5, 2.0) + .abs(); + + let mut terrain_height = height_base - crack_intensity * 1.2 * fluid_clamped; + if terrain_height >= 0.0 { + terrain_height += cell_noise * 0.25 + detail_noise * 0.6; + } + + let mut final_height = terrain_height - 0.1; + + let under_ground = -0.3 - final_height; + if under_ground > 0.0 { + let crack_noise = self + .noise2 + .noise_3d(world_x * 0.16, world_y * 0.16, world_z * 0.16) + - 1.0; + let depth_clamped = if under_ground > 1.0 { + 1.0 + } else { + under_ground + }; + let depth_power = (3.0 - depth_clamped - depth_clamped) * depth_clamped * depth_clamped; + final_height = -0.3 - depth_power * 3.7 + + depth_power * depth_power * depth_power * depth_power * crack_noise * 0.5; + } + + self.radius + final_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo6.rs b/inserter/src/algorithm/data/planet_algorithms/algo6.rs new file mode 100644 index 0000000..7aaf9eb --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo6.rs @@ -0,0 +1,137 @@ +use super::super::math::{levelize, levelize2}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm6 - Similar to algo5 but with different height/biomo formula. +pub struct PlanetAlgorithm6 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm6 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm6 { + fn get_height(&self, index: usize) -> f64 { + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let height_base = 0.0; + let leveled_x = levelize(world_x * 0.007, 1.0, 0.0); + let leveled_y = levelize(world_y * 0.007, 1.0, 0.0); + let leveled_z = levelize(world_z * 0.007, 1.0, 0.0); + + let xin = leveled_x + + self + .noise1 + .noise_3d(world_x * 0.05, world_y * 0.05, world_z * 0.05) + * 0.04; + let yin = leveled_y + + self + .noise1 + .noise_3d(world_y * 0.05, world_z * 0.05, world_x * 0.05) + * 0.04; + let zin = leveled_z + + self + .noise1 + .noise_3d(world_z * 0.05, world_x * 0.05, world_y * 0.05) + * 0.04; + + let cell_noise = self.noise2.noise_3d(xin, yin, zin).abs(); + let crack_depth = (0.16 - cell_noise) * 10.0; + let crack_clamped = if crack_depth > 0.0 { + if crack_depth > 1.0 { + 1.0 + } else { + crack_depth + } + } else { + 0.0 + }; + let crack_intensity = crack_clamped * crack_clamped; + + let fluid_level = (self.noise1.noise_3d_fbm( + world_y * 0.005, + world_z * 0.005, + world_x * 0.005, + 4, + 0.5, + 2.0, + ) + 0.22) + * 5.0; + let fluid_clamped = if fluid_level > 0.0 { + if fluid_level > 1.0 { + 1.0 + } else { + fluid_level + } + } else { + 0.0 + }; + + let detail_noise = self + .noise2 + .noise_3d_fbm(xin * 1.5, yin * 1.5, zin * 1.5, 2, 0.5, 2.0) + .abs(); + + let mut terrain_height = height_base - crack_intensity * 1.2 * fluid_clamped; + if terrain_height >= 0.0 { + terrain_height += cell_noise * 0.25 + detail_noise * 0.6; + } + + let mut final_height = terrain_height - 0.1; + + let under_ground = -0.3 - final_height; + if under_ground > 0.0 { + let depth_clamped = if under_ground > 1.0 { + 1.0 + } else { + under_ground + }; + final_height = + -0.3 - (3.0 - depth_clamped - depth_clamped) * depth_clamped * depth_clamped * 3.7; + } + + let floor_level = levelize2( + if crack_intensity > 0.3 { + crack_intensity + } else { + 0.3 + }, + 0.7, + 0.0, + ); + + let clamped_height = if final_height > -0.8 { + final_height + } else { + (-floor_level - cell_noise) * 0.9 + }; + + let result_height = if clamped_height > -1.2 { + clamped_height + } else { + -1.2 + }; + + self.radius + result_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo7.rs b/inserter/src/algorithm/data/planet_algorithms/algo7.rs new file mode 100644 index 0000000..1637232 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo7.rs @@ -0,0 +1,85 @@ +use super::super::math::{levelize2, levelize3}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm7 - Similar to algo1 but without +0.2 offset in height and different constants. +pub struct PlanetAlgorithm7 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm7 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm7 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.008; + let freq_scale_y: f64 = 0.01; + let freq_scale_z: f64 = 0.01; + let noise_amplitude: f64 = 3.0; + let noise_offset: f64 = -2.4; + let noise2_amplitude: f64 = 0.9; + let noise2_offset: f64 = 0.5; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let layer1_noise = self.noise1.noise_3d_fbm( + world_x * freq_scale_x, + world_y * freq_scale_y, + world_z * freq_scale_z, + 6, + 0.5, + 2.0, + ) * noise_amplitude + + noise_offset; + let layer2_noise = self.noise2.noise_3d_fbm( + world_x * (1.0 / 400.0), + world_y * (1.0 / 400.0), + world_z * (1.0 / 400.0), + 3, + 0.5, + 2.0, + ) * noise_amplitude + * noise2_amplitude + + noise2_offset; + + let clamped_layer2 = if layer2_noise > 0.0 { + layer2_noise * 0.5 + } else { + layer2_noise + }; + let combined_noise = layer1_noise + clamped_layer2; + let f = if combined_noise > 0.0 { + combined_noise * 0.5 + } else { + combined_noise * 1.6 + }; + + let shaped_height = if f > 0.0 { + levelize3(f, 0.7, 0.0) + } else { + levelize2(f, 0.5, 0.0) + }; + + self.radius + shaped_height + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo8.rs b/inserter/src/algorithm/data/planet_algorithms/algo8.rs new file mode 100644 index 0000000..eac0c58 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo8.rs @@ -0,0 +1,61 @@ +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm8 - Single noise layer with cosine-based terrain shaping. +pub struct PlanetAlgorithm8 { + grid: &'static PlanetGrid, + radius: f64, + noise: SimplexNoise, + freq_scale_x: f64, + freq_scale_y: f64, + freq_scale_z: f64, + mod_y: f64, +} + +impl PlanetAlgorithm8 { + pub fn new(planet: &Planet) -> Self { + let mod_x = planet.get_mod_x(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise: SimplexNoise::with_seed(DspRandom::new(planet.seed).next_seed()), + freq_scale_x: 0.002 * mod_x, + freq_scale_y: 0.002 * mod_x * mod_x * 6.66667, + freq_scale_z: 0.002 * mod_x, + mod_y: planet.get_mod_y(), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm8 { + fn get_height(&self, index: usize) -> f64 { + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let noise_val = (self.noise.noise_3d_fbm( + world_x * self.freq_scale_x, + world_y * self.freq_scale_y, + world_z * self.freq_scale_z, + 6, + 0.45, + 1.8, + ) + 1.0 + + self.mod_y * 0.01) + .clamp(0.0, 2.0); + + let shaped_height = if noise_val < 1.0 { + let f = (noise_val * std::f64::consts::PI).cos() * 1.1; + 1.0 - ((f.signum() * f.powi(4)).clamp(-1.0, 1.0) + 1.0) * 0.5 + } else { + let f = ((noise_val - 1.0) * std::f64::consts::PI).cos() * 1.1; + 2.0 - ((f.signum() * f.powi(4)).clamp(-1.0, 1.0) + 1.0) * 0.5 + }; + + self.radius + shaped_height + 0.1 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo9.rs b/inserter/src/algorithm/data/planet_algorithms/algo9.rs new file mode 100644 index 0000000..262758c --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo9.rs @@ -0,0 +1,135 @@ +use super::super::math::{levelize2, levelize3}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm9 - Complex multi-layer noise with modX/modY blending. +pub struct PlanetAlgorithm9 { + grid: &'static PlanetGrid, + radius: f64, + mod_x: f64, + mod_y: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm9 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + mod_x: planet.get_mod_x(), + mod_y: planet.get_mod_y(), + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm9 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.01; + let freq_scale_y: f64 = 0.012; + let freq_scale_z: f64 = 0.01; + let noise_amplitude: f64 = 3.0; + let noise_offset: f64 = -0.2; + let noise2_amplitude: f64 = 0.9; + let noise2_offset: f64 = 0.5; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let layer1_noise = self.noise1.noise_3d_fbm( + world_x * freq_scale_x * 0.75, + world_y * freq_scale_y * 0.5, + world_z * freq_scale_z * 0.75, + 6, + 0.5, + 2.0, + ) * noise_amplitude + + noise_offset; + let layer2_noise = self.noise2.noise_3d_fbm( + world_x * (1.0 / 400.0), + world_y * (1.0 / 400.0), + world_z * (1.0 / 400.0), + 3, + 0.5, + 2.0, + ) * noise_amplitude + * noise2_amplitude + + noise2_offset; + + let clamped_layer2 = if layer2_noise > 0.0 { + layer2_noise * 0.5 + } else { + layer2_noise + }; + let combined_noise = layer1_noise + clamped_layer2; + let f = if combined_noise > 0.0 { + combined_noise * 0.5 + } else { + combined_noise * 1.6 + }; + + let shaped_height = if f > 0.0 { + levelize3(f, 0.7, 0.0) + } else { + levelize2(f, 0.5, 0.0) + } + 0.618; + + let stretched_height = if shaped_height > -1.0 { + shaped_height * 1.5 + } else { + shaped_height * 4.0 + }; + + let layer3_noise = self.noise1.noise_3d_fbm( + world_x * freq_scale_x * self.mod_x, + world_y * freq_scale_y * self.mod_x, + world_z * freq_scale_z * self.mod_x, + 6, + 0.5, + 2.0, + ) * noise_amplitude + + noise_offset; + let layer4_noise = self.noise2.noise_3d_fbm( + world_x * (1.0 / 400.0), + world_y * (1.0 / 400.0), + world_z * (1.0 / 400.0), + 3, + 0.5, + 2.0, + ) * noise_amplitude + * noise2_amplitude + + noise2_offset; + let clamped_layer4 = if layer4_noise > 0.0 { + layer4_noise * 0.5 + } else { + layer4_noise + }; + + let alt_height = ((layer3_noise + clamped_layer4 + 5.0) * 0.13).powf(6.0) * 24.0 - 24.0; + + let blend_factor = if stretched_height >= -self.mod_y { + 0.0 + } else { + (((stretched_height + self.mod_y).abs() / 5.0).min(1.0)).powf(1.0) + }; + + let blended_height = stretched_height * (1.0 - blend_factor) + alt_height * blend_factor; + let final_height = if blended_height > 0.0 { + blended_height * 0.5 + } else { + blended_height + }; + + self.radius + final_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/mod.rs b/inserter/src/algorithm/data/planet_algorithms/mod.rs new file mode 100644 index 0000000..5966964 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/mod.rs @@ -0,0 +1,71 @@ +/// Planet algorithm implementations ported from PlanetAlgorithm0 through PlanetAlgorithm14. +/// Each algorithm lazily computes height data for a planet based on its seed and theme. +use super::planet::Planet; + +use algo0::PlanetAlgorithm0; +use algo1::PlanetAlgorithm1; +use algo10::PlanetAlgorithm10; +use algo11::PlanetAlgorithm11; +use algo12::PlanetAlgorithm12; +use algo13::PlanetAlgorithm13; +use algo14::PlanetAlgorithm14; +use algo2::PlanetAlgorithm2; +use algo3::PlanetAlgorithm3; +use algo4::PlanetAlgorithm4; +use algo5::PlanetAlgorithm5; +use algo6::PlanetAlgorithm6; +use algo7::PlanetAlgorithm7; +use algo8::PlanetAlgorithm8; +use algo9::PlanetAlgorithm9; + +mod algo0; +mod algo1; +mod algo10; +mod algo11; +mod algo12; +mod algo13; +mod algo14; +mod algo2; +mod algo3; +mod algo4; +mod algo5; +mod algo6; +mod algo7; +mod algo8; +mod algo9; + +/// Trait for planet algorithms. Each algorithm lazily computes height for individual vertices. +pub trait PlanetAlgorithm { + /// Compute the height for a single vertex index. + /// + /// # Arguments + /// * `index` - The vertex index (0..planet_raw_data::data_length()) + /// + /// # Returns + /// Height in game units (f64). + fn get_height(&self, index: usize) -> f64; +} + +/// Construct the algorithm matching the planet's algo ID. +/// Returns a boxed, fully-initialized algorithm ready for lazy height queries. +pub fn create_and_prepare_algo(planet: &Planet) -> Box { + let algo_id = planet.get_algo_id(); + match algo_id { + 0 => Box::new(PlanetAlgorithm0::new(planet)), + 1 => Box::new(PlanetAlgorithm1::new(planet)), + 2 => Box::new(PlanetAlgorithm2::new(planet)), + 3 => Box::new(PlanetAlgorithm3::new(planet)), + 4 => Box::new(PlanetAlgorithm4::new(planet)), + 5 => Box::new(PlanetAlgorithm5::new(planet)), + 6 => Box::new(PlanetAlgorithm6::new(planet)), + 7 => Box::new(PlanetAlgorithm7::new(planet)), + 8 => Box::new(PlanetAlgorithm8::new(planet)), + 9 => Box::new(PlanetAlgorithm9::new(planet)), + 10 => Box::new(PlanetAlgorithm10::new(planet)), + 11 => Box::new(PlanetAlgorithm11::new(planet)), + 12 => Box::new(PlanetAlgorithm12::new(planet)), + 13 => Box::new(PlanetAlgorithm13::new(planet)), + 14 => Box::new(PlanetAlgorithm14::new(planet)), + _ => panic!("Unknown planet algorithm ID: {}", algo_id), + } +} diff --git a/inserter/src/algorithm/data/planet_grid.rs b/inserter/src/algorithm/data/planet_grid.rs new file mode 100644 index 0000000..b7d28b9 --- /dev/null +++ b/inserter/src/algorithm/data/planet_grid.rs @@ -0,0 +1,166 @@ +use super::vector_f3::VectorF3; +use std::sync::OnceLock; + +// --------------------------------------------------------------------------- +// Constants – hardcoded for precision = 200 +// --------------------------------------------------------------------------- +pub const PRECISION: usize = 200; +pub const DATA_LENGTH: usize = (PRECISION + 1) * (PRECISION + 1) * 4; // 161604 +pub const STRIDE: i32 = ((PRECISION + 1) * 2) as i32; // 402 +const SUBSTRIDE: usize = PRECISION + 1; // 201 +const INDEX_MAP_PRECISION: usize = PRECISION >> 2; // 50 +const INDEX_MAP_FACE_STRIDE: usize = INDEX_MAP_PRECISION * INDEX_MAP_PRECISION; // 2500 +const INDEX_MAP_CORNER_STRIDE: usize = INDEX_MAP_FACE_STRIDE * 3; // 7500 +const INDEX_MAP_DATA_LENGTH: usize = INDEX_MAP_CORNER_STRIDE * 8; // 60000 + +// --------------------------------------------------------------------------- +// Pole constants (C# public static Vector3[] poles) +// --------------------------------------------------------------------------- +static POLES: [VectorF3; 6] = [ + VectorF3::right(), + VectorF3::left(), + VectorF3::up(), + VectorF3::down(), + VectorF3::forward(), + VectorF3::back(), +]; + +pub struct PlanetGrid { + pub vertices: Vec, + pub index_map: Vec, +} + +impl PlanetGrid { + pub fn get_vertex(&self, index: usize) -> &VectorF3 { + unsafe { &self.vertices.get_unchecked(index) } + } +} + +static PLANET_GRID: OnceLock = OnceLock::new(); + +pub fn get_planet_grid() -> &'static PlanetGrid { + PLANET_GRID.get_or_init(|| { + let mut vertices = vec![VectorF3::zero(); DATA_LENGTH]; + let mut index_map = vec![-1i32; INDEX_MAP_DATA_LENGTH]; + + for i in 0..DATA_LENGTH { + // C#: int num3 = index1 % num1; (num1 = stride) + let n3 = i % STRIDE as usize; + // C#: int num4 = index1 / num1; + let n4 = i / STRIDE as usize; + // C#: int num5 = num3 % num2; (num2 = substride) + let n5 = n3 % SUBSTRIDE; + // C#: int num6 = num4 % num2; + let n6 = n4 % SUBSTRIDE; + + // C#: int num7 = ((num3 >= num2 ? 1 : 0) + (num4 >= num2 ? 1 : 0) * 2) * 2 + // + (num5 >= num6 ? 0 : 1); + let n7 = (((n3 >= SUBSTRIDE) as usize) + ((n4 >= SUBSTRIDE) as usize) * 2) * 2 + + if n5 >= n6 { 0 } else { 1 }; + + let n8: f32 = if n5 >= n6 { + (PRECISION - n5) as f32 + } else { + n5 as f32 + }; + let n9: f32 = if n5 >= n6 { + n6 as f32 + } else { + (PRECISION - n6) as f32 + }; + + let n10: f32 = PRECISION as f32 - n9; + let t1: f32 = n9 / PRECISION as f32; + let t2: f32 = if n10 > 0.0f32 { n8 / n10 } else { 0.0f32 }; + + let (pole1, pole2, pole3, corner): (&VectorF3, &VectorF3, &VectorF3, usize) = match n7 { + 0 => (&POLES[2], &POLES[0], &POLES[4], 7), + 1 => (&POLES[3], &POLES[4], &POLES[0], 5), + 2 => (&POLES[2], &POLES[4], &POLES[1], 6), + 3 => (&POLES[3], &POLES[1], &POLES[4], 4), + 4 => (&POLES[2], &POLES[1], &POLES[5], 2), + 5 => (&POLES[3], &POLES[5], &POLES[1], 0), + 6 => (&POLES[2], &POLES[5], &POLES[0], 3), + 7 => (&POLES[3], &POLES[0], &POLES[5], 1), + _ => (&POLES[2], &POLES[0], &POLES[4], 7), + }; + + let slerp_a = VectorF3::slerp(pole1, pole3, t1); + let slerp_b = VectorF3::slerp(pole2, pole3, t1); + let vert = VectorF3::slerp(&slerp_a, &slerp_b, t2); + + // C#: int index2 = this.PositionHash(this.vertices[index1], corner); + let idx2 = position_hash(&vert, corner); + if index_map[idx2] == -1 { + index_map[idx2] = i as i32; + } + + vertices[i] = vert; + } + + // C#: forward-fill empty slots + for i in 1..INDEX_MAP_DATA_LENGTH { + if index_map[i] == -1 { + index_map[i] = index_map[i - 1]; + } + } + + PlanetGrid { + vertices, + index_map, + } + }) +} + +// --------------------------------------------------------------------------- +// trans (C# lines 433–439) +// --------------------------------------------------------------------------- +fn trans(x: f32, pr: usize) -> usize { + let num = ((x as f64 + 0.23f64).sqrt() - 0.47958320379257204f64) / 0.62947052717208862f64 + * (pr as f64); + let idx = num as usize; + if idx >= pr { + pr - 1 + } else { + idx + } +} + +// --------------------------------------------------------------------------- +// position_hash (internal) (C# lines 441–475) +// --------------------------------------------------------------------------- +pub fn position_hash(v: &VectorF3, corner: usize) -> usize { + let corner = if corner == 0 { + ((v.0 > 0.0) as usize) + ((v.1 > 0.0) as usize) * 2 + ((v.2 > 0.0) as usize) * 4 + } else { + corner + }; + + let vx = if v.0 < 0.0 { -v.0 } else { v.0 }; + let vy = if v.1 < 0.0 { -v.1 } else { v.1 }; + let vz = if v.2 < 0.0 { -v.2 } else { v.2 }; + + if vx < 1e-6 && vy < 1e-6 && vz < 1e-6 { + return 0; + } + + let n1: usize; + let n2: usize; + let n3: usize; + + if vx >= vy && vx >= vz { + n1 = 0; + n2 = trans((vz / vx) as f32, INDEX_MAP_PRECISION); + n3 = trans((vy / vx) as f32, INDEX_MAP_PRECISION); + } else if vy >= vx && vy >= vz { + n1 = 1; + n2 = trans((vx / vy) as f32, INDEX_MAP_PRECISION); + n3 = trans((vz / vy) as f32, INDEX_MAP_PRECISION); + } else { + n1 = 2; + n2 = trans((vx / vz) as f32, INDEX_MAP_PRECISION); + n3 = trans((vy / vz) as f32, INDEX_MAP_PRECISION); + } + + n2 + n3 * INDEX_MAP_PRECISION + n1 * INDEX_MAP_FACE_STRIDE + corner * INDEX_MAP_CORNER_STRIDE +} diff --git a/inserter/src/algorithm/data/planet_raw_data.rs b/inserter/src/algorithm/data/planet_raw_data.rs new file mode 100644 index 0000000..9f69138 --- /dev/null +++ b/inserter/src/algorithm/data/planet_raw_data.rs @@ -0,0 +1,75 @@ +use crate::algorithm::data::planet::Planet; +use crate::algorithm::data::planet_algorithms::{create_and_prepare_algo, PlanetAlgorithm}; +use crate::algorithm::data::planet_grid::{ + get_planet_grid, position_hash, PlanetGrid, DATA_LENGTH, PRECISION, STRIDE, +}; + +use super::vector_f3::VectorF3; +use std::f64::consts::PI; + +pub struct PlanetRawData { + grid: &'static PlanetGrid, + algo: Box, + cache: Vec, +} + +impl PlanetRawData { + pub fn new(planet: &Planet) -> Self { + Self { + grid: get_planet_grid(), + algo: create_and_prepare_algo(planet), + cache: vec![f32::NAN; DATA_LENGTH], + } + } + + #[inline] + fn get_height(&mut self, index: usize) -> f32 { + let val = self.cache[index]; + if val.is_nan() { + let h = (self.algo.get_height(index) * 100.0) as u16 as f32; + self.cache[index] = h; + h + } else { + val + } + } + + pub fn query_height_normalized(&mut self, vpos_normalized: &VectorF3) -> f32 { + let index1 = self.grid.index_map[position_hash(vpos_normalized, 0)]; + + let num1: f64 = (PI / (PRECISION as f64 * 2.0)) * 1.2_f64; + let num2: f64 = num1 * num1; + + let mut num3: f32 = 0.0f32; + let mut num4: f32 = 0.0f32; + + for i3 in -1..=3 { + let i4 = index1 + i3 * STRIDE; + for i2 in -1_i32..=3 { + let idx4 = (i4 + i2) as usize; + if idx4 < DATA_LENGTH { + let sqr_mag = self.grid.vertices[idx4].distance_sq_from(vpos_normalized); + if (sqr_mag as f64) <= num2 { + let num5 = 1.0f32 - (sqr_mag.sqrt() / num1 as f32); + let num6 = self.get_height(idx4); + num3 += num5; + num4 += num6 * num5; + } + } + } + } + + if num3 != 0.0f32 { + num4 / num3 * 0.01 + } else { + self.get_height(0) * 0.01 + } + } + + #[inline] + pub fn query_height(&mut self, vpos: &VectorF3) -> f32 { + let mut vpos = *vpos; + vpos.normalize(); + self.query_height_normalized(&vpos) + } +} diff --git a/inserter/src/algorithm/data/pose.rs b/inserter/src/algorithm/data/pose.rs new file mode 100644 index 0000000..55b2985 --- /dev/null +++ b/inserter/src/algorithm/data/pose.rs @@ -0,0 +1,16 @@ +use super::quaternion::Quaternion; +use super::vector_f3::VectorF3; + +/// Port of Unity's `Pose(Vector3 position, Quaternion rotation)`. +/// Uses VectorF3 for position (f32-based, matching VectorLF3 → VectorF3 mapping). +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct Pose { + pub position: VectorF3, + pub rotation: Quaternion, +} + +impl Pose { + pub fn new(position: VectorF3, rotation: Quaternion) -> Self { + Self { position, rotation } + } +} diff --git a/inserter/src/algorithm/data/quaternion.rs b/inserter/src/algorithm/data/quaternion.rs new file mode 100644 index 0000000..a250ab6 --- /dev/null +++ b/inserter/src/algorithm/data/quaternion.rs @@ -0,0 +1,209 @@ +use super::vector_f3::VectorF3; +use std::ops::Mul; + +/// Port of Unity's Quaternion (x, y, z, w) using f32. +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct Quaternion { + pub x: f32, + pub y: f32, + pub z: f32, + pub w: f32, +} + +impl Quaternion { + pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { + Self { x, y, z, w } + } + + pub fn identity() -> Self { + Self { + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + } + } + + /// Port of Unity's `Quaternion.AngleAxis(angle_degrees, axis)`. + /// `angle_degrees` is in degrees (same as Unity's AngleAxis). + pub fn angle_axis(angle_degrees: f32, axis: &VectorF3) -> Self { + let half_rad = (angle_degrees * std::f32::consts::PI / 180.0) * 0.5; + let s = half_rad.sin(); + let axis_norm = axis.normalized(); + Self { + x: axis_norm.0 * s, + y: axis_norm.1 * s, + z: axis_norm.2 * s, + w: half_rad.cos(), + } + } + + /// Port of `Maths.QInvRotateLF(Quaternion, VectorLF3)` — inverse rotates a VectorF3. + /// Since the user specified VectorLF3 maps to VectorF3 in Rust, this uses f32. + pub fn q_inv_rotate_lf(&self, v: &VectorF3) -> VectorF3 { + let vx = v.0 * 2.0; + let vy = v.1 * 2.0; + let vz = v.2 * 2.0; + let num1 = self.w * self.w - 0.5; + let num2 = self.x * vx + self.y * vy + self.z * vz; + VectorF3( + vx * num1 - (self.y * vz - self.z * vy) * self.w + self.x * num2, + vy * num1 - (self.z * vx - self.x * vz) * self.w + self.y * num2, + vz * num1 - (self.x * vy - self.y * vx) * self.w + self.z * num2, + ) + } + + /// Port of Unity's `Quaternion.FromToRotation(fromDirection, toDirection)`. + /// + /// Creates a rotation that rotates `from` to align with `to`. + pub fn from_to_rotation(from: &VectorF3, to: &VectorF3) -> Self { + let from_mag_sq = from.magnitude_sq(); + let to_mag_sq = to.magnitude_sq(); + if from_mag_sq < 1e-10 || to_mag_sq < 1e-10 { + return Self::identity(); + } + + let from_norm = from.normalized(); + let to_norm = to.normalized(); + + let dot = VectorF3::dot(&from_norm, &to_norm); + + // Vectors are nearly opposite — 180° rotation around an orthogonal axis + if dot < -0.999999 { + // Find an orthogonal axis to rotate around + let abs_x = from_norm.0.abs(); + let abs_y = from_norm.1.abs(); + let abs_z = from_norm.2.abs(); + + let axis = if abs_x < abs_y && abs_x < abs_z { + VectorF3::cross(&from_norm, &VectorF3(1.0, 0.0, 0.0)) + } else if abs_y < abs_z { + VectorF3::cross(&from_norm, &VectorF3(0.0, 1.0, 0.0)) + } else { + VectorF3::cross(&from_norm, &VectorF3(0.0, 0.0, 1.0)) + }; + + let axis_norm = axis.normalized(); + Self { + x: axis_norm.0, + y: axis_norm.1, + z: axis_norm.2, + w: 0.0, + } + } else { + // Standard case: rotation axis = cross(from, to), angle = acos(dot) + let cross = VectorF3::cross(&from_norm, &to_norm); + let s = ((1.0 + dot) * 2.0).sqrt(); + let inv_s = 1.0 / s; + let q = Self { + x: cross.0 * inv_s, + y: cross.1 * inv_s, + z: cross.2 * inv_s, + w: s * 0.5, + }; + // Normalize + let mag = (q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w).sqrt(); + Self { + x: q.x / mag, + y: q.y / mag, + z: q.z / mag, + w: q.w / mag, + } + } + } + + /// Port of `Maths.QRotateLF(Quaternion, VectorLF3)` — rotates a VectorF3. + pub fn q_rotate_lf(&self, v: &VectorF3) -> VectorF3 { + let vx = v.0 * 2.0; + let vy = v.1 * 2.0; + let vz = v.2 * 2.0; + let num1 = self.w * self.w - 0.5; + let num2 = self.x * vx + self.y * vy + self.z * vz; + VectorF3( + vx * num1 + (self.y * vz - self.z * vy) * self.w + self.x * num2, + vy * num1 + (self.z * vx - self.x * vz) * self.w + self.y * num2, + vz * num1 + (self.x * vy - self.y * vx) * self.w + self.z * num2, + ) + } +} + +/// Port of Unity's `Quaternion * Vector3` — rotates a VectorF3 by a Quaternion. +/// +/// Uses the full 3×3 rotation matrix derived from the quaternion, matching Unity's +/// internal implementation (which is Fused in IL2CPP builds). +impl Mul<&VectorF3> for &Quaternion { + type Output = VectorF3; + + fn mul(self, point: &VectorF3) -> VectorF3 { + let num1 = self.x * 2.0; + let num2 = self.y * 2.0; + let num3 = self.z * 2.0; + let num4 = self.x * num1; + let num5 = self.y * num2; + let num6 = self.z * num3; + let num7 = self.x * num2; + let num8 = self.x * num3; + let num9 = self.y * num3; + let num10 = self.w * num1; + let num11 = self.w * num2; + let num12 = self.w * num3; + VectorF3( + (1.0 - (num5 + num6)) * point.0 + (num7 - num12) * point.1 + (num8 + num11) * point.2, + (num7 + num12) * point.0 + (1.0 - (num4 + num6)) * point.1 + (num9 - num10) * point.2, + (num8 - num11) * point.0 + (num9 + num10) * point.1 + (1.0 - (num4 + num5)) * point.2, + ) + } +} + +/// Port of `Maths.QMultiply` for quaternion composition (equivalent to Unity's `q1 * q2`). +impl Mul for Quaternion { + type Output = Quaternion; + + fn mul(self, rhs: Quaternion) -> Quaternion { + Quaternion { + x: self.x * rhs.w + self.y * rhs.z - self.z * rhs.y + self.w * rhs.x, + y: -self.x * rhs.z + self.y * rhs.w + self.z * rhs.x + self.w * rhs.y, + z: self.x * rhs.y - self.y * rhs.x + self.z * rhs.w + self.w * rhs.z, + w: -self.x * rhs.x - self.y * rhs.y - self.z * rhs.z + self.w * rhs.w, + } + } +} + +impl Mul<&Quaternion> for Quaternion { + type Output = Quaternion; + + fn mul(self, rhs: &Quaternion) -> Quaternion { + Quaternion { + x: self.x * rhs.w + self.y * rhs.z - self.z * rhs.y + self.w * rhs.x, + y: -self.x * rhs.z + self.y * rhs.w + self.z * rhs.x + self.w * rhs.y, + z: self.x * rhs.y - self.y * rhs.x + self.z * rhs.w + self.w * rhs.z, + w: -self.x * rhs.x - self.y * rhs.y - self.z * rhs.z + self.w * rhs.w, + } + } +} + +impl Mul for &Quaternion { + type Output = Quaternion; + + fn mul(self, rhs: Quaternion) -> Quaternion { + Quaternion { + x: self.x * rhs.w + self.y * rhs.z - self.z * rhs.y + self.w * rhs.x, + y: -self.x * rhs.z + self.y * rhs.w + self.z * rhs.x + self.w * rhs.y, + z: self.x * rhs.y - self.y * rhs.x + self.z * rhs.w + self.w * rhs.z, + w: -self.x * rhs.x - self.y * rhs.y - self.z * rhs.z + self.w * rhs.w, + } + } +} + +impl Mul<&Quaternion> for &Quaternion { + type Output = Quaternion; + + fn mul(self, rhs: &Quaternion) -> Quaternion { + Quaternion { + x: self.x * rhs.w + self.y * rhs.z - self.z * rhs.y + self.w * rhs.x, + y: -self.x * rhs.z + self.y * rhs.w + self.z * rhs.x + self.w * rhs.y, + z: self.x * rhs.y - self.y * rhs.x + self.z * rhs.w + self.w * rhs.z, + w: -self.x * rhs.x - self.y * rhs.y - self.z * rhs.z + self.w * rhs.w, + } + } +} diff --git a/inserter/src/algorithm/data/random.rs b/inserter/src/algorithm/data/random.rs index 6a7aac2..34343dd 100644 --- a/inserter/src/algorithm/data/random.rs +++ b/inserter/src/algorithm/data/random.rs @@ -1,3 +1,4 @@ +#[derive(Debug)] pub struct DspRandom { inext: usize, inextp: usize, @@ -7,26 +8,44 @@ pub struct DspRandom { impl DspRandom { pub fn new(seed: i32) -> Self { let mut seed_array = [0; 56]; - let mut num1 = 161803398_i32.wrapping_sub(seed.wrapping_abs()); + let mut num1 = 161803398 - seed.abs(); seed_array[55] = num1; let mut num2 = 1; - for index1 in 1..55 { - let index2 = (21 * index1) % 55; + let mut index2 = 0; + for _ in 1..55 { + index2 += 21; + if index2 >= 55 { + index2 -= 55; + } seed_array[index2] = num2; - num2 = num1.wrapping_sub(num2); + num2 = num1 - num2; if num2 < 0 { num2 += i32::MAX; } num1 = seed_array[index2] } - for _index3 in 1..5 { - for index4 in 1..56 { - let mut val = seed_array[index4].wrapping_sub(seed_array[1 + (index4 + 30) % 55]); - if val < 0 { - val += i32::MAX; - } - seed_array[index4] = val; + + let ptr = seed_array.as_mut_ptr(); + + let (chunk1_lhs, chunk1_rhs, chunk2_lhs, chunk2_rhs) = unsafe { + ( + &mut *ptr.add(1).cast::<[i32; 24]>(), + &*ptr.add(32).cast::<[i32; 24]>(), + &mut *ptr.add(25).cast::<[i32; 31]>(), + &*ptr.add(1).cast::<[i32; 31]>(), + ) + }; + + let update = |(lhs, rhs): (&mut i32, &i32)| { + *lhs = lhs.wrapping_sub(*rhs); + if lhs.is_negative() { + *lhs += i32::MAX; } + }; + + for _ in 1..5 { + chunk1_lhs.iter_mut().zip(chunk1_rhs).for_each(update); + chunk2_lhs.iter_mut().zip(chunk2_rhs).for_each(update); } Self { @@ -36,6 +55,17 @@ impl DspRandom { } } + pub fn new_system_random(seed: i32) -> Self { + // Somehow System.Random mistyped inextp + // https://github.com/dotnet/runtime/issues/23198 + let r = Self::new(seed); + Self { + inext: 0, + inextp: 21, + seed_array: r.seed_array, + } + } + fn sample(&mut self) -> f64 { self.inext += 1; if self.inext >= 56 { @@ -45,7 +75,7 @@ impl DspRandom { if self.inextp >= 56 { self.inextp = 1 } - let mut num = self.seed_array[self.inext].wrapping_sub(self.seed_array[self.inextp]); + let mut num = self.seed_array[self.inext] - self.seed_array[self.inextp]; if num < 0 { num += i32::MAX; } @@ -65,7 +95,11 @@ impl DspRandom { #[inline] pub fn next_i32(&mut self, max_value: i32) -> i32 { - (self.sample() * (max_value as f64)) as i32 + if max_value <= 1 { + 0 + } else { + (self.sample() * (max_value as f64)) as i32 + } } #[inline] diff --git a/inserter/src/algorithm/data/random_table.rs b/inserter/src/algorithm/data/random_table.rs new file mode 100644 index 0000000..e98cdcf --- /dev/null +++ b/inserter/src/algorithm/data/random_table.rs @@ -0,0 +1,63 @@ +use std::sync::LazyLock; + +use super::random::DspRandom; +use crate::algorithm::data::vector3::Vector3; + +const TABLE_SIZE: usize = 65536; + +/// Pre-generated random table matching C# `RandomTable`. +/// `SphericNormal` generates normally-distributed 3D points (Box-Muller + rejection sampling) +/// for use in planet terrain algorithms (algo4, algo10, etc.). +pub struct RandomTable { + spheric_normal: Box<[Vector3; TABLE_SIZE]>, +} + +static RANDOM_TABLE: LazyLock = LazyLock::new(|| RandomTable::generate()); + +/// Box-Muller transform matching C# `RandomTable.Normal(System.Random rand)`. +/// Returns a standard normally-distributed value N(0,1). +fn normal(rand: &mut DspRandom) -> f64 { + let num = rand.next_f64(); + let a = rand.next_f64() * std::f64::consts::PI * 2.0; + (-2.0 * (1.0 - num).ln()).sqrt() * a.sin() +} + +impl RandomTable { + /// Generates the pre-computed spheric_normal table using DspRandom with seed 1001, + /// exactly matching C# `RandomTable.GenerateSphericNormal()`. + fn generate() -> Self { + let mut rand = DspRandom::new_system_random(1001); + let mut spheric_normal = Box::new([Vector3(0.0, 0.0, 0.0); TABLE_SIZE]); + + for entry in spheric_normal.iter_mut() { + let result = loop { + let n1 = rand.next_f64() * 2.0 - 1.0; + let n2 = rand.next_f64() * 2.0 - 1.0; + let n3 = rand.next_f64() * 2.0 - 1.0; + let n4 = normal(&mut rand); + + if n4 <= 5.0 && n4 >= -5.0 { + let d = n1 * n1 + n2 * n2 + n3 * n3; + if d <= 1.0 && d >= 1e-6 { + let num5 = n4 / d.sqrt(); + break (n1 * num5, n2 * num5, n3 * num5); + } + } + }; + + *entry = Vector3(result.0, result.1, result.2); + } + + Self { spheric_normal } + } + + /// Returns a normally-distributed 3D point scaled by `scale`, advancing the seed + /// as an index into the pre-generated table (matching C# `RandomTable.SphericNormal`). + /// + /// The `seed` is incremented and masked to 16 bits (0..65535) on each call. + pub fn spheric_normal(seed: &mut i32, scale: f64) -> Vector3 { + *seed = seed.wrapping_add(1) & 0xFFFF; + let v = RANDOM_TABLE.spheric_normal[*seed as usize]; + v * scale + } +} diff --git a/inserter/src/algorithm/data/rule.rs b/inserter/src/algorithm/data/rule.rs new file mode 100644 index 0000000..4624951 --- /dev/null +++ b/inserter/src/algorithm/data/rule.rs @@ -0,0 +1,141 @@ +use super::galaxy::Galaxy; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(tag = "type", content = "value")] +pub enum Condition { + Eq(f32), + Neq(f32), + Lt(f32), + Lte(f32), + Gt(f32), + Gte(f32), + Between(f32, f32), + NotBetween(f32, f32), +} + +impl Condition { + pub fn eval(&self, value: f32) -> bool { + match self { + Condition::Eq(f) => value == *f, + Condition::Neq(f) => value != *f, + Condition::Lt(f) => value < *f, + Condition::Lte(f) => value <= *f, + Condition::Gt(f) => value > *f, + Condition::Gte(f) => value >= *f, + Condition::Between(f1, f2) => *f1 <= value && value <= *f2, + Condition::NotBetween(f1, f2) => *f1 > value || value > *f2, + } + } +} + +#[macro_export] +macro_rules! evaluate_safe { + ($galaxy:expr, $evaluation:expr, |$sp:ident| $logic:expr) => {{ + let mut result: u64 = 0; + for (index, $sp) in $galaxy.stars.iter().take($evaluation.get_len()).enumerate() { + if $evaluation.is_known(index) { + continue; + } + if $logic { + result |= 1 << index; + } + } + result + }}; +} + +#[macro_export] +macro_rules! evaluate_unsafe { + ($galaxy:expr, $evaluation:expr, |$sp:ident| $logic:expr) => {{ + let mut result: u64 = 0; + for (index, $sp) in $galaxy.stars.iter().take($evaluation.get_len()).enumerate() { + if $evaluation.is_known(index) { + if !$sp.is_safe() { + $sp.load_planets() + } + continue; + } + + if $logic { + result |= 1 << index; + } + $sp.mark_safe(); + } + result + }}; +} + +#[allow(unused_variables)] +pub trait Rule { + fn get_priority(&self) -> i32 { + 0 + } + + fn evaluate(&self, galaxy: &Galaxy, evaluation: &Evaluation) -> u64 { + 0 + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Evaluation { + value: u64, + unknown: u64, + max_len: usize, +} + +impl Evaluation { + pub fn new(size: usize) -> Self { + Self { + value: 0, + unknown: u64::MAX >> (u64::BITS - size as u32), + max_len: size, + } + } + + #[inline] + pub fn is_known(&self, index: usize) -> bool { + (self.unknown & (1 << index)) == 0 + } + + /// Returns the minium number of stars that still needs to be evaluated + /// Useful for unsafe rules + #[inline] + pub fn get_len(&self) -> usize { + self.max_len + } + + #[inline] + fn load_max_len(&mut self) { + self.max_len = (u64::BITS - self.unknown.leading_zeros()) as usize; + } + + #[inline] + pub fn accept_many(&mut self, indices: u64) { + self.value |= self.unknown & indices; + self.unknown &= !indices; + self.load_max_len(); + } + + #[inline] + pub fn reject_others(&mut self, indices: u64) { + self.value &= !self.unknown | indices; + self.unknown &= indices; + self.load_max_len(); + } + + #[inline] + pub fn collect_known(&self) -> u64 { + !self.unknown & self.value + } + + #[inline] + pub fn collect_unknown(&self) -> u64 { + self.unknown | self.value + } + + #[inline] + pub fn is_done(&self) -> bool { + self.max_len == 0 + } +} diff --git a/inserter/src/algorithm/data/simplex_noise.rs b/inserter/src/algorithm/data/simplex_noise.rs new file mode 100644 index 0000000..41913a0 --- /dev/null +++ b/inserter/src/algorithm/data/simplex_noise.rs @@ -0,0 +1,253 @@ +/// Port of SimplexNoise from DSP game (SimpleNoise.cs) +/// Uses DspRandom for seed-based permutation shuffling. +use super::random::DspRandom; + +// ---- Static gradient tables (constant) ---- + +const GRAD3: [[f64; 3]; 12] = [ + [1.0, 1.0, 0.0], + [-1.0, 1.0, 0.0], + [1.0, -1.0, 0.0], + [-1.0, -1.0, 0.0], + [1.0, 0.0, 1.0], + [-1.0, 0.0, 1.0], + [1.0, 0.0, -1.0], + [-1.0, 0.0, -1.0], + [0.0, 1.0, 1.0], + [0.0, -1.0, 1.0], + [0.0, 1.0, -1.0], + [0.0, -1.0, -1.0], +]; + +// ---- Constants ---- +const F3: f64 = 1.0 / 3.0; +const G3: f64 = 1.0 / 6.0; + +/// SimplexNoise implementation with DSP-specific permutation. +pub struct SimplexNoise { + perm: [i16; 512], + perm_mod12: [i16; 512], +} + +impl SimplexNoise { + /// Create with seed-shuffled permutation. + pub fn with_seed(seed: i32) -> Self { + let mut noise = Self { + perm: [0; 512], + perm_mod12: [0; 512], + }; + noise.init_member_seed(seed); + noise + } + + /// Initialize with seed-shuffled permutation (using DspRandom). + fn init_member_seed(&mut self, seed: i32) { + let mut p: [i16; 256] = std::array::from_fn(|i| i as i16); + + // Only shuffle when seed != 0 (matching C# behavior where seed 0 might be special) + // Actually the C# always shuffles. Let's shuffle based on seed. + let mut rand = DspRandom::new(seed); + for i1 in 0..256 { + let i2 = rand.next_i32(256) as usize; + let num = p[i1]; + p[i1] = p[i2]; + p[i2] = num; + } + + for i in 0..512 { + self.perm[i] = p[i & 255]; + self.perm_mod12[i] = (self.perm[i] % 12) as i16; + } + } + + /// 3D simplex noise. + pub fn noise_3d(&self, xin: f64, yin: f64, zin: f64) -> f64 { + let num1 = (xin + yin + zin) * F3; + let num2 = fastfloor(xin + num1); + let num3 = fastfloor(yin + num1); + let num4 = fastfloor(zin + num1); + let num5 = (num2 + num3 + num4) as f64 * G3; + let num6 = num2 as f64 - num5; + let num7 = num3 as f64 - num5; + let num8 = num4 as f64 - num5; + let x1 = xin - num6; + let y1 = yin - num7; + let z1 = zin - num8; + + let (n9, n10, n11, n12, n13, n14) = if x1 >= y1 { + if y1 >= z1 { + (1, 0, 0, 1, 1, 0) + } else if x1 >= z1 { + (1, 0, 0, 1, 0, 1) + } else { + (0, 0, 1, 1, 0, 1) + } + } else if y1 < z1 { + (0, 0, 1, 0, 1, 1) + } else if x1 < z1 { + (0, 1, 0, 0, 1, 1) + } else { + (0, 1, 0, 1, 1, 0) + }; + + let x2 = x1 - n9 as f64 + G3; + let y2 = y1 - n10 as f64 + G3; + let z2 = z1 - n11 as f64 + G3; + let x3 = x1 - n12 as f64 + 2.0 * G3; + let y3 = y1 - n13 as f64 + 2.0 * G3; + let z3 = z1 - n14 as f64 + 2.0 * G3; + let x4 = x1 - 1.0 + 3.0 * G3; + let y4 = y1 - 1.0 + 3.0 * G3; + let z4 = z1 - 1.0 + 3.0 * G3; + + let n15 = (num2 & 255) as usize; + let n16 = (num3 & 255) as usize; + let idx1 = (num4 & 255) as usize; + + let index2 = self.perm_mod12 + [(n15 + self.perm[(n16 + self.perm[idx1] as usize) as usize] as usize) as usize] + as usize; + let index3 = self.perm_mod12[(n15 + + n9 + + self.perm[(n16 + n10 + self.perm[(idx1 + n11) as usize] as usize) as usize] as usize) + as usize] as usize; + let index4 = self.perm_mod12[(n15 + + n12 + + self.perm[(n16 + n13 + self.perm[(idx1 + n14) as usize] as usize) as usize] as usize) + as usize] as usize; + let index5 = self.perm_mod12[(n15 + + 1 + + self.perm[(n16 + 1 + self.perm[(idx1 + 1) as usize] as usize) as usize] as usize) + as usize] as usize; + + let n17 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; + let n18 = if n17 < 0.0 { + 0.0 + } else { + let n19 = n17 * n17; + n19 * n19 * dot3_3d(&GRAD3[index2], x1, y1, z1) + }; + + let n20 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; + let n21 = if n20 < 0.0 { + 0.0 + } else { + let n22 = n20 * n20; + n22 * n22 * dot3_3d(&GRAD3[index3], x2, y2, z2) + }; + + let n23 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; + let n24 = if n23 < 0.0 { + 0.0 + } else { + let n25 = n23 * n23; + n25 * n25 * dot3_3d(&GRAD3[index4], x3, y3, z3) + }; + + let n26 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4; + let n27 = if n26 < 0.0 { + 0.0 + } else { + let n28 = n26 * n26; + n28 * n28 * dot3_3d(&GRAD3[index5], x4, y4, z4) + }; + + 32.696434 * (n18 + n21 + n24 + n27) + } + + // ---- FBM (Fractal Brownian Motion) variants ---- + + /// 3D FBM noise. + pub fn noise_3d_fbm( + &self, + x: f64, + y: f64, + z: f64, + n_octaves: i32, + delta_amp: f64, + delta_wlen: f64, + ) -> f64 { + let mut num1 = 0.0; + let mut num2 = 0.5; + let mut x = x; + let mut y = y; + let mut z = z; + for _ in 0..n_octaves { + num1 += self.noise_3d(x, y, z) * num2; + num2 *= delta_amp; + x *= delta_wlen; + y *= delta_wlen; + z *= delta_wlen; + } + num1 + } + + /// 3D FBM noise with custom initial amplitude. + pub fn noise_3d_fbm_initial_amp( + &self, + x: f64, + y: f64, + z: f64, + n_octaves: i32, + delta_amp: f64, + delta_wlen: f64, + initial_amp: f64, + ) -> f64 { + let mut num1 = 0.0; + let mut num2 = initial_amp; + let mut x = x; + let mut y = y; + let mut z = z; + for _ in 0..n_octaves { + num1 += self.noise_3d(x, y, z) * num2; + num2 *= delta_amp; + x *= delta_wlen; + y *= delta_wlen; + z *= delta_wlen; + } + num1 + } + + /// 3D ridged noise. + pub fn ridged_noise( + &self, + x: f64, + y: f64, + z: f64, + n_octaves: i32, + delta_amp: f64, + delta_wlen: f64, + initial_amp: f64, + ) -> f64 { + let mut num1 = 0.0; + let mut num2 = initial_amp; + let mut x = x; + let mut y = y; + let mut z = z; + for _ in 0..n_octaves { + num1 += (self.noise_3d(x, y, z) * num2).abs(); + num2 *= delta_amp; + x *= delta_wlen; + y *= delta_wlen; + z *= delta_wlen; + } + num1 + } +} + +// ---- Helper functions ---- + +#[inline] +fn fastfloor(x: f64) -> i32 { + let num = x as i32; + if x >= num as f64 { + num + } else { + num - 1 + } +} + +#[inline] +fn dot3_3d(g: &[f64; 3], x: f64, y: f64, z: f64) -> f64 { + g[0] * x + g[1] * y + g[2] * z +} diff --git a/inserter/src/algorithm/data/star.rs b/inserter/src/algorithm/data/star.rs index b099673..abb4388 100644 --- a/inserter/src/algorithm/data/star.rs +++ b/inserter/src/algorithm/data/star.rs @@ -1,19 +1,16 @@ use super::enums::{SpectrType, StarType}; use super::game_desc::GameDesc; -use super::macros::macros::{lazy_getter, lazy_getter_ref}; use super::random::DspRandom; use super::vector3::Vector3; use serde::ser::{Serialize, SerializeStruct, Serializer}; -use std::cell::{RefCell, UnsafeCell}; +use std::cell::{OnceCell, RefCell}; use std::f64::consts::PI; -#[allow(dead_code)] #[derive(Debug)] pub struct Star<'a> { pub game_desc: &'a GameDesc, pub used_theme_ids: RefCell>, pub index: usize, - pub seed: i32, pub name_seed: i32, pub position: Vector3, pub level: f32, @@ -25,24 +22,27 @@ pub struct Star<'a> { lifetime_factor: f64, radius_factor: f64, pub planets_seed: i32, + safety_factor_modifier: f64, + max_hive_count_modifier: i32, mass_params: (f64, f64, f64, f64, f32), - get_unmodified_mass: UnsafeCell>, - get_resource_coef: UnsafeCell>, - get_lifetime: UnsafeCell>, - get_age: UnsafeCell>, - get_temperature_factor: UnsafeCell>, - get_unmodified_temperature: UnsafeCell>, - get_temperature: UnsafeCell>, - get_class_factor: UnsafeCell>, - get_spectr: UnsafeCell>, - // get_color: UnsafeCell>, - get_luminosity: UnsafeCell>, - get_radius: UnsafeCell>, - get_light_balance_radius: UnsafeCell>, - get_habitable_radius: UnsafeCell>, - get_mass: UnsafeCell>, - get_orbit_scaler: UnsafeCell>, - get_dyson_radius: UnsafeCell>, + unmodified_mass: OnceCell, + resource_coef: OnceCell, + age: OnceCell, + temperature_factor: OnceCell, + unmodified_temperature: OnceCell, + temperature: OnceCell, + class_factor: OnceCell, + spectr: OnceCell, + luminosity: OnceCell, + radius: OnceCell, + light_balance_radius: OnceCell, + habitable_radius: OnceCell, + mass: OnceCell, + orbit_scaler: OnceCell, + dyson_radius: OnceCell, + hive_rand: RefCell, + max_hive_count: OnceCell, + initial_hive_count: OnceCell, } impl<'a> Star<'a> { @@ -59,22 +59,26 @@ impl<'a> Star<'a> { let mut rand2 = DspRandom::new(rand1.next_seed()); rand1.next_f64(); let planets_seed = rand1.next_seed(); - let r1_1 = rand2.next_f64(); - let r2_1 = rand2.next_f64(); + let mass_random1 = rand2.next_f64(); + let mass_random2 = rand2.next_f64(); let age_factor = rand2.next_f64(); - let rn = rand2.next_f64(); - let rt = rand2.next_f64(); - let age_num1 = (rn * 0.1 + 0.95) as f32; - let age_num2 = (rt * 0.4 + 0.8) as f32; - let age_num3 = (rt * 9.0 + 1.0) as f32; + let age_num1_rand = rand2.next_f64(); + let age_factor_rand = rand2.next_f64(); + let age_num1 = (age_num1_rand * 0.1 + 0.95) as f32; + let age_num2 = (age_factor_rand * 0.4 + 0.8) as f32; + let age_num3 = (age_factor_rand * 9.0 + 1.0) as f32; let mass_factor = if index == 0 { 0.0 } else { rand2.next_f64() }; let lifetime_factor = rand2.next_f64(); - let y = rand2.next_f64() * 0.4 - 0.2; - let radius_factor = 2_f64.powf(y); + let radius_exponent = rand2.next_f64() * 0.4 - 0.2; + let radius_factor = 2_f64.powf(radius_exponent); + let hive_seed = rand2.next_seed(); + let mut hive_rand = DspRandom::new(hive_seed); + let safety_factor_modifier = hive_rand.next_f64(); + let max_hive_count_modifier = hive_rand.next_i32(1000); let mass_params = ( - r1_1, - r2_1, - y, + mass_random1, + mass_random2, + radius_exponent, mass_factor, match need_spectr { SpectrType::M => -3_f32, @@ -87,7 +91,6 @@ impl<'a> Star<'a> { game_desc, used_theme_ids: RefCell::new(vec![]), index, - seed, name_seed, position, level: (index as f32) / ((game_desc.star_count - 1) as f32), @@ -100,85 +103,106 @@ impl<'a> Star<'a> { radius_factor, planets_seed, mass_params, - get_unmodified_mass: UnsafeCell::new(None), - get_resource_coef: UnsafeCell::new(None), - get_lifetime: UnsafeCell::new(None), - get_age: UnsafeCell::new(None), - get_temperature_factor: UnsafeCell::new(None), - get_unmodified_temperature: UnsafeCell::new(None), - get_temperature: UnsafeCell::new(None), - get_class_factor: UnsafeCell::new(None), - get_spectr: UnsafeCell::new(None), - // get_color: UnsafeCell::new(None), - get_luminosity: UnsafeCell::new(None), - get_radius: UnsafeCell::new(None), - get_light_balance_radius: UnsafeCell::new(None), - get_habitable_radius: UnsafeCell::new(None), - get_mass: UnsafeCell::new(None), - get_orbit_scaler: UnsafeCell::new(None), - get_dyson_radius: UnsafeCell::new(None), + safety_factor_modifier, + max_hive_count_modifier, + unmodified_mass: OnceCell::new(), + resource_coef: OnceCell::new(), + age: OnceCell::new(), + temperature_factor: OnceCell::new(), + unmodified_temperature: OnceCell::new(), + temperature: OnceCell::new(), + class_factor: OnceCell::new(), + spectr: OnceCell::new(), + luminosity: OnceCell::new(), + radius: OnceCell::new(), + light_balance_radius: OnceCell::new(), + habitable_radius: OnceCell::new(), + mass: OnceCell::new(), + orbit_scaler: OnceCell::new(), + dyson_radius: OnceCell::new(), + hive_rand: RefCell::new(hive_rand), + max_hive_count: OnceCell::new(), + initial_hive_count: OnceCell::new(), } } pub fn is_birth(&self) -> bool { - return self.index == 0; + self.index == 0 } - lazy_getter!(self, get_unmodified_mass, f32, { - let (r1_1, r2_1, y, mass_factor, spectr_factor) = self.mass_params; - if self.is_birth() { - let p1 = rand_normal(0.0, 0.08, r1_1, r2_1).clamp(-0.2, 0.2); - 2_f32.powf(p1) - } else { - match self.star_type { - StarType::WhiteDwarf => (1.0 + r2_1 * 5.0) as f32, - StarType::NeutronStar => (7.0 + r1_1 * 11.0) as f32, - StarType::BlackHole => (18.0 + r1_1 * r2_1 * 30.0) as f32, - _ => { - let num8 = if spectr_factor != 0.0 { - spectr_factor - } else { - let num7 = -0.98 + (0.88 + 0.98) * self.level.clamp(0.0, 1.0); - let average_value = if self.star_type == StarType::GiantStar { - if y > -0.08 { - -1.5 - } else { - 1.6 - } - } else if num7 >= 0.0 { - num7 + 0.65 - } else { - num7 - 0.65 - }; - let standard_deviation = if self.star_type == StarType::GiantStar { - 0.3_f32 + pub fn get_unmodified_mass(&self) -> f32 { + *self.unmodified_mass.get_or_init(|| { + let (mass_random1, mass_random2, radius_exponent, mass_factor, spectr_factor) = + self.mass_params; + if self.is_birth() { + let birth_mass_exponent = + rand_normal(0.0, 0.08, mass_random1, mass_random2).clamp(-0.2, 0.2); + 2_f32.powf(birth_mass_exponent) + } else { + match self.star_type { + StarType::WhiteDwarf => (1.0 + mass_random2 * 5.0) as f32, + StarType::NeutronStar => (7.0 + mass_random1 * 11.0) as f32, + StarType::BlackHole => (18.0 + mass_random1 * mass_random2 * 30.0) as f32, + _ => { + let mass_exponent = if spectr_factor != 0.0 { + spectr_factor } else { - 0.33_f32 + let base_spectr_exponent = lerp(-0.98, 0.88, self.level); + let average_value = if self.star_type == StarType::GiantStar { + if radius_exponent > -0.08 { + -1.5 + } else { + 1.6 + } + } else if base_spectr_exponent >= 0.0 { + base_spectr_exponent + 0.65 + } else { + base_spectr_exponent - 0.65 + }; + let standard_deviation = if self.star_type == StarType::GiantStar { + 0.3_f32 + } else { + 0.33_f32 + }; + let random_mass_exponent = rand_normal( + average_value, + standard_deviation, + mass_random1, + mass_random2, + ); + (if random_mass_exponent <= 0.0 { + random_mass_exponent + } else { + random_mass_exponent * 2.0 + }) + .clamp(-2.4, 4.65) }; - let num = rand_normal(average_value, standard_deviation, r1_1, r2_1); - (if num <= 0.0 { num } else { num * 2.0 }).clamp(-2.4, 4.65) - }; - 2_f32.powf((num8 as f64 + (mass_factor - 0.5) * 0.2 + 1.0) as f32) + 2_f32.powf((mass_exponent as f64 + (mass_factor - 0.5) * 0.2 + 1.0) as f32) + } } } - } - }); + }) + } - lazy_getter!(self, get_resource_coef, f32, { - if self.is_birth() { - 0.6 - } else { - let mut num1 = (self.position.magnitude() as f32) / 32.0; - if (num1 as f64) > 1.0 { - num1 = ((((num1.ln() + 1.0).ln() + 1.0).ln() + 1.0).ln() + 1.0).ln() + 1.0 + pub fn get_resource_coef(&self) -> f32 { + *self.resource_coef.get_or_init(|| { + if self.is_birth() { + 0.6 + } else { + let mut distance_factor = (self.position.magnitude() as f32) / 32.0; + if (distance_factor as f64) > 1.0 { + distance_factor = + ((((distance_factor.ln() + 1.0).ln() + 1.0).ln() + 1.0).ln() + 1.0).ln() + + 1.0 + } + 7.0_f32.powf(distance_factor) * 0.6 } - 7.0_f32.powf(num1) * 0.6 - } - }); + }) + } - lazy_getter!(self, get_lifetime, f32, { + fn get_lifetime(&self) -> f32 { let unmodified_mass = self.get_unmodified_mass(); - let d = if unmodified_mass < 2.0 { + let lifetime_exponent_base = if unmodified_mass < 2.0 { 2.0 + 0.4 * (1.0 - (unmodified_mass as f64)) } else { 5.0 @@ -194,7 +218,10 @@ impl<'a> Star<'a> { _ => 0.0, }; let lifetime = (10000.0 - * 0.1_f64.powf(((self.get_unmodified_mass() as f64) * mass_multiplier).log(d) + 1.0) + * 0.1_f64.powf( + ((self.get_unmodified_mass() as f64) * mass_multiplier).log(lifetime_exponent_base) + + 1.0, + ) * (self.lifetime_factor * 0.2 + 0.9)) + lifetime_delta; @@ -202,190 +229,369 @@ impl<'a> Star<'a> { lifetime as f32 } else { let age = self.get_age(); - let mut num9 = (lifetime as f32) * age; - if num9 > 5000.0 { - num9 = (((num9 / 5000.0).ln() as f64 + 1.0) * 5000.0) as f32; + let mut adjusted_lifetime = (lifetime as f32) * age; + if adjusted_lifetime > 5000.0 { + adjusted_lifetime = + (((adjusted_lifetime / 5000.0).ln() as f64 + 1.0) * 5000.0) as f32; } - if num9 > 8000.0 { - num9 = - (((((num9 / 8000.0).ln() + 1.0).ln() + 1.0).ln() as f64 + 1.0) * 8000.0) as f32; + if adjusted_lifetime > 8000.0 { + adjusted_lifetime = + (((((adjusted_lifetime / 8000.0).ln() + 1.0).ln() + 1.0).ln() as f64 + 1.0) + * 8000.0) as f32; } - num9 / age + adjusted_lifetime / age } - }); + } - lazy_getter!(self, get_age, f32, { - (if self.is_birth() { - self.age_factor * 0.4 + 0.3 - } else { - match self.star_type { - StarType::GiantStar => self.age_factor * 0.04 + 0.96, - StarType::WhiteDwarf | StarType::NeutronStar | StarType::BlackHole => { - self.age_factor * 0.4 + 1.0 - } - _ => { - let unmodified_mass = self.get_unmodified_mass(); - if unmodified_mass >= 0.8 { - self.age_factor * 0.7 + 0.2 - } else if unmodified_mass >= 0.5 { - self.age_factor * 0.4 + 0.1 - } else { - self.age_factor * 0.12 + 0.02 + pub fn get_age(&self) -> f32 { + *self.age.get_or_init(|| { + (if self.is_birth() { + self.age_factor * 0.4 + 0.3 + } else { + match self.star_type { + StarType::GiantStar => self.age_factor * 0.04 + 0.96, + StarType::WhiteDwarf | StarType::NeutronStar | StarType::BlackHole => { + self.age_factor * 0.4 + 1.0 + } + _ => { + let unmodified_mass = self.get_unmodified_mass(); + if unmodified_mass >= 0.8 { + self.age_factor * 0.7 + 0.2 + } else if unmodified_mass >= 0.5 { + self.age_factor * 0.4 + 0.1 + } else { + self.age_factor * 0.12 + 0.02 + } } } - } - }) as f32 - }); + }) as f32 + }) + } - lazy_getter!(self, get_temperature_factor, f32, { - ((1.0 - (self.get_age().clamp(0.0, 1.0).powf(20.0) as f64) * 0.5) as f32) - * self.get_unmodified_mass() - }); + pub fn get_temperature_factor(&self) -> f32 { + *self.temperature_factor.get_or_init(|| { + ((1.0 - (self.get_age().clamp(0.0, 1.0).powf(20.0) as f64) * 0.5) as f32) + * self.get_unmodified_mass() + }) + } - lazy_getter!(self, get_unmodified_temperature, f32, { - let f1 = self.get_temperature_factor() as f64; - (f1.powf(0.56 + 0.14 / (f1 + 4.0).log(5.0)) * 4450.0 + 1300.0) as f32 - }); + pub fn get_unmodified_temperature(&self) -> f32 { + *self.unmodified_temperature.get_or_init(|| { + let temperature_factor_f64 = self.get_temperature_factor() as f64; + (temperature_factor_f64.powf(0.56 + 0.14 / (temperature_factor_f64 + 4.0).log(5.0)) + * 4450.0 + + 1300.0) as f32 + }) + } - lazy_getter!(self, get_temperature, f32, { - match self.star_type { + pub fn get_temperature(&self) -> f32 { + *self.temperature.get_or_init(|| match self.star_type { StarType::BlackHole => 0.0, StarType::NeutronStar => self.age_num3 * 1e+7, StarType::WhiteDwarf => self.age_num2 * 150000.0, _ => { let temperature = self.get_unmodified_temperature(); if self.star_type == StarType::GiantStar { - let num5 = 1.0 - self.get_age().powf(30.0) * 0.5; - temperature * num5 + let age_mass_factor = 1.0 - self.get_age().powf(30.0) * 0.5; + temperature * age_mass_factor } else { temperature } } - } - }); + }) + } - lazy_getter!(self, get_class_factor, f64, { - let temperature = self.get_unmodified_temperature() as f64; - let mut spectr_factor = ((temperature - 1300.0) / 4500.0).log(2.6) - 0.5; - if spectr_factor < 0.0 { - spectr_factor *= 4.0; - } - spectr_factor.clamp(-4.0, 2.0) - }); + pub fn get_class_factor(&self) -> f64 { + *self.class_factor.get_or_init(|| { + let temperature = self.get_unmodified_temperature() as f64; + let mut spectr_factor = ((temperature - 1300.0) / 4500.0).log(2.6) - 0.5; + if spectr_factor < 0.0 { + spectr_factor *= 4.0; + } + spectr_factor.clamp(-4.0, 2.0) + }) + } - lazy_getter_ref!(self, get_spectr, SpectrType, { - if matches!( - self.star_type, - StarType::WhiteDwarf | StarType::NeutronStar | StarType::BlackHole - ) { - SpectrType::X - } else { - unsafe { ::std::mem::transmute(self.get_class_factor().round() as i32) } - } - }); + pub fn get_spectr(&self) -> SpectrType { + *self.spectr.get_or_init(|| { + if matches!( + self.star_type, + StarType::WhiteDwarf | StarType::NeutronStar | StarType::BlackHole + ) { + SpectrType::X + } else { + unsafe { + ::std::mem::transmute::( + self.get_class_factor().round_ties_even() as i32, + ) + } + } + }) + } - // lazy_getter!(self, get_color, f32, { - // match self.star_type { - // StarType::BlackHole | StarType::NeutronStar => 1.0, - // StarType::WhiteDwarf => 0.7, - // _ => (((self.get_class_factor() + 3.5) * 0.2) as f32).clamp(0.0, 1.0), - // } - // }); + fn get_color(&self) -> f32 { + match self.star_type { + StarType::BlackHole | StarType::NeutronStar => 1.0, + StarType::WhiteDwarf => 0.7, + _ => (((self.get_class_factor() + 3.5) * 0.2) as f32).clamp(0.0, 1.0), + } + } - lazy_getter!(self, get_luminosity, f32, { - let base = self.get_temperature_factor().powf(0.7); - let factor = match self.star_type { - StarType::BlackHole => 1.0 / 1000.0 * self.age_num1, - StarType::NeutronStar => 0.1 * self.age_num1, - StarType::WhiteDwarf => 0.04 * self.age_num1, - StarType::GiantStar => 1.6, - _ => 1.0, - }; - let real = base * factor; - // displayed - (real.powf(0.33) * 1000.0).round() / 1000.0 - }); + pub fn get_luminosity(&self) -> f32 { + *self.luminosity.get_or_init(|| { + let base = self.get_temperature_factor().powf(0.7); + let factor = match self.star_type { + StarType::BlackHole => 1.0 / 1000.0 * self.age_num1, + StarType::NeutronStar => 0.1 * self.age_num1, + StarType::WhiteDwarf => 0.04 * self.age_num1, + StarType::GiantStar => 1.6, + _ => 1.0, + }; + let real = base * factor; + // displayed + (real.powf(0.33) * 1000.0).round_ties_even() / 1000.0 + }) + } - lazy_getter!(self, get_radius, f32, { - if self.star_type == StarType::GiantStar { - let mut num4 = (5.0_f64.powf(((self.get_unmodified_mass() as f64).log10() - 0.7).abs()) - * 5.0) as f32; - if num4 > 10.0 { - num4 = ((num4 * 0.1).ln() + 1.0) * 10.0; + pub fn get_radius(&self) -> f32 { + *self.radius.get_or_init(|| { + if self.star_type == StarType::GiantStar { + let mut giant_radius = (5.0_f64 + .powf(((self.get_unmodified_mass() as f64).log10() - 0.7).abs()) + * 5.0) as f32; + if giant_radius > 10.0 { + giant_radius = ((giant_radius * 0.1).ln() + 1.0) * 10.0; + } + giant_radius * self.age_num2 + } else { + (((self.get_unmodified_mass() as f64).powf(0.4) * self.radius_factor) as f32) + * (match self.star_type { + StarType::NeutronStar => 0.15, + StarType::WhiteDwarf => 0.2, + _ => 1.0, + }) } - num4 * self.age_num2 - } else { - (((self.get_unmodified_mass() as f64).powf(0.4) * self.radius_factor) as f32) - * (match self.star_type { - StarType::NeutronStar => 0.15, - StarType::WhiteDwarf => 0.2, + }) + } + + pub fn get_light_balance_radius(&self) -> f32 { + *self.light_balance_radius.get_or_init(|| { + if self.star_type == StarType::GiantStar { + 3.0 * self.get_habitable_radius() + } else { + let r = 1.7_f32.powf((self.get_class_factor() as f32) + 2.0); + let factor = match self.star_type { + StarType::BlackHole => 0.4 * self.age_num1, + StarType::NeutronStar => 3.0 * self.age_num1, + StarType::WhiteDwarf => 0.2 * self.age_num1, _ => 1.0, - }) - } - }); + }; + r * factor + } + }) + } - lazy_getter!(self, get_light_balance_radius, f32, { - if self.star_type == StarType::GiantStar { - 3.0 * self.get_habitable_radius() - } else { - let r = 1.7_f32.powf((self.get_class_factor() as f32) + 2.0); + pub fn get_habitable_radius(&self) -> f32 { + *self.habitable_radius.get_or_init(|| { let factor = match self.star_type { - StarType::BlackHole => 0.4 * self.age_num1, - StarType::NeutronStar => 3.0 * self.age_num1, - StarType::WhiteDwarf => 0.2 * self.age_num1, + StarType::BlackHole | StarType::NeutronStar => 0.0, + StarType::WhiteDwarf => 0.15 * self.age_num2, + StarType::GiantStar => 9.0, _ => 1.0, }; - r * factor - } - }); - - lazy_getter!(self, get_habitable_radius, f32, { - let factor = match self.star_type { - StarType::BlackHole | StarType::NeutronStar => 0.0, - StarType::WhiteDwarf => 0.15 * self.age_num2, - StarType::GiantStar => 9.0, - _ => 1.0, - }; - if factor == 0.0 { - 0.0 - } else { - (1.7_f32.powf((self.get_class_factor() as f32) + 2.0) - + if self.is_birth() { 0.2 } else { 0.25 }) - * factor - } - }); + if factor == 0.0 { + 0.0 + } else { + (1.7_f32.powf((self.get_class_factor() as f32) + 2.0) + + if self.is_birth() { 0.2 } else { 0.25 }) + * factor + } + }) + } - lazy_getter!(self, get_mass, f32, { - match self.star_type { + pub fn get_mass(&self) -> f32 { + *self.mass.get_or_init(|| match self.star_type { StarType::BlackHole => self.get_unmodified_mass() * 2.5 * self.age_num2, StarType::NeutronStar | StarType::WhiteDwarf => { self.get_unmodified_mass() * 0.2 * self.age_num1 } StarType::GiantStar => { - let num5 = 1.0 - self.get_age().powf(30.0) * 0.5; - self.get_unmodified_mass() * num5 + let age_mass_factor = 1.0 - self.get_age().powf(30.0) * 0.5; + self.get_unmodified_mass() * age_mass_factor } _ => self.get_unmodified_mass(), - } - }); + }) + } - lazy_getter!(self, get_orbit_scaler, f32, { - let mut orbit_scaler = 1.35_f32.powf((self.get_class_factor() as f32) + 2.0); - if orbit_scaler < 1.0 { - orbit_scaler += (1.0 - orbit_scaler) * 0.6; + pub fn get_orbit_scaler(&self) -> f32 { + *self.orbit_scaler.get_or_init(|| { + let mut orbit_scaler = 1.35_f32.powf((self.get_class_factor() as f32) + 2.0); + if orbit_scaler < 1.0 { + orbit_scaler += (1.0 - orbit_scaler) * 0.6; + } + orbit_scaler + * (match self.star_type { + StarType::NeutronStar => 1.5 * self.age_num1, + StarType::GiantStar => 3.3, + _ => 1.0, + }) + }) + } + + pub fn get_dyson_radius(&self) -> i32 { + *self.dyson_radius.get_or_init(|| { + (((self.get_orbit_scaler() * 0.28).max(self.get_radius() * 0.045) * 800.0) + .round_ties_even() as i32) + * 100 + }) + } + + fn get_safety_factor(&self) -> f32 { + if self.is_birth() { + return (0.847 + self.safety_factor_modifier * 0.026) as f32; + } + let mut adjusted_distance = + (((self.position.magnitude() - 2.0) / 20.0) as f32).clamp(0.0, 2.5); + if adjusted_distance > 1.0 { + adjusted_distance = (adjusted_distance.ln() + 1.0).ln() + 1.0 } - orbit_scaler - * (match self.star_type { - StarType::NeutronStar => 1.5 * self.age_num1, - StarType::GiantStar => 3.3, + let normalized_distance = adjusted_distance / 1.4; + let star_type_color_factor: f32 = match self.star_type { + StarType::BlackHole => 5.0, + StarType::NeutronStar => 1.7, + StarType::WhiteDwarf => 1.2, + _ => { + let base_color_factor = self.get_color().powf(1.3); + if self.star_type == StarType::GiantStar { + base_color_factor.max(0.6) + } else if self.get_spectr() == SpectrType::O { + base_color_factor + 0.05 + } else { + base_color_factor + } + } + }; + ((1.0 + - ((star_type_color_factor * 0.9 + 0.07).powf(0.73) as f64) + * (normalized_distance.powf(0.27) as f64) + + self.safety_factor_modifier * 0.08 + - 0.04) as f32) + .clamp(0.0, 1.0) + } + + pub fn get_max_hive_count(&self) -> i32 { + *self.max_hive_count.get_or_init(|| { + let star_type_hive_multiplier = match self.star_type { + StarType::BlackHole | StarType::NeutronStar => 2.0, _ => 1.0, - }) - }); + }; + ((self.game_desc.hive_max_density * star_type_hive_multiplier * 1000.0 + + (self.max_hive_count_modifier as f64) + + 0.5) as i32) + / 1000 + }) + } - lazy_getter!(self, get_dyson_radius, i32, { - (((self.get_orbit_scaler() * 0.28).max(self.get_radius() * 0.045) * 800.0).round() as i32) - * 100 - }); + pub fn get_initial_hive_count(&self) -> i32 { + *self.initial_hive_count.get_or_init(|| { + let initial_colonize = self.game_desc.hive_initial_colonize; + if initial_colonize < 0.015 { + return 0; + } + let max_hive_count = self.get_max_hive_count(); + if self.is_birth() { + let birth_min_hives = if initial_colonize * (max_hive_count as f64) < 0.7 { + 0 + } else { + 1 + }; + let mut birth_avg_hives = 0.6 * (initial_colonize as f32) * (max_hive_count as f32); + let mut birth_std_dev = 0.5; + if birth_avg_hives < 1.0 { + birth_std_dev = ((birth_avg_hives.sqrt() as f64) * 0.29 + 0.21) as f32; + } else if birth_avg_hives > max_hive_count as f32 { + birth_avg_hives = max_hive_count as f32; + } + let mut rand3 = self.hive_rand.borrow_mut(); + let mut initial_hive_count: i32 = -1; + for _ in 0..17 { + let r1_2 = rand3.next_f64(); + let r2_2 = rand3.next_f64(); + initial_hive_count = + (rand_normal(birth_avg_hives, birth_std_dev, r1_2, r2_2) + 0.5) as i32; + if initial_hive_count >= 0 && initial_hive_count <= max_hive_count { + break; + } + } + return initial_hive_count.clamp(birth_min_hives, max_hive_count); + } + let hive_probability_base = ((1.0 + - (((self.get_safety_factor() * 1.05 - 0.15) as f32) + .clamp(0.0, 1.0) + .powf(0.82) as f64) + - (((max_hive_count - 1) as f64) * 0.05)) + as f32) + .clamp(0.0, 1.0) + * ((1.1 - (max_hive_count as f64) * 0.1) as f32); + let raw_probability = if initial_colonize > 1.0 { + lerp( + hive_probability_base, + (1.0 + (initial_colonize - 1.0) * 0.2) as f32, + ((initial_colonize - 1.0) * 0.5) as f32, + ) + } else { + hive_probability_base * (initial_colonize as f32) + }; + let star_type_adjusted_probability = match self.star_type { + StarType::GiantStar => raw_probability * 1.2, + StarType::WhiteDwarf => raw_probability * 1.4, + StarType::NeutronStar => raw_probability * 1.6, + StarType::BlackHole => raw_probability * 1.8, + _ => { + if self.get_spectr() == SpectrType::O { + raw_probability * 1.1 + } else { + raw_probability + } + } + }; + let expected_hive_count: f32 = (star_type_adjusted_probability + * (max_hive_count as f32)) + .min((max_hive_count as f32) + 0.75); + let hive_std_dev: f32 = if expected_hive_count <= 0.01 { + 0.0 + } else if expected_hive_count < 1.0 { + expected_hive_count.sqrt() * 2.9 + 2.1 + } else if expected_hive_count > 1.0 { + 0.3 + 0.2 * expected_hive_count + } else { + 0.5 + }; + let mut rand3 = self.hive_rand.borrow_mut(); + let mut initial_hive_count: i32 = -1; + for _ in 0..65 { + let r1_2 = rand3.next_f64(); + let r2_2 = rand3.next_f64(); + initial_hive_count = + (rand_normal(expected_hive_count, hive_std_dev, r1_2, r2_2) + 0.5) as i32; + if initial_hive_count >= 0 && initial_hive_count <= max_hive_count { + break; + } + } + initial_hive_count = initial_hive_count.clamp(0, max_hive_count); + + if self.star_type == StarType::BlackHole { + (((self.game_desc.hive_max_density * 1000.0 + + (self.max_hive_count_modifier as f64) + + 0.5) as i32) + / 1000) + .max(initial_hive_count) + .max(1) + } else { + initial_hive_count + } + }) + } } impl Serialize for Star<'_> { @@ -393,7 +599,7 @@ impl Serialize for Star<'_> { where S: Serializer, { - let mut state = serializer.serialize_struct("Star", 11)?; + let mut state = serializer.serialize_struct("Star", 14)?; state.serialize_field("index", &self.index)?; state.serialize_field("position", &self.position)?; state.serialize_field("mass", &self.get_mass())?; @@ -405,6 +611,9 @@ impl Serialize for Star<'_> { state.serialize_field("luminosity", &self.get_luminosity())?; state.serialize_field("radius", &self.get_radius())?; state.serialize_field("dysonRadius", &self.get_dyson_radius())?; + state.serialize_field("initialHiveCount", &self.get_initial_hive_count())?; + state.serialize_field("maxHiveCount", &self.get_max_hive_count())?; + state.serialize_field("color", &self.get_color())?; state.end() } } @@ -413,3 +622,8 @@ fn rand_normal(average_value: f32, standard_deviation: f32, r1: f64, r2: f64) -> average_value + standard_deviation * ((-2.0 * (1.0 - r1).ln()).sqrt() * (2.0 * PI * r2).sin()) as f32 } + +#[inline] +fn lerp(a: f32, b: f32, t: f32) -> f32 { + a + (b - a) * t.clamp(0.0, 1.0) +} diff --git a/inserter/src/algorithm/data/star_planets.rs b/inserter/src/algorithm/data/star_planets.rs index 3d71a4d..b443823 100644 --- a/inserter/src/algorithm/data/star_planets.rs +++ b/inserter/src/algorithm/data/star_planets.rs @@ -1,34 +1,61 @@ -use std::cell::UnsafeCell; -use std::collections::HashMap; +use std::cell::{Cell, UnsafeCell}; use std::rc::Rc; +use crate::algorithm::data::game_desc::GameDesc; + use super::enums::{SpectrType, StarType, VeinType}; use super::planet::Planet; use super::random::DspRandom; use super::star::Star; -use serde::ser::SerializeStruct; -use serde::{Serialize, Serializer}; +use serde::Serialize; + +pub fn serialize_planets( + planets: &UnsafeCell>>, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + unsafe { &*planets.get() }.serialize(serializer) +} -#[allow(dead_code)] -#[derive(Debug)] +const MAX_VEIN_COUNT: usize = VeinType::Max as usize; + +#[derive(Debug, Serialize)] pub struct StarWithPlanets<'a> { + pub name: String, + #[serde(flatten)] pub star: Rc>, + #[serde(serialize_with = "serialize_planets")] planets: UnsafeCell>>, + + #[serde(skip)] safe: UnsafeCell, - avg_veins: UnsafeCell>, - avg_vein_amounts: HashMap, // for serde to serialize - pub name: String, + #[serde(skip)] + avg_veins: UnsafeCell<[f32; MAX_VEIN_COUNT]>, + #[serde(skip)] + actual_veins: UnsafeCell<[f32; MAX_VEIN_COUNT]>, + #[serde(skip)] + game_desc: &'a GameDesc, + #[serde(skip)] + habitable_count: &'a Cell, } impl<'a> StarWithPlanets<'a> { - pub fn new(star: Rc>) -> Self { + pub fn new( + star: Rc>, + game_desc: &'a GameDesc, + habitable_count: &'a Cell, + ) -> Self { Self { star, - planets: UnsafeCell::new(vec![]), + planets: UnsafeCell::new(Vec::with_capacity(6)), safe: UnsafeCell::new(false), - avg_veins: UnsafeCell::new(HashMap::new()), - avg_vein_amounts: HashMap::new(), + avg_veins: UnsafeCell::new([f32::NAN; MAX_VEIN_COUNT]), + actual_veins: UnsafeCell::new([f32::NAN; MAX_VEIN_COUNT]), name: Default::default(), + game_desc, + habitable_count, } } @@ -55,68 +82,79 @@ impl<'a> StarWithPlanets<'a> { && self.star.star_type != StarType::BlackHole && self.star.star_type != StarType::NeutronStar { + if !self.is_safe() { + self.load_planets(); + } return 0.0; } - let map = unsafe { &mut *self.avg_veins.get() }; - if let Some(val) = map.get(vein_type) { - return *val; + let index = *vein_type as usize; + let cached_value = unsafe { + let arr = &mut *self.avg_veins.get(); + arr.get_unchecked_mut(index) + }; + if !cached_value.is_nan() { + return *cached_value; } let mut count = 0_f32; - let is_rare = vein_type.is_rare(); - let is_safe = self.is_safe(); for planet in self.get_planets() { - if planet.is_gas_giant() { - if !is_safe { - planet.get_theme(); - } + if !planet.can_have_vein(vein_type) { continue; } - if is_rare { - let theme = planet.get_theme(); - // skip vein generation if possible - if !theme.rare_veins.contains(vein_type) { - continue; + if planet.is_acutal_veins_generated() { + for vein in planet.get_actual_veins() { + if &vein.vein_type == vein_type { + count += vein.amount as f32; + } } - } - for vein in planet.get_veins() { - if &vein.vein_type == vein_type { - let avg_patches = ((vein.min_patch + vein.max_patch) as f32) - * ((vein.min_group + vein.max_group) as f32) - * ((vein.min_amount + vein.max_amount) as f32) - / 8.0; - count += avg_patches; + } else { + for vein in planet.get_estimated_veins() { + if &vein.vein_type == vein_type { + let avg_patches = ((vein.min_patch + vein.max_patch) as f32) + * ((vein.min_group + vein.max_group) as f32) + * ((vein.min_amount + vein.max_amount) as f32) + / 8.0; + count += avg_patches; + } } } } - map.insert(vein_type.clone(), count); + *cached_value = count; self.mark_safe(); count } - fn all_avg_veins(&self) -> HashMap { - let mut results = HashMap::::new(); - let vts: Vec = vec![ - VeinType::Iron, - VeinType::Copper, - VeinType::Silicium, - VeinType::Titanium, - VeinType::Stone, - VeinType::Coal, - VeinType::Oil, - VeinType::Fireice, - VeinType::Diamond, - VeinType::Fractal, - VeinType::Crysrub, - VeinType::Grat, - VeinType::Bamboo, - VeinType::Mag, - VeinType::Max, - ]; - for vein_type in vts { - results.insert(vein_type.clone(), self.get_avg_vein(&vein_type)); + pub fn get_actual_vein(&self, vein_type: &VeinType) -> f32 { + if vein_type == &VeinType::Mag + && self.star.star_type != StarType::BlackHole + && self.star.star_type != StarType::NeutronStar + { + if !self.is_safe() { + self.load_planets(); + } + return 0.0; } - - results + let index = *vein_type as usize; + let cached_value = unsafe { + let arr = &mut *self.actual_veins.get(); + arr.get_unchecked_mut(index) + }; + if !cached_value.is_nan() { + return *cached_value; + } + let mut count = 0; + for planet in self.get_planets() { + if !planet.can_have_vein(vein_type) { + continue; + } + for vein in planet.get_actual_veins() { + if &vein.vein_type == vein_type { + count += vein.amount; + } + } + } + *cached_value = count as f32; + self.mark_safe(); + count as f32 } pub fn get_planets(&self) -> &Vec> { @@ -125,9 +163,9 @@ impl<'a> StarWithPlanets<'a> { return planets; } let mut rand2 = DspRandom::new(self.star.planets_seed); - let num1 = rand2.next_f64(); - let num2 = rand2.next_f64(); - let num3 = if rand2.next_f64() > 0.5 { 1 } else { 0 }; + let planet_count_rand = rand2.next_f64(); + let planet_config_rand = rand2.next_f64(); + let orbit_offset = if rand2.next_f64() > 0.5 { 1 } else { 0 }; rand2.next_f64(); rand2.next_f64(); rand2.next_f64(); @@ -137,8 +175,10 @@ impl<'a> StarWithPlanets<'a> { let info_seed = rand2.next_seed(); let gen_seed = rand2.next_seed(); Planet::new( + self.game_desc, self.star.clone(), index, + self.habitable_count, orbit_index, gas_giant, info_seed, @@ -151,9 +191,9 @@ impl<'a> StarWithPlanets<'a> { if star_type == &StarType::BlackHole || star_type == &StarType::NeutronStar { planets.push(make_planet(0, 3, false)); } else if star_type == &StarType::WhiteDwarf { - if num1 < 0.7 { + if planet_count_rand < 0.7 { planets.push(make_planet(0, 3, false)); - } else if num2 < 0.3 { + } else if planet_config_rand < 0.3 { planets.push(make_planet(0, 3, false)); planets.push(make_planet(1, 4, false)); } else { @@ -164,12 +204,12 @@ impl<'a> StarWithPlanets<'a> { planet2.orbit_around.replace(Some(planet1)); } } else if star_type == &StarType::GiantStar { - if num1 < 0.3 { - planets.push(make_planet(0, 2 + num3, false)); - } else if num1 < 0.8 { - if num2 < 0.25 { - planets.push(make_planet(0, 2 + num3, false)); - planets.push(make_planet(1, 3 + num3, false)); + if planet_count_rand < 0.3 { + planets.push(make_planet(0, 2 + orbit_offset, false)); + } else if planet_count_rand < 0.8 { + if planet_config_rand < 0.25 { + planets.push(make_planet(0, 2 + orbit_offset, false)); + planets.push(make_planet(1, 3 + orbit_offset, false)); } else { planets.push(make_planet(0, 3, true)); planets.push(make_planet(1, 1, false)); @@ -178,19 +218,19 @@ impl<'a> StarWithPlanets<'a> { planet2.orbit_around.replace(Some(planet1)); } } else { - if num2 < 0.15 { - planets.push(make_planet(0, 2 + num3, false)); - planets.push(make_planet(1, 3 + num3, false)); - planets.push(make_planet(2, 4 + num3, false)); - } else if num2 < 0.75 { - planets.push(make_planet(0, 2 + num3, false)); + if planet_config_rand < 0.15 { + planets.push(make_planet(0, 2 + orbit_offset, false)); + planets.push(make_planet(1, 3 + orbit_offset, false)); + planets.push(make_planet(2, 4 + orbit_offset, false)); + } else if planet_config_rand < 0.75 { + planets.push(make_planet(0, 2 + orbit_offset, false)); planets.push(make_planet(1, 4, true)); planets.push(make_planet(2, 1, false)); let planet2 = &planets[1]; let planet3 = &planets[2]; planet3.orbit_around.replace(Some(planet2)); } else { - planets.push(make_planet(0, 3 + num3, true)); + planets.push(make_planet(0, 3 + orbit_offset, true)); planets.push(make_planet(1, 1, false)); planets.push(make_planet(2, 2, false)); let planet1 = &planets[0]; @@ -206,11 +246,11 @@ impl<'a> StarWithPlanets<'a> { } else { match self.star.get_spectr() { SpectrType::M => { - let planet_count = if num1 >= 0.8 { + let planet_count = if planet_count_rand >= 0.8 { 4 - } else if num1 >= 0.3 { + } else if planet_count_rand >= 0.3 { 3 - } else if num1 >= 0.1 { + } else if planet_count_rand >= 0.1 { 2 } else { 1 @@ -225,13 +265,13 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::K => { - let planet_count = if num1 >= 0.95 { + let planet_count = if planet_count_rand >= 0.95 { 5 - } else if num1 >= 0.7 { + } else if planet_count_rand >= 0.7 { 4 - } else if num1 >= 0.2 { + } else if planet_count_rand >= 0.2 { 3 - } else if num1 >= 0.1 { + } else if planet_count_rand >= 0.1 { 2 } else { 1 @@ -246,9 +286,9 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::G => { - let planet_count = if num1 >= 0.9 { + let planet_count = if planet_count_rand >= 0.9 { 5 - } else if num1 >= 0.4 { + } else if planet_count_rand >= 0.4 { 4 } else { 3 @@ -263,9 +303,9 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::F => { - let planet_count = if num1 >= 0.8 { + let planet_count = if planet_count_rand >= 0.8 { 5 - } else if num1 >= 0.35 { + } else if planet_count_rand >= 0.35 { 4 } else { 3 @@ -280,9 +320,9 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::A => { - let planet_count = if num1 >= 0.75 { + let planet_count = if planet_count_rand >= 0.75 { 5 - } else if num1 >= 0.3 { + } else if planet_count_rand >= 0.3 { 4 } else { 3 @@ -297,9 +337,9 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::B => { - let planet_count = if num1 >= 0.75 { + let planet_count = if planet_count_rand >= 0.75 { 6 - } else if num1 >= 0.3 { + } else if planet_count_rand >= 0.3 { 5 } else { 4 @@ -314,7 +354,7 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::O => { - let planet_count = if num1 >= 0.5 { 6 } else { 5 }; + let planet_count = if planet_count_rand >= 0.5 { 6 } else { 5 }; (planet_count, P_GASES[9]) } _ => (1, P_GASES[0]), @@ -322,31 +362,37 @@ impl<'a> StarWithPlanets<'a> { }; let mut satellite_count = 0; let mut orbit_around: Option = None; - let mut num10: usize = 1; - let mut orbits: Vec<(usize, usize)> = vec![]; + let mut current_orbit_index: usize = 1; + let mut orbits: Vec<(usize, usize)> = Vec::with_capacity(4); for index in 0..planet_count as usize { let info_seed = rand2.next_seed(); let gen_seed = rand2.next_seed(); - let num11 = rand2.next_f64(); - let num12 = rand2.next_f64(); + let gas_giant_chance_rand = rand2.next_f64(); + let stop_satellite_chance_rand = rand2.next_f64(); let mut gas_giant = false; if orbit_around.is_none() { - if index < planet_count - 1 && num11 < p_gas[index] { + if index < planet_count - 1 && gas_giant_chance_rand < p_gas[index] { gas_giant = true; - if num10 < 3 { - num10 = 3; + if current_orbit_index < 3 { + current_orbit_index = 3; } } let mut broke_from_loop = false; - while !self.star.is_birth() || num10 != 3 { - let num13 = planet_count - index; - let num14 = 9 - num10; - if num14 > num13 { - let a = (num13 as f32) / (num14 as f32); - let a2 = if num10 <= 3 { 0.15_f32 } else { 0.45_f32 }; - let num15 = a + (1.0 - a) * a2 + 0.01; - if rand2.next_f64() < num15 as f64 { + while !self.star.is_birth() || current_orbit_index != 3 { + let remaining_planets = planet_count - index; + let remaining_orbit_slots = 9 - current_orbit_index; + if remaining_orbit_slots > remaining_planets { + let remaining_ratio = + (remaining_planets as f32) / (remaining_orbit_slots as f32); + let skip_chance_base = if current_orbit_index <= 3 { + 0.15_f32 + } else { + 0.45_f32 + }; + let orbit_skip_threshold = + remaining_ratio + (1.0 - remaining_ratio) * skip_chance_base + 0.01; + if rand2.next_f64() < orbit_skip_threshold as f64 { broke_from_loop = true; break; } @@ -354,7 +400,7 @@ impl<'a> StarWithPlanets<'a> { broke_from_loop = true; break; } - num10 += 1; + current_orbit_index += 1; } if !broke_from_loop { gas_giant = true; @@ -363,10 +409,12 @@ impl<'a> StarWithPlanets<'a> { satellite_count += 1; } let planet = Planet::new( + self.game_desc, self.star.clone(), index, + self.habitable_count, if orbit_around.is_none() { - num10 + current_orbit_index } else { satellite_count }, @@ -377,12 +425,12 @@ impl<'a> StarWithPlanets<'a> { if let Some(around) = orbit_around { orbits.push((index, around)) } - num10 += 1; + current_orbit_index += 1; if gas_giant { orbit_around = Some(index); satellite_count = 0; } - if satellite_count >= 1 && num12 < 0.8 { + if satellite_count >= 1 && stop_satellite_chance_rand < 0.8 { orbit_around = None; satellite_count = 0; } @@ -398,20 +446,7 @@ impl<'a> StarWithPlanets<'a> { planets } } -impl<'a> Serialize for StarWithPlanets<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("StarWithPlanets", 7)?; - state.serialize_field("star", &self.star)?; - state.serialize_field("planets", unsafe { &*self.planets.get() })?; - state.serialize_field("avg_veins", &self.all_avg_veins())?; - state.serialize_field("name", &self.name)?; - state.end() - } -} const P_GASES: [[f64; 6]; 10] = [ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], // birth [0.2, 0.2, 0.0, 0.0, 0.0, 0.0], // M / F / A / B, n <= 3 diff --git a/inserter/src/algorithm/data/theme_proto.rs b/inserter/src/algorithm/data/theme_proto.rs index fb137f3..61e0038 100644 --- a/inserter/src/algorithm/data/theme_proto.rs +++ b/inserter/src/algorithm/data/theme_proto.rs @@ -1,3 +1,5 @@ +use crate::algorithm::data::vector2::Vector2; + use super::enums::{PlanetType, ThemeDistribute, VeinType}; use once_cell::sync::Lazy; use serde::Serialize; @@ -11,7 +13,9 @@ pub struct ThemeProto { pub water_item_id: i32, #[serde(skip)] pub distribute: ThemeDistribute, + #[serde(skip)] pub temperature: f32, + #[serde(skip)] pub planet_type: PlanetType, #[serde(skip)] pub vein_spot: Vec, @@ -27,6 +31,12 @@ pub struct ThemeProto { pub gas_items: Vec, #[serde(skip)] pub gas_speeds: Vec, + #[serde(skip)] + pub algos: Vec, + #[serde(skip)] + pub mod_x: Vector2, + #[serde(skip)] + pub mod_y: Vector2, } pub const DEFAULT_THEME_PROTO: &'static ThemeProto = &ThemeProto { @@ -44,6 +54,9 @@ pub const DEFAULT_THEME_PROTO: &'static ThemeProto = &ThemeProto { rare_settings: vec![], gas_items: vec![], gas_speeds: vec![], + algos: vec![], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }; impl Default for ThemeProto { @@ -69,6 +82,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 1.0, 0.3, 0.3], gas_items: vec![], gas_speeds: vec![], + algos: vec![1], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 2, @@ -85,6 +101,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![1120, 1121], gas_speeds: vec![0.96, 0.04], + algos: vec![0], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 3, @@ -101,6 +120,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![1120, 1121], gas_speeds: vec![0.96, 0.04], + algos: vec![0], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 4, @@ -117,6 +139,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![1011, 1120], gas_speeds: vec![0.7, 0.3], + algos: vec![0], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 5, @@ -133,6 +158,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![1011, 1120], gas_speeds: vec![0.7, 0.3], + algos: vec![0], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 6, @@ -149,6 +177,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.18, 0.2, 0.3], gas_items: vec![], gas_speeds: vec![], + algos: vec![2], + mod_x: Vector2(1.0, 1.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 7, @@ -165,6 +196,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.3, 0.5, 0.7, 0.5, 0.0, 0.3, 0.2, 0.6], gas_items: vec![], gas_speeds: vec![], + algos: vec![2], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 8, @@ -181,6 +215,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 1.0, 0.3, 1.0, 0.0, 0.5, 0.2, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![1], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 9, @@ -189,7 +226,7 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { wind: 0.7, distribute: ThemeDistribute::Default, temperature: 5.0, - planet_type: PlanetType::Vocano, + planet_type: PlanetType::Volcano, vein_spot: vec![15, 15, 2, 9, 4, 2, 0], vein_count: vec![1.0, 1.0, 0.6, 1.0, 0.6, 0.3, 0.0], vein_opacity: vec![1.0, 1.0, 0.6, 1.0, 0.5, 0.3, 0.0], @@ -197,6 +234,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.2, 0.6, 0.7, 0.0, 0.2, 0.6, 0.7, 0.0, 0.1, 0.2, 0.8], gas_items: vec![], gas_speeds: vec![], + algos: vec![5], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 10, @@ -213,6 +253,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.3, 1.0, 0.8, 1.0, 0.0, 0.2, 0.6, 0.4, 0.0, 0.1, 0.2, 0.4], gas_items: vec![], gas_speeds: vec![], + algos: vec![3], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.34, -0.15), }, ThemeProto { id: 11, @@ -225,10 +268,20 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { vein_spot: vec![3, 3, 3, 6, 12, 0, 0], vein_count: vec![0.5, 0.5, 0.5, 1.0, 1.2, 0.0, 0.0], vein_opacity: vec![0.6, 0.6, 0.9, 0.9, 1.5, 0.0, 0.0], - rare_veins: vec![VeinType::Fireice, VeinType::Diamond, VeinType::Grat], - rare_settings: vec![0.25, 0.5, 0.6, 0.6, 0.0, 0.2, 0.6, 0.7, 0.0, 0.1, 0.2, 0.5], + rare_veins: vec![ + VeinType::Fireice, + VeinType::Diamond, + VeinType::Grat, + VeinType::Bamboo, + ], + rare_settings: vec![ + 0.25, 0.5, 0.6, 0.8, 0.0, 0.2, 0.6, 0.7, 0.0, 0.2, 0.3, 0.7, 0.1, 0.2, 0.2, 0.7, + ], gas_items: vec![], gas_speeds: vec![], + algos: vec![4], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 12, @@ -245,6 +298,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.25, 0.6, 0.6, 0.0, 0.25, 0.6, 0.6, 0.0, 0.1, 0.2, 0.5], gas_items: vec![], gas_speeds: vec![], + algos: vec![3], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 13, @@ -253,7 +309,7 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { wind: 0.8, distribute: ThemeDistribute::Interstellar, temperature: 4.0, - planet_type: PlanetType::Vocano, + planet_type: PlanetType::Volcano, vein_spot: vec![10, 10, 2, 7, 4, 1, 0], vein_count: vec![1.0, 1.0, 0.6, 1.0, 0.6, 0.3, 0.0], vein_opacity: vec![1.0, 1.0, 0.6, 1.0, 0.5, 0.3, 0.0], @@ -261,6 +317,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![], gas_speeds: vec![], + algos: vec![3], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 14, @@ -277,6 +336,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.4, 0.3, 0.5, 0.0, 1.0, 0.3, 0.8, 0.0, 0.5, 0.2, 0.8], gas_items: vec![], gas_speeds: vec![], + algos: vec![1], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 15, @@ -293,6 +355,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 1.0, 0.3, 1.0, 0.0, 0.5, 0.2, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![6], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 16, @@ -309,6 +374,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![1.0, 1.0, 1.0, 0.9], gas_items: vec![], gas_speeds: vec![], + algos: vec![7], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 17, @@ -325,6 +393,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.7, 0.7, 0.5, 0.0, 0.1, 0.2, 0.7], gas_items: vec![], gas_speeds: vec![], + algos: vec![2], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 18, @@ -341,6 +412,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 1.0, 0.3, 1.0, 0.0, 0.5, 0.2, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![1], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 19, @@ -357,6 +431,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.25, 0.6, 0.6, 0.0, 0.25, 0.6, 0.6, 0.0, 0.4, 0.3, 0.9], gas_items: vec![], gas_speeds: vec![], + algos: vec![8], + mod_x: Vector2(1.5, 1.5), + mod_y: Vector2(-5.0, -5.0), }, ThemeProto { id: 20, @@ -373,6 +450,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.25, 1.0, 0.6, 0.7, 0.0, 0.2, 0.6, 0.9, 0.0, 0.3, 0.4, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![9], + mod_x: Vector2(6.0, 6.0), + mod_y: Vector2(8.0, 8.0), }, ThemeProto { id: 21, @@ -389,6 +469,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![1120, 1121], gas_speeds: vec![0.84, 0.16], + algos: vec![0], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 22, @@ -405,6 +488,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 1.0, 0.5, 1.0, 0.0, 0.6, 0.25, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![10], + mod_x: Vector2(1.0, 1.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 23, @@ -421,6 +507,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.7, 0.2, 0.6, 0.0, 1.0, 1.0, 0.84], gas_items: vec![], gas_speeds: vec![], + algos: vec![11], + mod_x: Vector2(1.5, 1.5), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 24, @@ -437,11 +526,14 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.3, 1.0, 0.8, 1.0, 0.0, 1.0, 0.7, 1.0, 0.0, 0.4, 0.5, 0.7], gas_items: vec![], gas_speeds: vec![], + algos: vec![12], + mod_x: Vector2(1.0, 1.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 25, name: "Desert 11", - water_item_id: -2, + water_item_id: 0, wind: 1.0, distribute: ThemeDistribute::Interstellar, temperature: 0.0, @@ -453,6 +545,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.5, 0.3, 1.0, 0.0, 1.0, 0.3, 1.0, 0.0, 0.5, 0.2, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![13], + mod_x: Vector2(1.0, 1.0), + mod_y: Vector2(3.0, 3.0), }, ] }); diff --git a/inserter/src/algorithm/data/vector2.rs b/inserter/src/algorithm/data/vector2.rs new file mode 100644 index 0000000..c280122 --- /dev/null +++ b/inserter/src/algorithm/data/vector2.rs @@ -0,0 +1,4 @@ +use serde::Serialize; + +#[derive(Debug, PartialEq, Clone, Copy, Serialize)] +pub struct Vector2(pub f64, pub f64); diff --git a/inserter/src/algorithm/data/vector3.rs b/inserter/src/algorithm/data/vector3.rs index 910058e..44cb648 100644 --- a/inserter/src/algorithm/data/vector3.rs +++ b/inserter/src/algorithm/data/vector3.rs @@ -1,6 +1,6 @@ use serde::Serialize; -#[derive(Debug, PartialEq, Clone, Serialize)] +#[derive(Debug, PartialEq, Clone, Copy, Serialize)] pub struct Vector3(pub f64, pub f64, pub f64); impl Vector3 { @@ -29,7 +29,7 @@ impl Vector3 { pub fn normalize(&mut self) -> &mut Self { let num = self.magnitude(); - if num > 9.99999974737875e-6 { + if num > 1e-5 { *self /= num; } else { *self = Self::zero(); @@ -82,6 +82,14 @@ impl std::ops::SubAssign<&Vector3> for Vector3 { } } +impl std::ops::Mul for Vector3 { + type Output = Vector3; + + fn mul(self, rhs: f64) -> Vector3 { + return Vector3(self.0 * rhs, self.1 * rhs, self.2 * rhs); + } +} + impl std::ops::Mul for &Vector3 { type Output = Vector3; diff --git a/inserter/src/algorithm/data/vector_f2.rs b/inserter/src/algorithm/data/vector_f2.rs new file mode 100644 index 0000000..cbacb7f --- /dev/null +++ b/inserter/src/algorithm/data/vector_f2.rs @@ -0,0 +1,204 @@ +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +/// f32-based 2D vector, port of Unity's Vector2. +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct VectorF2(pub f32, pub f32); + +impl VectorF2 { + pub fn new(x: f32, y: f32) -> Self { + Self(x, y) + } + + pub fn zero() -> Self { + Self(0.0, 0.0) + } + + pub fn up() -> Self { + Self(0.0, 1.0) + } + + pub fn down() -> Self { + Self(0.0, -1.0) + } + + pub fn right() -> Self { + Self(1.0, 0.0) + } + + pub fn left() -> Self { + Self(-1.0, 0.0) + } + + pub fn magnitude_sq(&self) -> f32 { + self.0 * self.0 + self.1 * self.1 + } + + pub fn magnitude(&self) -> f32 { + self.magnitude_sq().sqrt() + } + + pub fn normalize(&mut self) -> &mut Self { + let mag = self.magnitude(); + if mag > 1e-10 { + *self /= mag; + } else { + *self = Self::zero(); + } + self + } + + pub fn normalized(&self) -> Self { + let mag = self.magnitude(); + if mag > 1e-10 { + *self / mag + } else { + Self::zero() + } + } + + pub fn dot(a: &Self, b: &Self) -> f32 { + a.0 * b.0 + a.1 * b.1 + } + + pub fn distance_sq_from(&self, pt: &Self) -> f32 { + let dx = pt.0 - self.0; + let dy = pt.1 - self.1; + dx * dx + dy * dy + } +} + +impl Add for VectorF2 { + type Output = VectorF2; + + fn add(self, rhs: VectorF2) -> VectorF2 { + VectorF2(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl Add<&VectorF2> for VectorF2 { + type Output = VectorF2; + + fn add(self, rhs: &VectorF2) -> VectorF2 { + VectorF2(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl Add<&VectorF2> for &VectorF2 { + type Output = VectorF2; + + fn add(self, rhs: &VectorF2) -> VectorF2 { + VectorF2(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl AddAssign for VectorF2 { + fn add_assign(&mut self, rhs: VectorF2) { + self.0 += rhs.0; + self.1 += rhs.1; + } +} + +impl AddAssign<&VectorF2> for VectorF2 { + fn add_assign(&mut self, rhs: &VectorF2) { + self.0 += rhs.0; + self.1 += rhs.1; + } +} + +impl Sub for VectorF2 { + type Output = VectorF2; + + fn sub(self, rhs: VectorF2) -> VectorF2 { + VectorF2(self.0 - rhs.0, self.1 - rhs.1) + } +} + +impl Sub<&VectorF2> for VectorF2 { + type Output = VectorF2; + + fn sub(self, rhs: &VectorF2) -> VectorF2 { + VectorF2(self.0 - rhs.0, self.1 - rhs.1) + } +} + +impl Sub<&VectorF2> for &VectorF2 { + type Output = VectorF2; + + fn sub(self, rhs: &VectorF2) -> VectorF2 { + VectorF2(self.0 - rhs.0, self.1 - rhs.1) + } +} + +impl SubAssign for VectorF2 { + fn sub_assign(&mut self, rhs: VectorF2) { + self.0 -= rhs.0; + self.1 -= rhs.1; + } +} + +impl SubAssign<&VectorF2> for VectorF2 { + fn sub_assign(&mut self, rhs: &VectorF2) { + self.0 -= rhs.0; + self.1 -= rhs.1; + } +} + +impl Mul for VectorF2 { + type Output = VectorF2; + + fn mul(self, rhs: f32) -> VectorF2 { + VectorF2(self.0 * rhs, self.1 * rhs) + } +} + +impl Mul for &VectorF2 { + type Output = VectorF2; + + fn mul(self, rhs: f32) -> VectorF2 { + VectorF2(self.0 * rhs, self.1 * rhs) + } +} + +impl MulAssign for VectorF2 { + fn mul_assign(&mut self, rhs: f32) { + self.0 *= rhs; + self.1 *= rhs; + } +} + +impl DivAssign for VectorF2 { + fn div_assign(&mut self, rhs: f32) { + self.0 /= rhs; + self.1 /= rhs; + } +} + +impl Div for VectorF2 { + type Output = VectorF2; + + fn div(self, rhs: f32) -> VectorF2 { + VectorF2(self.0 / rhs, self.1 / rhs) + } +} + +impl Div for &VectorF2 { + type Output = VectorF2; + + fn div(self, rhs: f32) -> VectorF2 { + VectorF2(self.0 / rhs, self.1 / rhs) + } +} + +impl Neg for VectorF2 { + type Output = VectorF2; + + fn neg(self) -> VectorF2 { + VectorF2(-self.0, -self.1) + } +} + +impl std::fmt::Display for VectorF2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({}, {})", self.0, self.1) + } +} diff --git a/inserter/src/algorithm/data/vector_f3.rs b/inserter/src/algorithm/data/vector_f3.rs new file mode 100644 index 0000000..e14104a --- /dev/null +++ b/inserter/src/algorithm/data/vector_f3.rs @@ -0,0 +1,235 @@ +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +/// f32-based 3D vector, used for birth point generation +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct VectorF3(pub f32, pub f32, pub f32); + +impl VectorF3 { + pub fn new(x: f32, y: f32, z: f32) -> Self { + Self(x, y, z) + } + + pub const fn zero() -> Self { + Self(0.0, 0.0, 0.0) + } + + pub const fn up() -> Self { + Self(0.0, 1.0, 0.0) + } + + pub const fn forward() -> Self { + Self(0.0, 0.0, 1.0) + } + + pub const fn back() -> Self { + Self(0.0, 0.0, -1.0) + } + + pub const fn right() -> Self { + Self(1.0, 0.0, 0.0) + } + + pub const fn left() -> Self { + Self(-1.0, 0.0, 0.0) + } + + pub const fn down() -> Self { + Self(0.0, -1.0, 0.0) + } + + pub fn magnitude_sq(&self) -> f32 { + self.0 * self.0 + self.1 * self.1 + self.2 * self.2 + } + + pub fn magnitude(&self) -> f32 { + self.magnitude_sq().sqrt() + } + + pub fn normalize(&mut self) -> &mut Self { + let mag = self.magnitude(); + if mag > 1e-10 { + *self /= mag; + } else { + *self = Self::zero(); + } + self + } + + pub fn normalized(&self) -> Self { + let mag = self.magnitude(); + if mag > 1e-10 { + *self / mag + } else { + Self::zero() + } + } + + pub fn distance_sq_from(&self, pt: &Self) -> f32 { + let dx = pt.0 - self.0; + let dy = pt.1 - self.1; + let dz = pt.2 - self.2; + dx * dx + dy * dy + dz * dz + } + + pub fn cross(a: &Self, b: &Self) -> Self { + Self( + a.1 * b.2 - a.2 * b.1, + a.2 * b.0 - a.0 * b.2, + a.0 * b.1 - a.1 * b.0, + ) + } + + pub fn dot(a: &Self, b: &Self) -> f32 { + a.0 * b.0 + a.1 * b.1 + a.2 * b.2 + } + + pub fn slerp(lhs: &VectorF3, rhs: &VectorF3, percent: f32) -> VectorF3 { + let dot = VectorF3::dot(lhs, rhs).clamp(-1.0, 1.0); + let theta = dot.acos() * percent; + let mut relative_vec = rhs - &(lhs * dot); + relative_vec.normalize(); + &(lhs * theta.cos()) + &(&relative_vec * theta.sin()) + } +} + +impl Add for VectorF3 { + type Output = VectorF3; + + fn add(self, rhs: VectorF3) -> VectorF3 { + VectorF3(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) + } +} + +impl Add<&VectorF3> for VectorF3 { + type Output = VectorF3; + + fn add(self, rhs: &VectorF3) -> VectorF3 { + VectorF3(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) + } +} + +impl Add<&VectorF3> for &VectorF3 { + type Output = VectorF3; + + fn add(self, rhs: &VectorF3) -> VectorF3 { + VectorF3(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) + } +} + +impl AddAssign for VectorF3 { + fn add_assign(&mut self, rhs: VectorF3) { + self.0 += rhs.0; + self.1 += rhs.1; + self.2 += rhs.2; + } +} + +impl AddAssign<&VectorF3> for VectorF3 { + fn add_assign(&mut self, rhs: &VectorF3) { + self.0 += rhs.0; + self.1 += rhs.1; + self.2 += rhs.2; + } +} + +impl Sub for VectorF3 { + type Output = VectorF3; + + fn sub(self, rhs: VectorF3) -> VectorF3 { + VectorF3(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2) + } +} + +impl Sub<&VectorF3> for VectorF3 { + type Output = VectorF3; + + fn sub(self, rhs: &VectorF3) -> VectorF3 { + VectorF3(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2) + } +} + +impl Sub<&VectorF3> for &VectorF3 { + type Output = VectorF3; + + fn sub(self, rhs: &VectorF3) -> VectorF3 { + VectorF3(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2) + } +} + +impl SubAssign for VectorF3 { + fn sub_assign(&mut self, rhs: VectorF3) { + self.0 -= rhs.0; + self.1 -= rhs.1; + self.2 -= rhs.2; + } +} + +impl SubAssign<&VectorF3> for VectorF3 { + fn sub_assign(&mut self, rhs: &VectorF3) { + self.0 -= rhs.0; + self.1 -= rhs.1; + self.2 -= rhs.2; + } +} + +impl Mul for VectorF3 { + type Output = VectorF3; + + fn mul(self, rhs: f32) -> VectorF3 { + VectorF3(self.0 * rhs, self.1 * rhs, self.2 * rhs) + } +} + +impl Mul for &VectorF3 { + type Output = VectorF3; + + fn mul(self, rhs: f32) -> VectorF3 { + VectorF3(self.0 * rhs, self.1 * rhs, self.2 * rhs) + } +} + +impl MulAssign for VectorF3 { + fn mul_assign(&mut self, rhs: f32) { + self.0 *= rhs; + self.1 *= rhs; + self.2 *= rhs; + } +} + +impl DivAssign for VectorF3 { + fn div_assign(&mut self, rhs: f32) { + self.0 /= rhs; + self.1 /= rhs; + self.2 /= rhs; + } +} + +impl Div for VectorF3 { + type Output = VectorF3; + + fn div(self, rhs: f32) -> VectorF3 { + VectorF3(self.0 / rhs, self.1 / rhs, self.2 / rhs) + } +} + +impl Div for &VectorF3 { + type Output = VectorF3; + + fn div(self, rhs: f32) -> VectorF3 { + VectorF3(self.0 / rhs, self.1 / rhs, self.2 / rhs) + } +} + +impl Neg for VectorF3 { + type Output = VectorF3; + + fn neg(self) -> VectorF3 { + VectorF3(-self.0, -self.1, -self.2) + } +} + +impl std::fmt::Display for VectorF3 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({}, {}, {})", self.0, self.1, self.2) + } +} diff --git a/inserter/src/algorithm/data/vein.rs b/inserter/src/algorithm/data/vein.rs index b51c282..1fc81a8 100644 --- a/inserter/src/algorithm/data/vein.rs +++ b/inserter/src/algorithm/data/vein.rs @@ -3,7 +3,7 @@ use serde::Serialize; #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct Vein { +pub struct EstimatedVein { pub vein_type: VeinType, pub min_group: i32, pub max_group: i32, @@ -13,7 +13,7 @@ pub struct Vein { pub max_amount: i32, } -impl Default for Vein { +impl Default for EstimatedVein { fn default() -> Self { Self { vein_type: VeinType::None, @@ -27,7 +27,7 @@ impl Default for Vein { } } -impl Vein { +impl EstimatedVein { pub fn new() -> Self { Default::default() } @@ -44,3 +44,19 @@ impl Vein { / 8i64 } } + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActualVein { + pub vein_type: VeinType, + pub amount: i32, // times 4e-5 for oil +} + +impl Default for ActualVein { + fn default() -> Self { + Self { + vein_type: VeinType::None, + amount: 0, + } + } +} diff --git a/inserter/src/algorithm/mod.rs b/inserter/src/algorithm/mod.rs index d271c82..e2567f7 100644 --- a/inserter/src/algorithm/mod.rs +++ b/inserter/src/algorithm/mod.rs @@ -1,2 +1,2 @@ pub mod data; -pub mod worldgen; \ No newline at end of file +pub mod worldgen; diff --git a/inserter/src/algorithm/tests/mod.rs b/inserter/src/algorithm/tests/mod.rs new file mode 100644 index 0000000..9410494 --- /dev/null +++ b/inserter/src/algorithm/tests/mod.rs @@ -0,0 +1 @@ +pub mod worldgen_test; diff --git a/inserter/src/algorithm/tests/worldgen_test.rs b/inserter/src/algorithm/tests/worldgen_test.rs new file mode 100644 index 0000000..e345868 --- /dev/null +++ b/inserter/src/algorithm/tests/worldgen_test.rs @@ -0,0 +1,28 @@ +#[cfg(test)] +mod tests { + use std::cell::Cell; + + use crate::data::game_desc::GameDesc; + use crate::worldgen::galaxy_gen::create_galaxy; + + #[test] + fn test_worldgen() { + let game = GameDesc { + star_count: 64, + resource_multiplier: 1.0, + hive_initial_colonize: 1.0, + hive_max_density: 1.0, + use_actual_veins: true, + }; + let habitable_count = Cell::new(0_i32); + let galaxy = create_galaxy(1, &game, &habitable_count); + let _result = galaxy + .stars + .get(0) + .unwrap() + .get_planets() + .get(3) + .unwrap() + .get_actual_veins(); + } +} diff --git a/inserter/src/algorithm/worldgen/galaxy_gen.rs b/inserter/src/algorithm/worldgen/galaxy_gen.rs index ba827b5..998d6e7 100644 --- a/inserter/src/algorithm/worldgen/galaxy_gen.rs +++ b/inserter/src/algorithm/worldgen/galaxy_gen.rs @@ -3,33 +3,29 @@ use crate::algorithm::data::enums::{SpectrType, StarType}; use crate::algorithm::data::galaxy::Galaxy; use crate::algorithm::data::game_desc::GameDesc; use crate::algorithm::data::random::DspRandom; +use crate::algorithm::data::rule::{Evaluation, Rule}; use crate::algorithm::data::star::Star; use crate::algorithm::data::star_planets::StarWithPlanets; use crate::algorithm::data::vector3::Vector3; +use std::cell::Cell; use std::rc::Rc; -fn generate_temp_poses( - seed: i32, - target_count: usize, - iter_count: usize, - min_dist: f64, - min_step_len: f64, - max_step_len: f64, - flatten: f64, -) -> Vec { - let mut tmp_poses: Vec = vec![]; - let actual_iter_count = iter_count.clamp(1, 16); - random_poses( - &mut tmp_poses, - seed, - target_count * actual_iter_count, - min_dist, - max_step_len - min_step_len, - flatten, - ); +const ITER_COUNT: usize = 4; +const MIN_DIST: f64 = 2.0; +const MIN_DIST_SQ: f64 = MIN_DIST * MIN_DIST; +const STEP_DIFF: f64 = 3.5 - 2.3; +const FLATTEN: f64 = 0.18; +const MIN_DRUNK_NUM: i32 = 6; +const MAX_DRUNK_NUM: i32 = 8; +const DRUNK_NUM_RANGE: f64 = (MAX_DRUNK_NUM - MIN_DRUNK_NUM) as f64; + +fn generate_temp_poses(seed: i32, target_count: usize) -> Vec { + let max_count = target_count * ITER_COUNT; + let mut tmp_poses = Vec::with_capacity(max_count); + random_poses(&mut tmp_poses, seed, max_count); for index in (0..tmp_poses.len()).rev() { - if index % iter_count != 0 { + if index % ITER_COUNT != 0 { tmp_poses.remove(index); } if tmp_poses.len() <= target_count { @@ -40,35 +36,25 @@ fn generate_temp_poses( tmp_poses } -fn random_poses( - tmp_poses: &mut Vec, - seed: i32, - max_count: usize, - min_dist: f64, - step_diff: f64, - flatten: f64, -) { +fn random_poses(tmp_poses: &mut Vec, seed: i32, max_count: usize) { let mut rand = DspRandom::new(seed); - let num1 = rand.next_f64(); - let mut tmp_drunk: Vec = vec![]; + let drunk_walk_count_rand = rand.next_f64(); + let mut tmp_drunk: Vec = Vec::with_capacity(max_count); tmp_poses.push(Vector3::zero()); - let num2 = 6; - let num3 = 8; - let num4 = (num3 - num2) as f64; - let num5 = (num1 * num4 + (num2 as f64)) as i32; - for _ in 0..num5 { + let drunk_num = (drunk_walk_count_rand * DRUNK_NUM_RANGE + (MIN_DRUNK_NUM as f64)) as i32; + for _ in 0..drunk_num { for _ in 0..256 { - let num7 = rand.next_f64() * 2.0 - 1.0; - let num8 = (rand.next_f64() * 2.0 - 1.0) * flatten; - let num9 = rand.next_f64() * 2.0 - 1.0; - let num10 = rand.next_f64(); - let d = num7 * num7 + num8 * num8 + num9 * num9; - if d <= 1.0 && d >= 1e-8 { - let num11 = d.sqrt(); - let num12 = (num10 * step_diff + min_dist) / num11; - let pt = Vector3(num7 * num12, num8 * num12, num9 * num12); - if !check_collision(tmp_poses, &pt, min_dist) { - tmp_drunk.push(pt.clone()); + let u = rand.next_f64() * 2.0 - 1.0; + let w = (rand.next_f64() * 2.0 - 1.0) * FLATTEN; + let v = rand.next_f64() * 2.0 - 1.0; + let first_step_len_rand = rand.next_f64(); + let squared_length = u * u + w * w + v * v; + if squared_length <= 1.0 && squared_length >= 1e-8 { + let distance = squared_length.sqrt(); + let step_len_mult = (first_step_len_rand * STEP_DIFF + MIN_DIST) / distance; + let pt = Vector3(u * step_len_mult, w * step_len_mult, v * step_len_mult); + if !check_collision(tmp_poses, &pt) { + tmp_drunk.push(pt); tmp_poses.push(pt); if tmp_poses.len() >= max_count { return; @@ -82,21 +68,21 @@ fn random_poses( for pt in tmp_drunk.iter_mut() { if rand.next_f64() <= 0.7 { for _ in 0..256 { - let num15 = rand.next_f64() * 2.0 - 1.0; - let num16 = (rand.next_f64() * 2.0 - 1.0) * flatten; - let num17 = rand.next_f64() * 2.0 - 1.0; - let num18 = rand.next_f64(); - let d = num15 * num15 + num16 * num16 + num17 * num17; - if d <= 1.0 && d >= 1e-8 { - let num19 = d.sqrt(); - let num20 = (num18 * step_diff + min_dist) / num19; + let u = rand.next_f64() * 2.0 - 1.0; + let w = (rand.next_f64() * 2.0 - 1.0) * FLATTEN; + let v = rand.next_f64() * 2.0 - 1.0; + let step_len_rand = rand.next_f64(); + let squared_length2 = u * u + w * w + v * v; + if squared_length2 <= 1.0 && squared_length2 >= 1e-8 { + let distance = squared_length2.sqrt(); + let step_len_mult = (step_len_rand * STEP_DIFF + MIN_DIST) / distance; let new_pt = Vector3( - pt.0 + num15 * num20, - pt.1 + num16 * num20, - pt.2 + num17 * num20, + pt.0 + u * step_len_mult, + pt.1 + w * step_len_mult, + pt.2 + v * step_len_mult, ); - if !check_collision(tmp_poses, &new_pt, min_dist) { - *pt = new_pt.clone(); + if !check_collision(tmp_poses, &new_pt) { + *pt = new_pt; tmp_poses.push(new_pt); if tmp_poses.len() >= max_count { return; @@ -110,100 +96,122 @@ fn random_poses( } } -fn check_collision(tmp_poses: &Vec, pt: &Vector3, min_dist: f64) -> bool { - let min_dist_sq = min_dist * min_dist; +fn check_collision(tmp_poses: &Vec, pt: &Vector3) -> bool { tmp_poses .iter() - .any(|pt1| pt1.distance_sq_from(pt) < min_dist_sq) + .any(|existing_point| existing_point.distance_sq_from(pt) < MIN_DIST_SQ) } -fn generate_stars(game_desc: &GameDesc) -> Vec { - let galaxy_seed = game_desc.seed; - - let mut rand = DspRandom::new(galaxy_seed); - let tmp_poses = generate_temp_poses( - rand.next_seed(), - game_desc.star_count, - 4, - 2.0, - 2.3, - 3.5, - 0.18, - ); +fn generate_stars<'a>( + seed: i32, + game_desc: &'a GameDesc, + habitable_count: &'a Cell, +) -> Vec> { + let mut rand = DspRandom::new(seed); + let tmp_poses = generate_temp_poses(rand.next_seed(), game_desc.star_count); let star_count = tmp_poses.len(); - let num1 = rand.next_f32(); - let num2 = rand.next_f32(); - let num3 = rand.next_f32(); - let num4 = rand.next_f32(); - let num5 = (0.01 * (star_count as f64) + (num1 as f64) * 0.3).ceil() as usize; - let num6 = (0.01 * (star_count as f64) + (num2 as f64) * 0.3).ceil() as usize; - let num7 = (0.016 * (star_count as f64) + (num3 as f64) * 0.4).ceil() as usize; - let num8 = (0.013 * (star_count as f64) + (num4 as f64) * 1.3).ceil() as usize; - let num9 = star_count - num5; - let num10 = num9 - num6; - let num11 = num10 - num7; - let num12 = (num11 - 1) / num8; - let num13 = num12 / 2; + let black_hole_count_rand = rand.next_f32(); + let neutron_star_count_rand = rand.next_f32(); + let white_dwarf_count_rand = rand.next_f32(); + let giant_star_count_rand = rand.next_f32(); + let black_hole_num = ((0.01 * (star_count as f64) + (black_hole_count_rand as f64) * 0.3) + as f32) + .ceil() as usize; + let neutro_star_num = ((0.01 * (star_count as f64) + (neutron_star_count_rand as f64) * 0.3) + as f32) + .ceil() as usize; + let white_dwarf_num = ((0.016 * (star_count as f64) + (white_dwarf_count_rand as f64) * 0.4) + as f32) + .ceil() as usize; + let giant_star_num = ((0.013 * (star_count as f64) + (giant_star_count_rand as f64) * 1.4) + as f32) + .ceil() as usize; + let black_hole_start = star_count - black_hole_num; + let neutron_star_start = black_hole_start - neutro_star_num; + let white_dwarf_start = neutron_star_start - white_dwarf_num; + let giant_group_num = (white_dwarf_start - 1) / giant_star_num; + let giant_offset = giant_group_num / 2; - let mut stars: Vec = vec![]; + let mut stars: Vec = Vec::with_capacity(star_count); for (index, position) in tmp_poses.into_iter().enumerate() { let seed = rand.next_seed(); if index == 0 { - stars.push(StarWithPlanets::new(Rc::new(Star::new( + stars.push(StarWithPlanets::new( + Rc::new(Star::new( + game_desc, + 0, + seed, + Vector3::zero(), + StarType::MainSeqStar, + &SpectrType::X, + )), game_desc, - 0, - seed, - Vector3::zero(), - StarType::MainSeqStar, - &SpectrType::X, - )))); + habitable_count, + )); } else { let need_spectr = if index == 3 { SpectrType::M - } else if index == num11 - 1 { + } else if index == white_dwarf_start - 1 { SpectrType::O } else { SpectrType::X }; - let need_type = if index % num12 == num13 { - StarType::GiantStar - } else if index >= num9 { + let need_type = if index >= black_hole_start { StarType::BlackHole - } else if index >= num10 { + } else if index >= neutron_star_start { StarType::NeutronStar - } else if index >= num11 { + } else if index >= white_dwarf_start { StarType::WhiteDwarf + } else if index % giant_group_num == giant_offset { + StarType::GiantStar } else { StarType::MainSeqStar }; - stars.push(StarWithPlanets::new(Rc::new(Star::new( + stars.push(StarWithPlanets::new( + Rc::new(Star::new( + game_desc, + index, + seed, + position, + need_type, + &need_spectr, + )), game_desc, - index, - seed, - position, - need_type, - &need_spectr, - )))); + habitable_count, + )); } } stars } -pub fn create_galaxy(game_desc: &GameDesc) -> Galaxy { - let mut stars = generate_stars(game_desc); - let mut names: Vec<&str> = vec![]; +pub fn create_galaxy<'a>( + seed: i32, + game_desc: &'a GameDesc, + habitable_count: &'a Cell, +) -> Galaxy<'a> { + let mut stars = generate_stars(seed, game_desc, habitable_count); + let mut names: Vec<&str> = Vec::with_capacity(game_desc.star_count); for sp in stars.iter_mut() { let name = random_name(sp.star.name_seed, &sp.star, names.iter()); sp.name = name; - sp.load_planets(); names.push(&sp.name); + sp.load_planets(); } - Galaxy { - seed: game_desc.seed, - stars, - } + Galaxy { seed, stars } +} + +pub fn find_stars(seed: i32, game_desc: &GameDesc, rule: &Box) -> u64 { + let habitable_count = Cell::new(0_i32); + let galaxy = Galaxy { + seed, + stars: generate_stars(seed, game_desc, &habitable_count), + }; + + let evaluation = Evaluation::new(game_desc.star_count); + let result = rule.evaluate(&galaxy, &evaluation); + result } diff --git a/inserter/src/generate_csv.rs b/inserter/src/generate_csv.rs index 6a7b176..1b404c5 100644 --- a/inserter/src/generate_csv.rs +++ b/inserter/src/generate_csv.rs @@ -3,15 +3,13 @@ use crate::algorithm::data::enums::{PlanetType, ORES}; use crate::algorithm::data::game_desc::GameDesc; use crate::algorithm::worldgen::galaxy_gen::create_galaxy; -pub fn gen_formatted(seed: i32, star_count: usize, resource_multiplier: f32) -> Result<(String, String), Box> { - let mut game_desc: GameDesc = GameDesc::default(); - game_desc.seed = seed; - game_desc.star_count = star_count; - game_desc.resource_multiplier = resource_multiplier; - let galaxy = create_galaxy(&game_desc); +pub fn gen_formatted(seed: i32) -> Result<(String, String), Box> { + let game_desc: GameDesc = GameDesc::default(); + let hab_count = std::cell::Cell::new(0i32); + let galaxy = create_galaxy(seed, &game_desc, &hab_count); - let mut stars: String = String::with_capacity(star_count * 128); - let mut planets: String = String::with_capacity(star_count * 256 * 5); + let mut stars: String = String::with_capacity(64 * 128); + let mut planets: String = String::with_capacity(64 * 256 * 5); for solar_system in galaxy.stars { let star = solar_system.star.clone(); @@ -25,7 +23,7 @@ pub fn gen_formatted(seed: i32, star_count: usize, resource_multiplier: f32) -> star.get_luminosity(), star.get_dyson_radius(), star.star_type as i32 + 1, - *star.get_spectr() as i32 + star.get_spectr() as i32 ).as_str()); for (index, ore) in ORES[1..15].iter().enumerate() { @@ -82,12 +80,12 @@ pub fn gen_formatted(seed: i32, star_count: usize, resource_multiplier: f32) -> planet.get_rotation_period() == planet.get_orbital_period() ).as_str()); - let veins = planet.get_veins(); + let veins = planet.get_estimated_veins(); // OPTIMIZATION: Use fixed-size array indexed by VeinType discriminant instead of HashMap. // VeinType is #[repr(i32)] with variants None=0..Max=15, so a 16-element array gives // O(1) lookup with zero heap allocation, vs HashMap which allocates per-planet. // Impact: Eliminates ~256K HashMap allocations per 1000 seeds in the hot loop. - let mut vein_map: [Option<&data::vein::Vein>; 16] = [None; 16]; + let mut vein_map: [Option<&data::vein::EstimatedVein>; 16] = [None; 16]; for v in veins.iter() { vein_map[v.vein_type as usize] = Some(v); } diff --git a/inserter/src/threads.rs b/inserter/src/threads.rs index 2bf3baf..ecc4f61 100644 --- a/inserter/src/threads.rs +++ b/inserter/src/threads.rs @@ -11,7 +11,7 @@ use crate::misc::{COPY_PLANET, COPY_STAR}; pub fn worker_thread(seeds: Range, send: Sender<(String, String)>, id: usize) -> Result<()> { for seed in seeds { - let entry = gen_formatted(seed, STAR_COUNT, REC_MULTIPLIER).expect("gen_formatted failed"); + let entry = gen_formatted(seed).expect("gen_formatted failed"); send.send(entry)?; PROGRESS_WORKERS[id].fetch_add(1, Relaxed); diff --git a/inserter/src/wasm.rs b/inserter/src/wasm.rs new file mode 100644 index 0000000..cc75385 --- /dev/null +++ b/inserter/src/wasm.rs @@ -0,0 +1,79 @@ +#![cfg(target_arch = "wasm32")] + +mod data; +mod rules; +mod transform_rules; +mod worldgen; + +use data::game_desc::GameDesc; +use serde::Serialize; +use std::cell::Cell; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::spawn_local; +use worldgen::galaxy_gen::{create_galaxy, find_stars}; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = worldgen)] + async fn found(value: JsValue) -> JsValue; +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn generate(seed: JsValue, gameDesc: JsValue) -> Result { + let seed: i32 = serde_wasm_bindgen::from_value(seed)?; + let game_desc: GameDesc = serde_wasm_bindgen::from_value(gameDesc)?; + let habitable_count = Cell::new(0_i32); + let galaxy = create_galaxy(seed, &game_desc, &habitable_count); + galaxy.serialize(&serde_wasm_bindgen::Serializer::json_compatible()) +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn searchStar( + seed: JsValue, + gameDesc: JsValue, + rule: JsValue, +) -> Result { + let seed: i32 = serde_wasm_bindgen::from_value(seed)?; + let game_desc: GameDesc = serde_wasm_bindgen::from_value(gameDesc)?; + let rule = serde_wasm_bindgen::from_value(rule).unwrap(); + let transformed = transform_rules::transform_rules(rule); + let star_indexes = find_stars(seed, &game_desc, &transformed); + let indexes: Vec = (0..64) + .filter(|&i| (star_indexes & (1_u64 << i)) != 0) + .collect(); + serde_wasm_bindgen::to_value(&indexes) +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn findStars(gameDesc: JsValue, rule: JsValue, seeds: JsValue) { + spawn_local(async { + let game_desc: GameDesc = serde_wasm_bindgen::from_value(gameDesc).unwrap(); + let mut seeds: Vec = serde_wasm_bindgen::from_value(seeds).unwrap(); + let rule = serde_wasm_bindgen::from_value(rule).unwrap(); + let transformed = transform_rules::transform_rules(rule); + loop { + let mut results: Vec = vec![]; + for seed in seeds { + let star_indexes = find_stars(seed, &game_desc, &transformed); + if star_indexes != 0 { + results.push(seed); + } + } + let result = serde_wasm_bindgen::to_value(&results).unwrap(); + let next_batch: JsValue = found(result).await; + let next_seeds: Result, serde_wasm_bindgen::Error> = + serde_wasm_bindgen::from_value(next_batch); + match next_seeds { + Ok(f) => { + seeds = f; + } + Err(_) => { + break; + } + } + } + }) +} From a6b240bcfba1b07bc9ad9ff45ef80be3d07b20e6 Mon Sep 17 00:00:00 2001 From: SuperB3333 Date: Sat, 11 Jul 2026 16:45:24 +0200 Subject: [PATCH 2/2] Copied over new algorithm and adapted the old and new code to work together --- inserter/src/algorithm/data/birth_points.rs | 139 ++ inserter/src/algorithm/data/enums.rs | 24 +- inserter/src/algorithm/data/game_desc.rs | 33 +- inserter/src/algorithm/data/macros.rs | 38 - inserter/src/algorithm/data/math.rs | 77 + inserter/src/algorithm/data/mod.rs | 14 +- inserter/src/algorithm/data/planet.rs | 1388 ++++++++++++----- .../algorithm/data/planet_algorithms/algo0.rs | 22 + .../algorithm/data/planet_algorithms/algo1.rs | 83 + .../data/planet_algorithms/algo10.rs | 243 +++ .../data/planet_algorithms/algo11.rs | 99 ++ .../data/planet_algorithms/algo12.rs | 104 ++ .../data/planet_algorithms/algo13.rs | 81 + .../data/planet_algorithms/algo14.rs | 169 ++ .../algorithm/data/planet_algorithms/algo2.rs | 72 + .../algorithm/data/planet_algorithms/algo3.rs | 140 ++ .../algorithm/data/planet_algorithms/algo4.rs | 113 ++ .../algorithm/data/planet_algorithms/algo5.rs | 120 ++ .../algorithm/data/planet_algorithms/algo6.rs | 137 ++ .../algorithm/data/planet_algorithms/algo7.rs | 85 + .../algorithm/data/planet_algorithms/algo8.rs | 61 + .../algorithm/data/planet_algorithms/algo9.rs | 135 ++ .../algorithm/data/planet_algorithms/mod.rs | 71 + inserter/src/algorithm/data/planet_grid.rs | 166 ++ .../src/algorithm/data/planet_raw_data.rs | 75 + inserter/src/algorithm/data/pose.rs | 16 + inserter/src/algorithm/data/quaternion.rs | 209 +++ inserter/src/algorithm/data/random.rs | 60 +- inserter/src/algorithm/data/random_table.rs | 63 + inserter/src/algorithm/data/rule.rs | 141 ++ inserter/src/algorithm/data/simplex_noise.rs | 253 +++ inserter/src/algorithm/data/star.rs | 700 ++++++--- inserter/src/algorithm/data/star_planets.rs | 285 ++-- inserter/src/algorithm/data/theme_proto.rs | 105 +- inserter/src/algorithm/data/vector2.rs | 4 + inserter/src/algorithm/data/vector3.rs | 12 +- inserter/src/algorithm/data/vector_f2.rs | 204 +++ inserter/src/algorithm/data/vector_f3.rs | 235 +++ inserter/src/algorithm/data/vein.rs | 22 +- inserter/src/algorithm/mod.rs | 2 +- inserter/src/algorithm/tests/mod.rs | 1 + inserter/src/algorithm/tests/worldgen_test.rs | 28 + inserter/src/algorithm/worldgen/galaxy_gen.rs | 242 +-- inserter/src/generate_csv.rs | 26 +- inserter/src/main.rs | 2 + inserter/src/threads.rs | 2 +- inserter/src/wasm.rs | 79 + 47 files changed, 5397 insertions(+), 983 deletions(-) create mode 100644 inserter/src/algorithm/data/birth_points.rs delete mode 100644 inserter/src/algorithm/data/macros.rs create mode 100644 inserter/src/algorithm/data/math.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo0.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo1.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo10.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo11.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo12.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo13.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo14.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo2.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo3.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo4.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo5.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo6.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo7.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo8.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/algo9.rs create mode 100644 inserter/src/algorithm/data/planet_algorithms/mod.rs create mode 100644 inserter/src/algorithm/data/planet_grid.rs create mode 100644 inserter/src/algorithm/data/planet_raw_data.rs create mode 100644 inserter/src/algorithm/data/pose.rs create mode 100644 inserter/src/algorithm/data/quaternion.rs create mode 100644 inserter/src/algorithm/data/random_table.rs create mode 100644 inserter/src/algorithm/data/rule.rs create mode 100644 inserter/src/algorithm/data/simplex_noise.rs create mode 100644 inserter/src/algorithm/data/vector2.rs create mode 100644 inserter/src/algorithm/data/vector_f2.rs create mode 100644 inserter/src/algorithm/data/vector_f3.rs create mode 100644 inserter/src/algorithm/tests/mod.rs create mode 100644 inserter/src/algorithm/tests/worldgen_test.rs create mode 100644 inserter/src/wasm.rs diff --git a/inserter/src/algorithm/data/birth_points.rs b/inserter/src/algorithm/data/birth_points.rs new file mode 100644 index 0000000..5b2e9d7 --- /dev/null +++ b/inserter/src/algorithm/data/birth_points.rs @@ -0,0 +1,139 @@ +use super::random::DspRandom; +use super::vector_f3::VectorF3; +use crate::algorithm::data::planet_raw_data::PlanetRawData; + +/// Return value for gen_birth_points, containing the three birth positions. +#[derive(Debug, Clone)] +pub struct BirthPoints { + /// Main spawn point on the planet surface + pub birth_point: VectorF3, + /// First resource point + pub birth_resource_point0: VectorF3, + /// Second resource point + pub birth_resource_point1: VectorF3, +} + +impl BirthPoints { + pub fn new( + raw_data: &mut PlanetRawData, + birth_seed: i32, + radius: f32, + star_direction: VectorF3, + ) -> Self { + // ---- main GenBirthPoints(PlanetRawData, int) body (C# lines 761-821) -- + let mut rand = DspRandom::new(birth_seed); + + // vector3_1 is the incoming star_direction (already normalised) + let star_dir = star_direction.normalized(); + + // C#: Vector3 normalized1 = Vector3.Cross(vector3_1, Vector3.up).normalized + let mut basis1 = VectorF3::cross(&star_dir, &VectorF3::up()).normalized(); + // C#: Vector3 normalized2 = Vector3.Cross(normalized1, vector3_1).normalized + let mut basis2 = VectorF3::cross(&basis1, &star_dir).normalized(); + + let num2 = 256; + + // Outer loop – try up to 256 candidate birth directions + for _ in 0..num2 { + // C#: float num3 = (float)(dotNet35Random.NextDouble() * 2.0 - 1.0) * 0.5f + let num3 = (rand.next_f64() * 2.0 - 1.0) as f32 * 0.5; + // C#: float num4 = (float)(dotNet35Random.NextDouble() * 2.0 - 1.0) * 0.5f + let num4 = (rand.next_f64() * 2.0 - 1.0) as f32 * 0.5; + + // C#: Vector3 vector3_2 = vector3_1 + num3 * normalized1 + num4 * normalized2 + let mut candidate = star_dir + basis1 * num3 + basis2 * num4; + candidate.normalize(); + + // C#: this.birthPoint = vector3_2 * (realRadius + 0.2f + 1.45f); + // 0.2f + 1.45f = 1.65f, but keep the original constants for clarity + let birth_point = candidate * (radius + 0.2 + 1.45); + + // C#: Vector3 vector3_3 = Vector3.Cross(vector3_2, Vector3.up); + // normalized1 = vector3_3.normalized; + let cross_tmp = VectorF3::cross(&candidate, &VectorF3::up()); + basis1 = cross_tmp.normalized(); + // C#: vector3_3 = Vector3.Cross(normalized1, vector3_2); + // normalized2 = vector3_3.normalized; + let cross_tmp2 = VectorF3::cross(&basis1, &candidate); + basis2 = cross_tmp2.normalized(); + + // Inner loop – try up to 10 resource-point offsets + for _ in 0..10 { + // C#: Vector2(x, y).normalized * 0.1f + let v2x = (rand.next_f64() * 2.0 - 1.0) as f32; + let v2y = (rand.next_f64() * 2.0 - 1.0) as f32; + let v2len = (v2x * v2x + v2y * v2y).sqrt(); + let (v2x, v2y) = if v2len > 1e-10 { + (v2x / v2len * 0.1, v2y / v2len * 0.1) + } else { + (0.0, 0.0) + }; + + // C#: Vector2 vector2_2 = -vector2_1; + let v2x2 = -v2x; + let v2y2 = -v2y; + + // C#: num5 = (float)(dotNet35Random.NextDouble() * 2.0 - 1.0) * 0.06f + let num5 = (rand.next_f64() * 2.0 - 1.0) as f32 * 0.06; + // C#: num6 = (float)(dotNet35Random.NextDouble() * 2.0 - 1.0) * 0.06f + let num6 = (rand.next_f64() * 2.0 - 1.0) as f32 * 0.06; + + let v2x2 = v2x2 + num5; + let v2y2 = v2y2 + num6; + + // C#: normalized3 = (vector3_2 + vector2_1.x * normalized1 + vector2_1.y * normalized2).normalized + let rp0_dir = (candidate + basis1 * v2x + basis2 * v2y).normalized(); + + // C#: normalized4 = (vector3_2 + vector2_2.x * normalized1 + vector2_2.y * normalized2).normalized + let rp1_dir = (candidate + basis1 * v2x2 + basis2 * v2y2).normalized(); + + // height threshold + let height_threshold = radius + 0.2; + + // Use normalized variant since candidate, rp0_dir, rp1_dir are already unit-length + if raw_data.query_height_normalized(&candidate) > height_threshold + && raw_data.query_height_normalized(&rp0_dir) > height_threshold + && raw_data.query_height_normalized(&rp1_dir) > height_threshold + { + // C#: check 8 surrounding offsets + let vpos1 = rp0_dir + basis1 * 0.03; + let vpos2 = rp0_dir - basis1 * 0.03; + let vpos3 = rp0_dir + basis2 * 0.03; + let vpos4 = rp0_dir - basis2 * 0.03; + let vpos5 = rp1_dir + basis1 * 0.03; + let vpos6 = rp1_dir - basis1 * 0.03; + let vpos7 = rp1_dir + basis2 * 0.03; + let vpos8 = rp1_dir - basis2 * 0.03; + + // Offset vectors are not unit-length; use the normalising variant + if raw_data.query_height(&vpos1) > height_threshold + && raw_data.query_height(&vpos2) > height_threshold + && raw_data.query_height(&vpos3) > height_threshold + && raw_data.query_height(&vpos4) > height_threshold + && raw_data.query_height(&vpos5) > height_threshold + && raw_data.query_height(&vpos6) > height_threshold + && raw_data.query_height(&vpos7) > height_threshold + && raw_data.query_height(&vpos8) > height_threshold + { + // Re‑normalise both resource-point directions + let rp0 = rp0_dir.normalized(); + let rp1 = rp1_dir.normalized(); + + return Self { + birth_point, + birth_resource_point0: rp0, + birth_resource_point1: rp1, + }; + } + } + } + } + + // ---- fallback (C# line 820) ------------------------------------------- + Self { + birth_point: VectorF3(0.0, radius + 5.0, 0.0), + birth_resource_point0: VectorF3::up(), + birth_resource_point1: VectorF3::down(), + } + } +} diff --git a/inserter/src/algorithm/data/enums.rs b/inserter/src/algorithm/data/enums.rs index 8982942..67e0095 100644 --- a/inserter/src/algorithm/data/enums.rs +++ b/inserter/src/algorithm/data/enums.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; +#[allow(dead_code)] #[repr(i32)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)] pub enum StarType { @@ -15,6 +16,7 @@ impl Default for StarType { Self::MainSeqStar } } + #[allow(dead_code)] #[repr(i32)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)] @@ -34,7 +36,7 @@ pub enum SpectrType { #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)] pub enum PlanetType { None, - Vocano, + Volcano, Ocean, Desert, Ice, @@ -93,18 +95,18 @@ impl Default for VeinType { impl VeinType { pub fn is_rare(&self) -> bool { - match self { + matches!( + self, VeinType::Fireice - | VeinType::Diamond - | VeinType::Fractal - | VeinType::Crysrub - | VeinType::Grat - | VeinType::Bamboo => true, - _ => false, - } + | VeinType::Diamond + | VeinType::Fractal + | VeinType::Crysrub + | VeinType::Grat + | VeinType::Bamboo + | VeinType::Mag + ) } } - pub const ORES: [VeinType; 16] = [ VeinType::None, VeinType::Iron, @@ -122,4 +124,4 @@ pub const ORES: [VeinType; 16] = [ VeinType::Bamboo, VeinType::Mag, VeinType::Max, -]; +]; \ No newline at end of file diff --git a/inserter/src/algorithm/data/game_desc.rs b/inserter/src/algorithm/data/game_desc.rs index b259ac9..c075c98 100644 --- a/inserter/src/algorithm/data/game_desc.rs +++ b/inserter/src/algorithm/data/game_desc.rs @@ -1,18 +1,18 @@ -use std::cell::Cell; - use serde::{Deserialize, Serialize}; -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct GameDesc { - #[serde(default)] - pub seed: i32, #[serde(default = "GameDesc::default_star_count")] pub star_count: usize, #[serde(default = "GameDesc::default_resource_multiplier")] pub resource_multiplier: f32, - #[serde(skip)] - pub habitable_count: Cell, + #[serde(default = "GameDesc::default_hive_initial_colonize")] + pub hive_initial_colonize: f64, + #[serde(default = "GameDesc::default_hive_max_density")] + pub hive_max_density: f64, + #[serde(default)] + pub use_actual_veins: bool, } impl GameDesc { @@ -22,6 +22,12 @@ impl GameDesc { pub fn default_resource_multiplier() -> f32 { 1.0 } + pub fn default_hive_initial_colonize() -> f64 { + 1.0 + } + pub fn default_hive_max_density() -> f64 { + 1.0 + } pub fn is_infinite_resource(&self) -> bool { self.resource_multiplier >= 99.5 @@ -31,7 +37,7 @@ impl GameDesc { self.resource_multiplier <= 0.1001 } - pub fn oil_amount_multipler(&self) -> f32 { + pub fn oil_amount_multiplier(&self) -> f32 { if self.is_rare_resource() { 0.5 } else { @@ -47,3 +53,14 @@ impl GameDesc { } } } +impl Default for GameDesc { + fn default() -> Self { + Self { + star_count: 64, + resource_multiplier: 1.0, + hive_initial_colonize: 1.0, + hive_max_density: 1.0, + use_actual_veins: false + } + } +} \ No newline at end of file diff --git a/inserter/src/algorithm/data/macros.rs b/inserter/src/algorithm/data/macros.rs deleted file mode 100644 index eec6484..0000000 --- a/inserter/src/algorithm/data/macros.rs +++ /dev/null @@ -1,38 +0,0 @@ -#[macro_use] -pub mod macros { - macro_rules! lazy_getter { - ($self:ident, $name:ident, $t:ty, $b:block) => { - pub fn $name(&$self) -> $t { - if let Some(val) = unsafe { *$self.$name.get() } { - val - } else { - let val = (|| $b)(); - unsafe { - let r = $self.$name.get(); - *r = Some(val); - (&*r).unwrap_unchecked() - } - } - } - }; - } - pub(crate) use lazy_getter; - - macro_rules! lazy_getter_ref { - ($self:ident, $name:ident, $t:ty, $b:block) => { - pub fn $name(&$self) -> &$t { - if let Some(val) = unsafe { &*$self.$name.get() }.as_ref() { - val - } else { - let val = (|| $b)(); - unsafe { - let r = $self.$name.get(); - *r = Some(val); - (&*r).as_ref().unwrap_unchecked() - } - } - } - }; - } - pub(crate) use lazy_getter_ref; -} diff --git a/inserter/src/algorithm/data/math.rs b/inserter/src/algorithm/data/math.rs new file mode 100644 index 0000000..825d8e1 --- /dev/null +++ b/inserter/src/algorithm/data/math.rs @@ -0,0 +1,77 @@ +/// Mathematical utility functions ported from Maths.cs +/// These are used by PlanetAlgorithm implementations. + +/// Clamp a value between min and max (inclusive). +#[inline] +pub fn clamp(value: T, min: T, max: T) -> T { + if value < min { + min + } else if value > max { + max + } else { + value + } +} + +/// Clamp a value between 0.0 and 1.0. +#[inline] +pub fn clamp01(value: f64) -> f64 { + clamp(value, 0.0, 1.0) +} + +/// C# Levelize: smoothstep-based levelize +/// f = f / level - offset; +/// num1 = floor(f); +/// num2 = f - num1; +/// num3 = (3.0 - num2 - num2) * num2 * num2; (smoothstep) +/// f = num1 + num3; +/// f = (f + offset) * level; +#[inline] +pub fn levelize(f: f64, level: f64, offset: f64) -> f64 { + let f = f / level - offset; + let num1 = f.floor(); + let num2 = f - num1; + let num3 = (3.0 - num2 - num2) * num2 * num2; + let f = num1 + num3; + (f + offset) * level +} + +/// C# Levelize2: smoothstep applied twice (smoother steps) +#[inline] +pub fn levelize2(f: f64, level: f64, offset: f64) -> f64 { + let f = f / level - offset; + let num1 = f.floor(); + let num2 = f - num1; + let num3 = (3.0 - num2 - num2) * num2 * num2; + let num4 = (3.0 - num3 - num3) * num3 * num3; + let f = num1 + num4; + (f + offset) * level +} + +/// C# Levelize3: smoothstep applied three times (even smoother steps) +#[inline] +pub fn levelize3(f: f64, level: f64, offset: f64) -> f64 { + let f = f / level - offset; + let num1 = f.floor(); + let num2 = f - num1; + let num3 = (3.0 - num2 - num2) * num2 * num2; + let num4 = (3.0 - num3 - num3) * num3 * num3; + let num5 = (3.0 - num4 - num4) * num4 * num4; + let f = num1 + num5; + (f + offset) * level +} + +/// C# Levelize4: smoothstep applied four times (even smoother steps) +/// Uses floor() for correct negative-value handling. +#[inline] +pub fn levelize4(f: f64, level: f64, offset: f64) -> f64 { + let f = f / level - offset; + let num1 = f.floor(); + let num2 = f - num1; + let num3 = (3.0 - num2 - num2) * num2 * num2; + let num4 = (3.0 - num3 - num3) * num3 * num3; + let num5 = (3.0 - num4 - num4) * num4 * num4; + let num6 = (3.0 - num5 - num5) * num5 * num5; + let f = num1 + num6; + (f + offset) * level +} diff --git a/inserter/src/algorithm/data/mod.rs b/inserter/src/algorithm/data/mod.rs index 89b49dc..1665ad4 100644 --- a/inserter/src/algorithm/data/mod.rs +++ b/inserter/src/algorithm/data/mod.rs @@ -1,11 +1,23 @@ +pub mod birth_points; pub mod enums; pub mod galaxy; pub mod game_desc; -pub mod macros; +pub mod math; pub mod planet; +pub mod planet_algorithms; +pub mod planet_grid; +pub mod planet_raw_data; +pub mod pose; +pub mod quaternion; pub mod random; +pub mod random_table; +pub mod rule; +pub mod simplex_noise; pub mod star; pub mod star_planets; pub mod theme_proto; +pub mod vector2; pub mod vector3; +pub mod vector_f2; +pub mod vector_f3; pub mod vein; diff --git a/inserter/src/algorithm/data/planet.rs b/inserter/src/algorithm/data/planet.rs index 76b51ed..e00bec3 100644 --- a/inserter/src/algorithm/data/planet.rs +++ b/inserter/src/algorithm/data/planet.rs @@ -1,21 +1,28 @@ +use crate::algorithm::data::birth_points::BirthPoints; +use crate::algorithm::data::game_desc::GameDesc; +use crate::algorithm::data::planet_raw_data::PlanetRawData; +use crate::algorithm::data::vector_f2::VectorF2; + use super::enums::{PlanetType, SpectrType, StarType, ThemeDistribute, VeinType}; -use super::macros::macros::{lazy_getter, lazy_getter_ref}; +use super::pose::Pose; +use super::quaternion::Quaternion; use super::random::DspRandom; use super::star::Star; use super::theme_proto::{ThemeProto, THEME_PROTOS}; -use super::vein::Vein; +use super::vector_f3::VectorF3; +use super::vein::{ActualVein, EstimatedVein}; use serde::ser::{Serialize, SerializeStruct, Serializer}; -use std::cell::{RefCell, UnsafeCell}; +use std::cell::{Cell, OnceCell, RefCell}; use std::f64::consts::PI; use std::rc::Rc; -#[allow(dead_code)] #[derive(Debug)] pub struct Planet<'a> { + game_desc: &'a GameDesc, pub star: Rc>, pub index: usize, + habitable_count: &'a Cell, pub seed: i32, - pub info_seed: i32, pub theme_seed: i32, pub orbit_around: RefCell>>, pub orbit_index: usize, @@ -33,22 +40,21 @@ pub struct Planet<'a> { pub orbit_phase: f32, pub rotation_phase: f32, theme_rand1: f64, - get_orbital_radius: UnsafeCell>, - get_sun_distance: UnsafeCell>, - get_temperature_factor: UnsafeCell>, - get_habitable_bias: UnsafeCell>, - get_temperature_bias: UnsafeCell>, - get_luminosity: UnsafeCell>, - get_unmodified_planet_type: UnsafeCell>, - get_orbit_inclination: UnsafeCell>, - get_sun_orbital_period: UnsafeCell>, - get_orbital_period: UnsafeCell>, - get_obliquity: UnsafeCell>, - get_eligible_for_resonance: UnsafeCell>, - get_rotation_period: UnsafeCell>, - get_theme: UnsafeCell>, - get_gases: UnsafeCell>>, - get_veins: UnsafeCell>>, + theme_rand2: f64, + theme_rand3: f64, + theme_rand4: f64, + orbital_radius: OnceCell, + sun_distance: OnceCell, + temperature_factor: OnceCell, + orbital_period: OnceCell, + obliquity: OnceCell, + eligible_for_resonance: OnceCell, + rotation_period: OnceCell, + theme: OnceCell<&'static ThemeProto>, + gases: OnceCell>, + estimated_veins: OnceCell>, + actual_veins: OnceCell>, + theme_algo_id: OnceCell, } const ORBIT_RADIUS: &'static [f32] = &[ @@ -57,8 +63,10 @@ const ORBIT_RADIUS: &'static [f32] = &[ impl<'a> Planet<'a> { pub fn new( + game_desc: &'a GameDesc, star: Rc>, index: usize, + habitable_count: &'a Cell, orbit_index: usize, gas_giant: bool, info_seed: i32, @@ -66,26 +74,26 @@ impl<'a> Planet<'a> { ) -> Self { let mut rand = DspRandom::new(info_seed); - let num3 = rand.next_f64(); - let num4 = rand.next_f64(); - let orbit_radius_factor = num3 * (num4 - 0.5) * 0.5; + let orbit_radius_rand1 = rand.next_f64(); + let orbit_radius_rand2 = rand.next_f64(); + let orbit_radius_factor = orbit_radius_rand1 * (orbit_radius_rand2 - 0.5) * 0.5; let orbit_inclination_factor = rand.next_f64(); let orbit_longitude = (rand.next_f64() * 360.0) as f32; let orbit_phase = (rand.next_f64() * 360.0) as f32; - let num8 = rand.next_f64(); - let num9 = rand.next_f64(); - let obliquity_scale = num8 * (num9 - 0.5); - let num10 = rand.next_f64(); - let num11 = rand.next_f64(); - let rotation_scale = num10 * num11 * 1000.0 + 400.0; + let obliquity_rand1 = rand.next_f64(); + let obliquity_rand2 = rand.next_f64(); + let obliquity_scale = obliquity_rand1 * (obliquity_rand2 - 0.5); + let rotation_rand1 = rand.next_f64(); + let rotation_rand2 = rand.next_f64(); + let rotation_scale = rotation_rand1 * rotation_rand2 * 1000.0 + 400.0; let rotation_phase = (rand.next_f64() * 360.0) as f32; let habitable_factor = rand.next_f64(); let type_factor = rand.next_f64(); let theme_rand1 = rand.next_f64(); let rotation_param = rand.next_f64(); - rand.next_f64(); - rand.next_f64(); - rand.next_f64(); + let theme_rand2 = rand.next_f64(); + let theme_rand3 = rand.next_f64(); + let theme_rand4 = rand.next_f64(); let theme_seed = rand.next_seed(); let (radius, scale) = if gas_giant { @@ -95,10 +103,11 @@ impl<'a> Planet<'a> { }; Self { + game_desc, star, index, + habitable_count, seed: gen_seed, - info_seed, theme_seed, orbit_around: RefCell::new(None), orbit_index, @@ -108,6 +117,9 @@ impl<'a> Planet<'a> { orbit_phase, rotation_phase, theme_rand1, + theme_rand2, + theme_rand3, + theme_rand4, obliquity_scale, rotation_param, rotation_scale, @@ -116,22 +128,18 @@ impl<'a> Planet<'a> { habitable_factor, type_factor, gas_giant, - get_orbital_radius: UnsafeCell::new(None), - get_sun_distance: UnsafeCell::new(None), - get_temperature_factor: UnsafeCell::new(None), - get_habitable_bias: UnsafeCell::new(None), - get_temperature_bias: UnsafeCell::new(None), - get_luminosity: UnsafeCell::new(None), - get_unmodified_planet_type: UnsafeCell::new(None), - get_orbit_inclination: UnsafeCell::new(None), - get_sun_orbital_period: UnsafeCell::new(None), - get_orbital_period: UnsafeCell::new(None), - get_obliquity: UnsafeCell::new(None), - get_eligible_for_resonance: UnsafeCell::new(None), - get_rotation_period: UnsafeCell::new(None), - get_theme: UnsafeCell::new(None), - get_gases: UnsafeCell::new(None), - get_veins: UnsafeCell::new(None), + orbital_radius: OnceCell::new(), + sun_distance: OnceCell::new(), + temperature_factor: OnceCell::new(), + orbital_period: OnceCell::new(), + obliquity: OnceCell::new(), + eligible_for_resonance: OnceCell::new(), + rotation_period: OnceCell::new(), + theme: OnceCell::new(), + gases: OnceCell::new(), + estimated_veins: OnceCell::new(), + theme_algo_id: OnceCell::new(), + actual_veins: OnceCell::new(), } } @@ -151,67 +159,73 @@ impl<'a> Planet<'a> { self.orbit_around.borrow().is_some() } - lazy_getter!(self, get_orbital_radius, f32, { - let a = 1.2_f32.powf(self.orbit_radius_factor as f32); - if let Some(orbit_planet) = self.orbit_around.borrow().as_deref() { - (((1600.0 * (self.orbit_index as f64) + 200.0) - * (self.star.get_orbit_scaler().powf(0.3) as f64) - * ((a + (1.0 - a) * 0.5) as f64) - + (orbit_planet.real_radius() as f64)) - / 40000.0) as f32 - } else { - let b = ORBIT_RADIUS[self.orbit_index as usize] * self.star.get_orbit_scaler(); - let num16 = (((a - 1.0) as f64) / (b.max(1.0) as f64) + 1.0) as f32; - b * num16 - } - }); + pub fn get_orbital_radius(&self) -> f32 { + *self.orbital_radius.get_or_init(|| { + let a = 1.2_f32.powf(self.orbit_radius_factor as f32); + if let Some(orbit_planet) = self.orbit_around.borrow().as_deref() { + (((1600.0 * (self.orbit_index as f64) + 200.0) + * (self.star.get_orbit_scaler().powf(0.3) as f64) + * ((a + (1.0 - a) * 0.5) as f64) + + (orbit_planet.real_radius() as f64)) + / 40000.0) as f32 + } else { + let b = ORBIT_RADIUS[self.orbit_index] * self.star.get_orbit_scaler(); + let adjusted_orbit_radius = (((a - 1.0) as f64) / (b.max(1.0) as f64) + 1.0) as f32; + b * adjusted_orbit_radius + } + }) + } - lazy_getter!(self, get_sun_distance, f32, { - if let Some(orbit_planet) = self.orbit_around.borrow().as_deref() { - orbit_planet.get_orbital_radius() - } else { - self.get_orbital_radius() - } - }); + pub fn get_sun_distance(&self) -> f32 { + *self.sun_distance.get_or_init(|| { + if let Some(orbit_planet) = self.orbit_around.borrow().as_deref() { + orbit_planet.get_orbital_radius() + } else { + self.get_orbital_radius() + } + }) + } - lazy_getter!(self, get_temperature_factor, f32, { - if self.is_gas_giant() { - 0.0 - } else { - let habitable_radius = self.star.get_habitable_radius(); - if habitable_radius > 0.0 { - self.get_sun_distance() / habitable_radius + pub fn get_temperature_factor(&self) -> f32 { + *self.temperature_factor.get_or_init(|| { + if self.is_gas_giant() { + 0.0 } else { - 1000.0 + let habitable_radius = self.star.get_habitable_radius(); + if habitable_radius > 0.0 { + self.get_sun_distance() / habitable_radius + } else { + 1000.0 + } } - } - }); + }) + } - lazy_getter!(self, get_habitable_bias, f32, { + fn get_habitable_bias(&self) -> f32 { if self.is_gas_giant() { 1000.0 } else { let habitable_radius = self.star.get_habitable_radius(); - let num21 = if habitable_radius > 0.0 { + let distance_log_factor = if habitable_radius > 0.0 { (self.get_sun_distance() / habitable_radius).ln().abs() } else { 1000.0 }; - let num22 = habitable_radius.sqrt().clamp(1.0, 2.0) - 0.04; - num21 * num22 + let habitable_radius_sqrt_clamped = habitable_radius.sqrt().clamp(1.0, 2.0) - 0.04; + distance_log_factor * habitable_radius_sqrt_clamped } - }); + } - lazy_getter!(self, get_temperature_bias, f32, { + fn get_temperature_bias(&self) -> f32 { if self.is_gas_giant() { 0.0 } else { - let f2 = self.get_temperature_factor(); - (1.2 / ((f2 as f64) + 0.2) - 1.0) as f32 + let temperature_factor_val = self.get_temperature_factor(); + (1.2 / ((temperature_factor_val as f64) + 0.2) - 1.0) as f32 } - }); + } - lazy_getter!(self, get_luminosity, f32, { + fn get_luminosity(&self) -> f32 { let mut luminosity = (self.star.get_light_balance_radius() / (self.get_sun_distance() + 0.01)).powf(0.6); if luminosity > 1.0 { @@ -219,18 +233,14 @@ impl<'a> Planet<'a> { luminosity = luminosity.ln() + 1.0; luminosity = luminosity.ln() + 1.0; } - (luminosity * 100.0).round() / 100.0 - }); + (luminosity * 100.0).round_ties_even() / 100.0 + } fn increment_habitable_count(&self) { - self.star - .game_desc - .habitable_count - .set(self.star.game_desc.habitable_count.get() + 1); + self.habitable_count.set(self.habitable_count.get() + 1); } - lazy_getter_ref!(self, get_unmodified_planet_type, PlanetType, { - // can only be called once and in order + fn get_unmodified_planet_type(&self) -> PlanetType { if self.is_gas_giant() { PlanetType::Gas } else if self.is_birth() { @@ -239,48 +249,48 @@ impl<'a> Planet<'a> { } else { let f2 = self.get_temperature_factor(); if !self.star.is_birth() { - let star_count = self.star.game_desc.star_count; - let num18 = ((star_count as f32) * 0.29).ceil().max(11.0); - let num19 = (num18 as f64) - (self.star.game_desc.habitable_count.get() as f64); - let num20 = (star_count - self.star.index) as f32; - let num23 = num20 as f64; - let a = (num19 / num23) as f32; - let num24 = (a + (0.35 - a) * 0.5).clamp(0.08, 0.8); - let num25 = (self.get_habitable_bias() / num24) + let star_count = self.game_desc.star_count; + let habitable_ceiling = ((star_count as f32) * 0.29).ceil().max(11.0); + let remaining_habitable_slots = + (habitable_ceiling as f64) - (self.habitable_count.get() as f64); + let remaining_stars = (star_count - self.star.index) as f32; + let remaining_stars_f64 = remaining_stars as f64; + let remaining_ratio = (remaining_habitable_slots / remaining_stars_f64) as f32; + let allocation_probability = + (remaining_ratio + (0.35 - remaining_ratio) * 0.5).clamp(0.08, 0.8); + let habitable_bias_threshold = (self.get_habitable_bias() / allocation_probability) .clamp(0.0, 1.1) - .powf(num24 * 10.0); - if self.habitable_factor > (num25 as f64) { + .powf(allocation_probability * 10.0); + if self.habitable_factor > (habitable_bias_threshold as f64) { self.increment_habitable_count(); return PlanetType::Ocean; } } - if f2 < 5.0 / 6.0 { - let num26 = ((f2 as f64) * 2.5 - 0.85).max(0.15); - if self.type_factor >= num26 { - PlanetType::Vocano + let volcano_type_threshold = ((f2 as f64) * 2.5 - 0.85).max(0.15); + if self.type_factor >= volcano_type_threshold { + PlanetType::Volcano } else { PlanetType::Desert } } else if f2 < 1.2 { PlanetType::Desert } else { - let num27 = 0.9 / (f2 as f64) - 0.1; - if self.type_factor >= num27 { + let ice_type_threshold = 0.9 / (f2 as f64) - 0.1; + if self.type_factor >= ice_type_threshold { PlanetType::Ice } else { PlanetType::Desert } } } - }); + } - #[allow(dead_code)] pub fn is_tidal_locked(&self) -> bool { self.get_rotation_period() == self.get_orbital_period() } - lazy_getter!(self, get_orbit_inclination, f32, { + fn get_orbit_inclination(&self) -> f32 { let mut orbit_inclination = (self.orbit_inclination_factor * 16.0 - 8.0) as f32; if self.has_orbit_around() { orbit_inclination *= 2.2; @@ -293,357 +303,925 @@ impl<'a> Planet<'a> { } } orbit_inclination - }); + } - lazy_getter!(self, get_sun_orbital_period, f64, { + fn get_sun_orbital_period(&self) -> f64 { if let Some(orbit_planet) = self.orbit_around.borrow().as_deref() { orbit_planet.get_orbital_period() } else { self.get_orbital_period() } - }); - - lazy_getter!(self, get_orbital_period, f64, { - const FOUR_PI_SQUARE: f64 = 4.0 * PI * PI; - let f1 = self.get_orbital_radius() as f64; - (FOUR_PI_SQUARE * f1 * f1 * f1 - / (if self.has_orbit_around() { - 1.08308421068537e-08 // cannot figure out what this is, probably related to Kepler's third law - } else { - 1.35385519905204e-06 * (self.star.get_mass() as f64) - })) - .sqrt() - }); - - lazy_getter!(self, get_obliquity, f32, { - let mut obliquity: f32; - if self.rotation_param < 0.04 { - obliquity = (self.obliquity_scale * 39.9) as f32; - if obliquity < 0.0 { - obliquity -= 70.0; - } else { - obliquity += 70.0; - } - } else if self.rotation_param < 0.1 { - obliquity = (self.obliquity_scale * 80.0) as f32; - if obliquity < 0.0 { - obliquity -= 30.0; + } + + pub fn get_orbital_period(&self) -> f64 { + *self.orbital_period.get_or_init(|| { + const FOUR_PI_SQUARE: f64 = 4.0 * PI * PI; + let orbital_radius_f64 = self.get_orbital_radius() as f64; + (FOUR_PI_SQUARE * orbital_radius_f64 * orbital_radius_f64 * orbital_radius_f64 + / (if self.has_orbit_around() { + 1.08308421068537e-08 + } else { + 1.35385519905204e-06 * (self.star.get_mass() as f64) + })) + .sqrt() + }) + } + + pub fn get_obliquity(&self) -> f32 { + *self.obliquity.get_or_init(|| { + let mut obliquity: f32; + if self.rotation_param < 0.04 { + obliquity = (self.obliquity_scale * 39.9) as f32; + if obliquity < 0.0 { + obliquity -= 70.0; + } else { + obliquity += 70.0; + } + } else if self.rotation_param < 0.1 { + obliquity = (self.obliquity_scale * 80.0) as f32; + if obliquity < 0.0 { + obliquity -= 30.0; + } else { + obliquity += 30.0; + } } else { - obliquity += 30.0; + obliquity = (self.obliquity_scale * 60.0) as f32; + if self.get_eligible_for_resonance() { + if self.rotation_param > 0.96 { + obliquity *= 0.01; + } else if self.rotation_param > 0.93 { + obliquity *= 0.1; + } else if self.rotation_param > 0.9 { + obliquity *= 0.2; + } + } } - } else { - obliquity = (self.obliquity_scale * 60.0) as f32; + obliquity + }) + } + + pub fn get_eligible_for_resonance(&self) -> bool { + *self.eligible_for_resonance.get_or_init(|| { + let gas_giant = self.is_gas_giant(); + !self.has_orbit_around() && self.orbit_index <= 4 && !gas_giant + }) + } + + pub fn get_rotation_period(&self) -> f64 { + *self.rotation_period.get_or_init(|| { if self.get_eligible_for_resonance() { if self.rotation_param > 0.96 { - obliquity *= 0.01; + return self.get_orbital_period(); } else if self.rotation_param > 0.93 { - obliquity *= 0.1; + return self.get_orbital_period() * 0.5; } else if self.rotation_param > 0.9 { - obliquity *= 0.2; + return self.get_orbital_period() * 0.25; } } - } - - obliquity - }); - - lazy_getter!(self, get_eligible_for_resonance, bool, { - let gas_giant = self.is_gas_giant(); - !self.has_orbit_around() && self.orbit_index <= 4 && !gas_giant - }); - - lazy_getter!(self, get_rotation_period, f64, { - if self.get_eligible_for_resonance() { - if self.rotation_param > 0.96 { - return self.get_orbital_period(); - } else if self.rotation_param > 0.93 { - return self.get_orbital_period() * 0.5; - } else if self.rotation_param > 0.9 { - return self.get_orbital_period() * 0.25; + let gas_giant = self.is_gas_giant(); + let mut rotation_period = self.rotation_scale + * (if gas_giant { + 0.2 + } else { + match self.star.star_type { + StarType::WhiteDwarf => 0.5, + StarType::NeutronStar => 0.2, + StarType::BlackHole => 0.15, + _ => 1.0, + } + }) + * (if self.has_orbit_around() { + 1.0 + } else { + self.get_orbital_radius().powf(0.25) as f64 + }); + rotation_period = 1.0 / (1.0 / self.get_sun_orbital_period() + 1.0 / rotation_period); + if self.rotation_param > 0.85 && self.rotation_param <= 0.9 { + rotation_period = -rotation_period; } - } - - let gas_giant = self.is_gas_giant(); - let mut rotation_period = self.rotation_scale - * (if gas_giant { - 0.2 - } else { - match self.star.star_type { - StarType::WhiteDwarf => 0.5, - StarType::NeutronStar => 0.2, - StarType::BlackHole => 0.15, - _ => 1.0, - } - }) - * (if self.has_orbit_around() { - 1.0 - } else { - self.get_orbital_radius().powf(0.25) as f64 - }); - - rotation_period = 1.0 / (1.0 / self.get_sun_orbital_period() + 1.0 / rotation_period); + rotation_period + }) + } - if self.rotation_param > 0.85 && self.rotation_param <= 0.9 { - rotation_period = -rotation_period; - } - rotation_period - }); - - lazy_getter!(self, get_theme, &'static ThemeProto, { - // can only be called once and in order - let mut potential_themes: Vec<&'static ThemeProto> = vec![]; - let mut used_theme_ids = self.star.used_theme_ids.borrow_mut(); - let unused_themes: Vec<&'static ThemeProto> = THEME_PROTOS - .iter() - .filter(|&theme| !used_theme_ids.contains(&theme.id)) - .collect(); - - let planet_type = self.get_unmodified_planet_type(); - let temperature_bias = self.get_temperature_bias(); - - for theme in &unused_themes { - if self.star.is_birth() && planet_type == &PlanetType::Ocean { - if theme.distribute == ThemeDistribute::Birth { - potential_themes.push(theme); - } - } else { - let flag2 = - if theme.temperature.abs() < 0.5 && theme.planet_type == PlanetType::Desert { + pub fn get_theme(&self) -> &'static ThemeProto { + self.theme.get_or_init(|| { + let mut potential_themes: Vec<&'static ThemeProto> = Vec::new(); + let mut used_theme_ids = self.star.used_theme_ids.borrow_mut(); + let unused_themes: Vec<&'static ThemeProto> = THEME_PROTOS + .iter() + .filter(|&theme| !used_theme_ids.contains(&theme.id)) + .collect(); + let planet_type = self.get_unmodified_planet_type(); + let temperature_bias = self.get_temperature_bias(); + for theme in &unused_themes { + if self.star.is_birth() && planet_type == PlanetType::Ocean { + if theme.distribute == ThemeDistribute::Birth { + potential_themes.push(theme); + } + } else { + let temperature_matches = if theme.temperature.abs() < 0.5 + && theme.planet_type == PlanetType::Desert + { (temperature_bias.abs() as f64) < (theme.temperature.abs() as f64) + 0.1 } else { (theme.temperature as f64) * (temperature_bias as f64) >= -0.1 }; - if (theme.planet_type == *planet_type) && flag2 { - if self.star.is_birth() { - if theme.distribute == ThemeDistribute::Default { + if (theme.planet_type == planet_type) && temperature_matches { + if self.star.is_birth() { + if theme.distribute == ThemeDistribute::Default { + potential_themes.push(theme); + } + } else if theme.distribute == ThemeDistribute::Default + || theme.distribute == ThemeDistribute::Interstellar + { potential_themes.push(theme); } - } else if theme.distribute == ThemeDistribute::Default - || theme.distribute == ThemeDistribute::Interstellar - { - potential_themes.push(theme); } } } - } - - if potential_themes.is_empty() { - for theme in &unused_themes { - if theme.planet_type == PlanetType::Desert { - potential_themes.push(theme); + if potential_themes.is_empty() { + for theme in &unused_themes { + if theme.planet_type == PlanetType::Desert { + potential_themes.push(theme); + } } } - } - if potential_themes.is_empty() { - for theme in &*THEME_PROTOS { - if theme.planet_type == PlanetType::Desert { - potential_themes.push(theme); + if potential_themes.is_empty() { + for theme in &*THEME_PROTOS { + if theme.planet_type == PlanetType::Desert { + potential_themes.push(theme); + } } } - } - let theme_proto = potential_themes[((self.theme_rand1 * (potential_themes.len() as f64)) - as usize) - % potential_themes.len()]; - used_theme_ids.push(theme_proto.id); - theme_proto - }); + let theme_proto = potential_themes[((self.theme_rand1 * (potential_themes.len() as f64)) + as usize) + % potential_themes.len()]; + used_theme_ids.push(theme_proto.id); + theme_proto + }) + } - pub fn get_type(&self) -> &PlanetType { - &self.get_theme().planet_type + pub fn get_algo_id(&self) -> i32 { + *self.theme_algo_id.get_or_init(|| { + let theme = self.get_theme(); + if theme.algos.is_empty() { + 0 + } else { + *theme + .algos + .get( + (self.theme_rand2 * (theme.algos.len() as f64)) as usize + % theme.algos.len(), + ) + .unwrap() + } + }) } - lazy_getter_ref!(self, get_gases, Vec<(i32, f32)>, { - let mut gases: Vec<(i32, f32)> = vec![]; - if !self.is_gas_giant() { - return gases; + pub fn get_mod_x(&self) -> f64 { + let theme = self.get_theme(); + if theme.algos.is_empty() { + 0.0 + } else { + theme.mod_x.0 + self.theme_rand3 * (theme.mod_x.1 - theme.mod_x.0) } - let gas_coef = self.star.game_desc.gas_coef(); - let mut rand = DspRandom::new(self.theme_seed); - - let theme_proto = self.get_theme(); - let coef = self.star.get_resource_coef().powf(0.3); - - for (item, speed) in theme_proto - .gas_items - .iter() - .zip(theme_proto.gas_speeds.iter()) - { - let num2 = speed * (rand.next_f32() * 21.0 / 110.0 + 10.0 / 11.0) * gas_coef; - gases.push((*item, num2 * coef)) + } + + pub fn get_mod_y(&self) -> f64 { + let theme = self.get_theme(); + if theme.algos.is_empty() { + 0.0 + } else { + theme.mod_y.0 + self.theme_rand4 * (theme.mod_y.1 - theme.mod_y.0) } - gases - }); + } + + pub fn get_type(&self) -> &PlanetType { + &self.get_theme().planet_type + } + pub fn get_gases(&self) -> &Vec<(i32, f32)> { + self.gases.get_or_init(|| { + if !self.is_gas_giant() { + return Vec::with_capacity(0); + } + let mut gases: Vec<(i32, f32)> = Vec::with_capacity(2); + let gas_coef = self.game_desc.gas_coef(); + let mut rand = DspRandom::new(self.theme_seed); + let theme_proto = self.get_theme(); + let coef = self.star.get_resource_coef().powf(0.3); + for (item, speed) in theme_proto + .gas_items + .iter() + .zip(theme_proto.gas_speeds.iter()) + { + let num2 = speed * (rand.next_f32() * 21.0 / 110.0 + 10.0 / 11.0) * gas_coef; + gases.push((*item, num2 * coef)) + } + gases + }) + } - lazy_getter_ref!(self, get_veins, Vec, { - let mut output: Vec = vec![]; + pub fn can_have_vein(&self, vein_type: &VeinType) -> bool { + let theme = self.get_theme(); if self.is_gas_giant() { - return output; + false + } else if vein_type.is_rare() { + theme.rare_veins.contains(vein_type) + || match self.star.star_type { + StarType::BlackHole | StarType::NeutronStar => vein_type == &VeinType::Mag, + StarType::WhiteDwarf => { + matches!( + vein_type, + VeinType::Diamond | VeinType::Fractal | VeinType::Grat + ) + } + _ => false, + } + } else { + let vein_index = *vein_type as i32; + if let Some(x) = theme.vein_spot.get((vein_index - 1) as usize) { + *x != 0 + } else { + false + } } - let mut rand1 = DspRandom::new(self.seed); - rand1.next_f64(); - rand1.next_f64(); - rand1.next_f64(); - rand1.next_f64(); - rand1.next_f64(); - rand1.next_f64(); - let theme_proto = self.get_theme(); - let mut num_array_1: Vec = (0..15_i32) - .map(|i| *theme_proto.vein_spot.get((i - 1) as usize).unwrap_or(&0)) - .collect(); - let mut num_array_2: Vec = (0..15_i32) - .map(|i| *theme_proto.vein_count.get((i - 1) as usize).unwrap_or(&0.0)) - .collect(); - let mut num_array_3: Vec = (0..15_i32) - .map(|i| { - *theme_proto - .vein_opacity - .get((i - 1) as usize) - .unwrap_or(&0.0) - }) - .collect(); - - let mut add_until = |i: &mut i32, t: f64| { - for _ in 1..12 { - if rand1.next_f64() >= t { - break; + } + + pub fn get_estimated_veins(&self) -> &Vec { + self.estimated_veins.get_or_init(|| { + if self.is_gas_giant() { + return Vec::with_capacity(0); + } + let mut output: Vec = Vec::with_capacity(14); + let mut rand1 = DspRandom::new(self.seed); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + let theme_proto = self.get_theme(); + let mut vein_spots: Vec = (0..15_i32) + .map(|i| *theme_proto.vein_spot.get((i - 1) as usize).unwrap_or(&0)) + .collect(); + let mut vein_counts: Vec = (0..15_i32) + .map(|i| *theme_proto.vein_count.get((i - 1) as usize).unwrap_or(&0.0)) + .collect(); + let mut vein_opacities: Vec = (0..15_i32) + .map(|i| { + *theme_proto + .vein_opacity + .get((i - 1) as usize) + .unwrap_or(&0.0) + }) + .collect(); + + let mut random_vein_spots = |t: f64| { + for i in 0..11 { + if rand1.next_f64() >= t { + return i; + } + } + 11 + }; + + let star_type_multiplier: f32 = match self.star.star_type { + StarType::MainSeqStar => match self.star.get_spectr() { + SpectrType::M => 2.5, + SpectrType::G => 0.7, + SpectrType::F => 0.6, + SpectrType::B => 0.4, + SpectrType::O => 1.6, + _ => 1.0, + }, + StarType::GiantStar => 2.5, + StarType::WhiteDwarf => { + vein_spots[9] += 2 + random_vein_spots(0.45); + vein_counts[9] = 0.7; + vein_opacities[9] = 1.0; + vein_spots[10] += 2 + random_vein_spots(0.45); + vein_counts[10] = 0.7; + vein_opacities[10] = 1.0; + vein_spots[12] += 1 + random_vein_spots(0.5); + vein_counts[12] = 0.7; + vein_opacities[12] = 0.3; + 3.5 } - *i += 1; + StarType::NeutronStar => { + vein_spots[14] += 1 + random_vein_spots(0.65); + vein_counts[14] = 0.7; + vein_opacities[14] = 0.3; + 4.5 + } + StarType::BlackHole => { + vein_spots[14] += 1 + random_vein_spots(0.65); + vein_counts[14] = 0.7; + vein_opacities[14] = 0.3; + 5.0 + } + }; + let is_rare_resource = self.game_desc.is_rare_resource(); + let mut f = self.star.get_resource_coef(); + if theme_proto.distribute == ThemeDistribute::Birth { + f *= 2.0 / 3.0; + } else if is_rare_resource { + if f > 1.0 { + f = f.powf(0.8) + } + f *= 0.7; } - }; + for (index1, rare_vein_ref) in theme_proto.rare_veins.iter().enumerate() { + let rare_vein = *rare_vein_ref as usize; + let rare_vein_chance = theme_proto.rare_settings + [index1 * 4 + (if self.star.is_birth() { 0 } else { 1 })]; + let rare_setting_1 = theme_proto.rare_settings[index1 * 4 + 2]; + let rare_setting_2 = theme_proto.rare_settings[index1 * 4 + 3]; + let adjusted_rare_chance = + 1.0 - (1.0 - rare_vein_chance).powf(star_type_multiplier); + let adjusted_rare_count = 1.0 - (1.0 - rare_setting_2).powf(star_type_multiplier); + if rand1.next_f64() < (adjusted_rare_chance as f64) { + vein_spots[rare_vein] += 1; + vein_counts[rare_vein] = adjusted_rare_count; + vein_opacities[rare_vein] = adjusted_rare_count; + for _ in 1..12 { + if rand1.next_f64() >= (rare_setting_1 as f64) { + break; + } + vein_spots[rare_vein] += 1; + } + } + } + let is_infinite_resource = self.game_desc.is_infinite_resource(); + for index3 in 1..15 { + let vein_spot_count = vein_spots[index3 as usize]; + if vein_spot_count > 0 { + let vein_type: VeinType = unsafe { ::std::mem::transmute(index3) }; + let mut vein = EstimatedVein::new(); + vein.vein_type = vein_type; + vein.min_group = vein_spot_count - 1; + vein.max_group = vein_spot_count + 1; + if vein.vein_type == VeinType::Oil { + vein.min_patch = 1; + vein.max_patch = 1; + } else { + let vein_count_factor = vein_counts[index3 as usize]; + vein.min_patch = (vein_count_factor * 20.0).round_ties_even() as i32; + vein.max_patch = (vein_count_factor * 24.0).round_ties_even() as i32; + } + let total_amount_factor = if vein.vein_type == VeinType::Oil { + f.powf(0.5) + } else { + f + }; + if is_infinite_resource && vein.vein_type != VeinType::Oil { + vein.min_amount = 1; + vein.max_amount = 1; + } else { + let base_amount = + ((vein_opacities[index3 as usize] * 100000.0 * total_amount_factor) + .round_ties_even() as i32) + .max(20); + let amount_variance = if base_amount < 16000 { + ((base_amount as f32) * (15.0 / 16.0)).floor() as i32 + } else { + 15000 + }; + let map_amount = |amount: i32| -> i32 { + let x1 = ((amount as f32) * 1.1).round_ties_even(); + let x2 = (if vein.vein_type == VeinType::Oil { + x1 * self.game_desc.oil_amount_multiplier() + } else { + x1 * self.game_desc.resource_multiplier + }) + .round_ties_even() as i32; + x2.max(1) + }; + vein.min_amount = map_amount(base_amount - amount_variance); + vein.max_amount = map_amount(base_amount + amount_variance); + } + output.push(vein); + } + } + output + }) + } + + pub fn get_runtime_orbit_rotation(&self) -> Quaternion { + let mut rot = Quaternion::angle_axis(self.orbit_longitude, &VectorF3::up()) + * Quaternion::angle_axis(self.get_orbit_inclination(), &VectorF3::forward()); + if let Some(parent) = self.orbit_around.borrow().as_deref() { + rot = parent.get_runtime_orbit_rotation() * rot; + } + rot + } - let p: f32 = match self.star.star_type { - StarType::MainSeqStar => match self.star.get_spectr() { - SpectrType::M => 2.5, - SpectrType::G => 0.7, - SpectrType::F => 0.6, - SpectrType::B => 0.4, - SpectrType::O => 1.6, - _ => 1.0, - }, - StarType::GiantStar => 2.5, - StarType::WhiteDwarf => { - num_array_1[9] += 2; - add_until(num_array_1.get_mut(9).unwrap(), 0.45); - num_array_2[9] = 0.7; - num_array_3[9] = 1.0; - num_array_1[10] += 2; - add_until(num_array_1.get_mut(10).unwrap(), 0.45); - num_array_2[10] = 0.7; - num_array_3[10] = 1.0; - num_array_1[12] += 1; - add_until(num_array_1.get_mut(12).unwrap(), 0.5); - num_array_2[12] = 0.7; - num_array_3[12] = 0.3; - 3.5 + pub fn get_runtime_system_rotation(&self) -> Quaternion { + self.get_runtime_orbit_rotation() + * Quaternion::angle_axis(self.get_obliquity(), &VectorF3::forward()) + } + + pub fn predict_pose(&self, time: f64) -> Pose { + use std::f64::consts::PI as PI_F64; + + // Orbit angle + let orbit_phase_time = time / self.get_orbital_period() + (self.orbit_phase as f64) / 360.0; + let orbit_cycle = (orbit_phase_time + 0.1) as i32; + let orbit_fraction = orbit_phase_time - (orbit_cycle as f64); + let orbit_angle = orbit_fraction * 2.0 * PI_F64; + + let orbit_radius = self.get_orbital_radius() as f64; + let local_pos = VectorF3( + (orbit_angle.cos() * orbit_radius) as f32, + 0.0, + (orbit_angle.sin() * orbit_radius) as f32, + ); + + let orbit_rot = self.get_runtime_orbit_rotation(); + let mut position = orbit_rot.q_rotate_lf(&local_pos); + + // If this planet orbits another planet, add the parent's position + if let Some(parent) = self.orbit_around.borrow().as_deref() { + let parent_pose = parent.predict_pose(time); + position = VectorF3( + position.0 + parent_pose.position.0, + position.1 + parent_pose.position.1, + position.2 + parent_pose.position.2, + ); + } + + // Rotation angle from time + let rotation_phase_time = + time / self.get_rotation_period() + (self.rotation_phase as f64) / 360.0; + let rotation_cycle = (rotation_phase_time + 0.1) as i32; + let rotation_angle = (rotation_phase_time - (rotation_cycle as f64)) * 360.0; + + let rotation = self.get_runtime_system_rotation() + * Quaternion::angle_axis(rotation_angle as f32, &VectorF3::down()); + + Pose::new(position, rotation) + } + + /// Computes the star direction vector at time 85.0 (used in `gen_birth_points`). + /// + /// Port of C# lines 764-766: + /// ```csharp + /// Pose pose = this.PredictPose(85.0); + /// Vector3 vector3_1 = (Vector3) Maths.QInvRotateLF( + /// pose.rotation, this.star.uPosition - (VectorLF3) pose.position * 40000.0); + /// vector3_1.Normalize(); + /// ``` + /// + /// Where `star.uPosition` = `star.position * 2400000.0`. + pub fn get_star_direction(&self) -> VectorF3 { + let pose = self.predict_pose(85.0); + + // star.uPosition = star.position * 2400000.0 + let star_pos = &self.star.position; // Vector3 (f64) + let star_u_pos = VectorF3( + (star_pos.0 * 2400000.0) as f32, + (star_pos.1 * 2400000.0) as f32, + (star_pos.2 * 2400000.0) as f32, + ); + + // pose.position * 40000.0 + let pose_scaled = VectorF3( + pose.position.0 * 40000.0, + pose.position.1 * 40000.0, + pose.position.2 * 40000.0, + ); + + // star.uPosition - pose.position * 40000.0 + let delta = VectorF3( + star_u_pos.0 - pose_scaled.0, + star_u_pos.1 - pose_scaled.1, + star_u_pos.2 - pose_scaled.2, + ); + + // QInvRotateLF(pose.rotation, delta) then normalize + let mut dir = pose.rotation.q_inv_rotate_lf(&delta); + dir.normalize(); + dir + } + + fn can_place_vein( + &self, + algo_id: i32, + vein_type: &VeinType, + zero: &VectorF3, + raw_data: &mut PlanetRawData, + ) -> bool { + if algo_id == 7 && vein_type != &VeinType::Bamboo { + return true; + } + // `zero` is already normalized by the caller + let height = raw_data.query_height_normalized(zero); + match algo_id { + 7 => height <= self.radius - 4.0, + 11 => { + height >= self.radius + && match vein_type { + &VeinType::Oil => height >= self.radius + 0.5, + &VeinType::Iron | &VeinType::Copper => height <= self.radius + 0.7, + &VeinType::Silicium | &VeinType::Titanium => height > self.radius + 0.7, + _ => true, + } } - StarType::NeutronStar => { - num_array_1[14] += 1; - add_until(num_array_1.get_mut(14).unwrap(), 0.65); - num_array_2[14] = 0.7; - num_array_3[14] = 0.3; - 4.5 + 12 => { + height >= self.radius + && match vein_type { + &VeinType::Oil => height >= self.radius + 0.5, + &VeinType::Fireice => height >= self.radius + 1.2, + _ => true, + } } - StarType::BlackHole => { - num_array_1[14] += 1; - add_until(num_array_1.get_mut(14).unwrap(), 0.65); - num_array_2[14] = 0.7; - num_array_3[14] = 0.3; - 5.0 + 13 => { + height >= self.radius + && match vein_type { + &VeinType::Oil => height >= self.radius + 0.5, + &VeinType::Iron + | &VeinType::Copper + | &VeinType::Silicium + | &VeinType::Titanium => height <= self.radius + 0.7, + _ => true, + } } - }; - let is_rare_resource = self.star.game_desc.is_rare_resource(); - let mut f = self.star.get_resource_coef(); - if theme_proto.distribute == ThemeDistribute::Birth { - f *= 2.0 / 3.0; - } else if is_rare_resource { - if f > 1.0 { - f = f.powf(0.8) + _ => { + height >= self.radius + && match vein_type { + &VeinType::Oil => height >= self.radius + 0.5, + _ => true, + } } - f *= 0.7; } + } - for (index1, rare_vein_ref) in theme_proto.rare_veins.iter().enumerate() { - let rare_vein = rare_vein_ref.clone() as usize; - let num2 = - theme_proto.rare_settings[index1 * 4 + (if self.star.is_birth() { 0 } else { 1 })]; - let rare_setting_1 = theme_proto.rare_settings[index1 * 4 + 2]; - let rare_setting_2 = theme_proto.rare_settings[index1 * 4 + 3]; - let num4 = 1.0 - (1.0 - num2).powf(p); - let num5 = 1.0 - (1.0 - rare_setting_2).powf(p); - if rand1.next_f64() < (num4 as f64) { - num_array_1[rare_vein] += 1; - num_array_2[rare_vein] = num5; - num_array_3[rare_vein] = num5; - for _ in 1..12 { - if rand1.next_f64() >= (rare_setting_1 as f64) { - break; + pub fn get_actual_veins(&self) -> &Vec { + self.actual_veins.get_or_init(|| { + if self.gas_giant { + return Vec::with_capacity(0); + } + + let theme = self.get_theme(); + let mut rand1 = DspRandom::new(self.seed); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + rand1.next_f64(); + let birth_seed = rand1.next_seed(); + let mut rand2 = DspRandom::new(rand1.next_seed()); + let mut vein_spots: Vec = (0..15_i32) + .map(|i| *theme.vein_spot.get((i - 1) as usize).unwrap_or(&0)) + .collect(); + let mut vein_counts: Vec = (0..15_i32) + .map(|i| *theme.vein_count.get((i - 1) as usize).unwrap_or(&0.0)) + .collect(); + let mut vein_opacities: Vec = (0..15_i32) + .map(|i| *theme.vein_opacity.get((i - 1) as usize).unwrap_or(&0.0)) + .collect(); + + let mut random_vein_spots = |t: f64| { + for i in 0..11 { + if rand1.next_f64() >= t { + return i; + } + } + 11 + }; + + let star_type_multiplier: f32 = match self.star.star_type { + StarType::MainSeqStar => match self.star.get_spectr() { + SpectrType::M => 2.5, + SpectrType::G => 0.7, + SpectrType::F => 0.6, + SpectrType::B => 0.4, + SpectrType::O => 1.6, + _ => 1.0, + }, + StarType::GiantStar => 2.5, + StarType::WhiteDwarf => { + vein_spots[9] += 2 + random_vein_spots(0.45); + vein_counts[9] = 0.7; + vein_opacities[9] = 1.0; + vein_spots[10] += 2 + random_vein_spots(0.45); + vein_counts[10] = 0.7; + vein_opacities[10] = 1.0; + vein_spots[12] += 1 + random_vein_spots(0.5); + vein_counts[12] = 0.7; + vein_opacities[12] = 0.3; + 3.5 + } + StarType::NeutronStar => { + vein_spots[14] += 1 + random_vein_spots(0.65); + vein_counts[14] = 0.7; + vein_opacities[14] = 0.3; + 4.5 + } + StarType::BlackHole => { + vein_spots[14] += 1 + random_vein_spots(0.65); + vein_counts[14] = 0.7; + vein_opacities[14] = 0.3; + 5.0 + } + }; + + for (index1, rare_vein_ref) in theme.rare_veins.iter().enumerate() { + let rare_vein = *rare_vein_ref as usize; + let rare_vein_chance = + theme.rare_settings[index1 * 4 + (if self.star.is_birth() { 0 } else { 1 })]; + let rare_setting_1 = theme.rare_settings[index1 * 4 + 2]; + let rare_setting_2 = theme.rare_settings[index1 * 4 + 3]; + let adjusted_rare_chance = + 1.0 - (1.0 - rare_vein_chance).powf(star_type_multiplier); + let adjust_rare_count = 1.0 - (1.0 - rare_setting_2).powf(star_type_multiplier); + if rand1.next_f64() < (adjusted_rare_chance as f64) { + vein_spots[rare_vein] += 1; + vein_counts[rare_vein] = adjust_rare_count; + vein_opacities[rare_vein] = adjust_rare_count; + for _ in 1..12 { + if rand1.next_f64() >= (rare_setting_1 as f64) { + break; + } + vein_spots[rare_vein] += 1; } - num_array_1[rare_vein] += 1; } } - } - let is_infinite_resource = self.star.game_desc.is_infinite_resource(); - for index3 in 1..15 { - let num8 = num_array_1[index3 as usize]; - if num8 > 0 { + let is_rare_resource = self.game_desc.is_rare_resource(); + let mut resource_coef = self.star.get_resource_coef(); + let is_birth_planet = theme.distribute == ThemeDistribute::Birth; + if is_birth_planet { + resource_coef *= 2.0 / 3.0; + } else if is_rare_resource { + if resource_coef > 1.0 { + resource_coef = resource_coef.powf(0.8) + } + resource_coef *= 0.7; + } + let mut vein_vectors: Vec<(VeinType, VectorF3, bool)> = Vec::with_capacity(512); + // Fetch PlanetRawData once and thread it through all query_height calls + let mut raw_data = PlanetRawData::new(&self); + + let birth_point = if is_birth_planet { + let star_direction = self.get_star_direction(); + let birth_point_data = + BirthPoints::new(&mut raw_data, birth_seed, self.radius, star_direction); + vein_vectors.push((VeinType::Iron, birth_point_data.birth_resource_point0, true)); + vein_vectors.push(( + VeinType::Copper, + birth_point_data.birth_resource_point1, + true, + )); + let mut birth_point = birth_point_data.birth_point; + birth_point.normalize(); + birth_point * 0.75 + } else { + let x = rand2.next_f64() * 2.0 - 1.0; + let y = rand2.next_f64() - 0.5; + let z = rand2.next_f64() * 2.0 - 1.0; + let mut birth_point = VectorF3::new(x as f32, y as f32, z as f32); + birth_point.normalize(); + birth_point * (rand2.next_f64() * 0.4 + 0.2) as f32 + }; + + let is_infinite_resource = self.game_desc.is_infinite_resource(); + // Fixed array indexed by VeinType discriminant (0..16) — avoids HashMap hashing overhead + let mut amount_map: [i32; 16] = [0; 16]; + + let min_vein_spacing = 2.1 / self.radius; + let min_vein_spacing_sq = (min_vein_spacing as f64) * (min_vein_spacing as f64); + let algo_id = self.get_algo_id(); + + for index3 in 1..15 { + if vein_vectors.len() >= 512 { + break; + } + let mut vein_spot_count = vein_spots[index3 as usize]; + if vein_spot_count > 1 { + vein_spot_count += rand2.next_i32(3) - 1; + } let vein_type: VeinType = unsafe { ::std::mem::transmute(index3) }; - let mut vein = Vein::new(); - vein.vein_type = vein_type; - vein.min_group = num8 - 1; - vein.max_group = num8 + 1; - if vein.vein_type == VeinType::Oil { - vein.min_patch = 1; - vein.max_patch = 1; + let min_sq_dist = min_vein_spacing_sq + * (if vein_type == VeinType::Oil { + 100_f64 + } else { + 196_f64 + }); + + for _ in 0..vein_spot_count { + for _ in 0..200 { + let x = rand2.next_f64() * 2.0 - 1.0; + let y = rand2.next_f64() * 2.0 - 1.0; + let z = rand2.next_f64() * 2.0 - 1.0; + let mut normal_dir = VectorF3(x as f32, y as f32, z as f32); + if vein_type != VeinType::Oil { + normal_dir += birth_point; + } + normal_dir.normalize(); + if self.can_place_vein(algo_id, &vein_type, &normal_dir, &mut raw_data) { + let not_too_close_to_other_vein = + vein_vectors.iter().all(|(_, pos, _)| { + (pos.distance_sq_from(&normal_dir) as f64) >= min_sq_dist + }); + if not_too_close_to_other_vein { + vein_vectors.push((vein_type, normal_dir, false)); + break; + } + } + } + if vein_vectors.len() >= 512 { + break; + } + } + } + + for (vein_type, vein_vector, is_birth_resource) in vein_vectors.iter() { + let is_oil = vein_type == &VeinType::Oil; + let normalized = vein_vector.normalized(); + let rotation = Quaternion::from_to_rotation(&VectorF3::up(), &normalized); + let right_axis = &rotation * &VectorF3::right(); + let forward_axis = &rotation * &VectorF3::forward(); + let vein_type_index = *vein_type as i32; + let target_node_count = if *is_birth_resource { + rand2.next_f64(); + 6 + } else if is_oil { + rand2.next_f64(); + 1 + } else { + (vein_counts.get(vein_type_index as usize).unwrap() + * (rand2.next_i32(5) + 20) as f32) + .round_ties_even() as usize + }; + let mut vein_nodes = Vec::with_capacity(target_node_count); + vein_nodes.push(VectorF2::zero()); + let vein_density = if *is_birth_resource { + 0.2_f32 } else { - let num12 = num_array_2[index3 as usize]; - vein.min_patch = (num12 * 20.0).round() as i32; - vein.max_patch = (num12 * 24.0).round() as i32; + *vein_opacities.get(vein_type_index as usize).unwrap() + }; + for _ in 0..20 { + if vein_nodes.len() >= target_node_count { + break; + } + for index8 in 0..vein_nodes.len() { + let existing_node = vein_nodes.get(index8).unwrap(); + if existing_node.magnitude_sq() <= 36.0 { + let random_angle_radians = rand2.next_f64() * PI * 2.0; + let mut random_dir = VectorF2::new( + random_angle_radians.cos() as f32, + random_angle_radians.sin() as f32, + ); + random_dir += existing_node * 0.2; + random_dir.normalize(); + let new_node = existing_node + &random_dir; + let not_too_close_to_other_node = vein_nodes + .iter() + .all(|v| v.distance_sq_from(&new_node) >= 0.85); + if not_too_close_to_other_node { + vein_nodes.push(new_node); + } + if vein_nodes.len() >= target_node_count { + break; + } + } + } } - let num16 = if vein.vein_type == VeinType::Oil { - f.powf(0.5) + let adjusted_resource_coef = if is_oil { + resource_coef.powf(0.5) } else { - f + resource_coef }; - if is_infinite_resource && vein.vein_type != VeinType::Oil { - vein.min_amount = 1; - vein.max_amount = 1; + let total_amount = ((vein_density * 100000.0 * adjusted_resource_coef) + .round_ties_even() as i32) + .max(20); + let amount_variance = if total_amount < 16000 { + ((total_amount as f32) * (15.0 / 16.0)) as i32 } else { - let num17 = - ((num_array_3[index3 as usize] * 100000.0 * num16).round() as i32).max(20); - let num18 = if num17 < 16000 { - ((num17 as f32) * (15.0 / 16.0)).floor() as i32 + 15000 + }; + let min_value = total_amount - amount_variance; + let value_range = amount_variance * 2 + 1; + for pos in vein_nodes.iter() { + let raw_amount = rand2.next_i32(value_range) + min_value; + let amount = if is_infinite_resource && !is_oil { + 1 } else { - 15000 - }; - - let map_amount = |amount: i32| -> i32 { - let x1 = ((amount as f32) * 1.1).round(); - let x2 = (if vein.vein_type == VeinType::Oil { - x1 * self.star.game_desc.oil_amount_multipler() + let multiplier = if is_oil { + self.game_desc.oil_amount_multiplier() } else { - x1 * self.star.game_desc.resource_multiplier - }) - .round() as i32; - x2.max(1) + self.game_desc.resource_multiplier + }; + (((raw_amount as f32) * 1.1 * multiplier).round_ties_even() as i32).max(1) }; - - vein.min_amount = map_amount(num17 - num18); - vein.max_amount = map_amount(num17 + num18); + if algo_id == 7 || theme.water_item_id == 0 { + amount_map[*vein_type as usize] += amount; + } else { + let node_offset = + ((right_axis * pos.0) + (forward_axis * pos.1)) * min_vein_spacing; + let mut pos = normalized + node_offset; + if is_oil { + pos = self.snap_to(&pos); + } + let surface_height = raw_data.query_height(&pos); + if surface_height >= self.radius { + // println!("{:?},{:?},{}", pos * surface_height, vein_type, amount); + amount_map[*vein_type as usize] += amount; + } + } } - output.push(vein); } - } - output - }); + + amount_map + .iter() + .enumerate() + .filter(|(_, &amount)| amount > 0) + .map(|(i, &amount)| ActualVein { + vein_type: unsafe { ::std::mem::transmute(i as i32) }, + amount, + }) + .collect() + }) + } + + pub fn is_acutal_veins_generated(&self) -> bool { + self.actual_veins.get().is_some() + } + + fn snap_to(&self, pos: &VectorF3) -> VectorF3 { + let segment = ((self.radius / 4.0 + 0.1) as i32 * 4) as f32; + let two_pi = PI as f32 * 2.0; + let pos = pos.normalized(); + let latitude_angle = pos.1.asin(); + let mut longitude_angle = pos.0.atan2(-pos.2); + let mut latitude_index_raw = latitude_angle / two_pi * segment; + let latitude_index = (latitude_index_raw.abs() - 0.1).max(0.0) as i32; + let longitude_segment_count = + determine_longitude_segment_count(latitude_index, segment) as f32; + let mut longitude_index_raw = longitude_angle / two_pi * longitude_segment_count; + latitude_index_raw = (latitude_index_raw * 5.0).round_ties_even() / 5.0; + longitude_index_raw = (longitude_index_raw * 5.0).round_ties_even() / 5.0; + let latitude_radians = latitude_index_raw / segment * two_pi; + longitude_angle = longitude_index_raw / longitude_segment_count * two_pi; + let latitude_sin = latitude_radians.sin(); + let latitude_cos = latitude_radians.cos(); + let longitude_sin = longitude_angle.sin(); + let longitude_cos = longitude_angle.cos(); + VectorF3( + latitude_cos * longitude_sin, + latitude_sin, + latitude_cos * (-longitude_cos), + ) + } } +fn determine_longitude_segment_count(latitude_index: i32, segment: f32) -> i32 { + let candidate_segment_count = (((latitude_index as f32) / (segment / 4.0) * PI as f32 * 0.5) + .cos() + .abs() + * segment) + .ceil() as usize; + if candidate_segment_count < 500 { + SEGMENT_TABLE[candidate_segment_count] + } else { + ((candidate_segment_count as i32) + 49) / 100 * 100 + } +} + +const SEGMENT_TABLE: [i32; 512] = [ + 1, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16, 16, 16, 20, 20, 20, 20, 20, 20, 20, 20, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, 200, 200, 200, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, + 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, + 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, + 240, 240, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, + 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, + 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, + 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, + 300, 300, 300, 300, 300, 300, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, + 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, + 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, + 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, + 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, + 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 500, 500, 500, 500, 500, 500, 500, 500, + 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, + 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, + 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, +]; + impl Serialize for Planet<'_> { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let mut state = serializer.serialize_struct("Planet", 16)?; + let mut state = serializer.serialize_struct("Planet", 15)?; state.serialize_field("index", &self.index)?; state.serialize_field("orbitAround", &self.orbit_around.borrow().map(|p| p.index))?; state.serialize_field("orbitIndex", &self.orbit_index)?; @@ -651,15 +1229,17 @@ impl Serialize for Planet<'_> { state.serialize_field("orbitInclination", &self.get_orbit_inclination())?; state.serialize_field("orbitLongitude", &self.orbit_longitude)?; state.serialize_field("orbitalPeriod", &self.get_orbital_period())?; - state.serialize_field("orbitPhase", &self.orbit_phase)?; state.serialize_field("obliquity", &self.get_obliquity())?; state.serialize_field("rotationPeriod", &self.get_rotation_period())?; - state.serialize_field("rotationPhase", &self.rotation_phase)?; state.serialize_field("type", &self.get_type())?; state.serialize_field("luminosity", &self.get_luminosity())?; state.serialize_field("theme", &self.get_theme())?; - state.serialize_field("veins", &self.get_veins())?; state.serialize_field("gases", &self.get_gases())?; + if self.game_desc.use_actual_veins { + state.serialize_field("actualVeins", &self.get_actual_veins())?; + } else { + state.serialize_field("veins", &self.get_estimated_veins())?; + } state.end() } } diff --git a/inserter/src/algorithm/data/planet_algorithms/algo0.rs b/inserter/src/algorithm/data/planet_algorithms/algo0.rs new file mode 100644 index 0000000..12c42cb --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo0.rs @@ -0,0 +1,22 @@ +use super::super::planet::Planet; +use super::PlanetAlgorithm; + +/// PlanetAlgorithm0 - All vertices at planet radius. +/// This is the simplest algorithm. +pub struct PlanetAlgorithm0 { + radius: f64, +} + +impl PlanetAlgorithm0 { + pub fn new(planet: &Planet) -> Self { + Self { + radius: planet.radius as f64, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm0 { + fn get_height(&self, _index: usize) -> f64 { + self.radius + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo1.rs b/inserter/src/algorithm/data/planet_algorithms/algo1.rs new file mode 100644 index 0000000..0ecf336 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo1.rs @@ -0,0 +1,83 @@ +use super::super::math::*; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm1 - Two-layer FBM noise with Levelize3. +pub struct PlanetAlgorithm1 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm1 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm1 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.01; + let freq_scale_y: f64 = 0.012; + let freq_scale_z: f64 = 0.01; + let noise_amplitude: f64 = 3.0; + let noise_offset: f64 = -0.2; + let noise2_amplitude: f64 = 0.9; + let noise2_offset: f64 = 0.5; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let layer1_noise = self.noise1.noise_3d_fbm( + world_x * freq_scale_x, + world_y * freq_scale_y, + world_z * freq_scale_z, + 6, + 0.5, + 2.0, + ) * noise_amplitude + + noise_offset; + let layer2_noise = self.noise2.noise_3d_fbm( + world_x * (1.0 / 400.0), + world_y * (1.0 / 400.0), + world_z * (1.0 / 400.0), + 3, + 0.5, + 2.0, + ) * noise_amplitude + * noise2_amplitude + + noise2_offset; + let clamped_layer2 = if layer2_noise > 0.0 { + layer2_noise * 0.5 + } else { + layer2_noise + }; + let combined_noise = layer1_noise + clamped_layer2; + let f = if combined_noise > 0.0 { + combined_noise * 0.5 + } else { + combined_noise * 1.6 + }; + let shaped_height = if f > 0.0 { + levelize3(f, 0.7, 0.0) + } else { + levelize2(f, 0.5, 0.0) + }; + + self.radius + shaped_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo10.rs b/inserter/src/algorithm/data/planet_algorithms/algo10.rs new file mode 100644 index 0000000..78c8dde --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo10.rs @@ -0,0 +1,243 @@ +use super::super::math::{levelize, levelize4}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::random_table::RandomTable; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm10 - FBM noise with 10 elliptical crater features. +pub struct PlanetAlgorithm10 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, + noise3: SimplexNoise, + noise4: SimplexNoise, + ellipses: Vec<([f64; 3], f64)>, + eccentricities: Vec, + heights: Vec, +} + +#[inline] +fn remap(src_min: f64, src_max: f64, tgt_min: f64, tgt_max: f64, x: f64) -> f64 { + (x - src_min) / (src_max - src_min) * (tgt_max - tgt_min) + tgt_min +} + +impl PlanetAlgorithm10 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + let seed3 = rand.next_seed(); + let seed4 = rand.next_seed(); + + let mut seed5 = rand.next_seed(); + let mut els = Vec::with_capacity(10); + let mut eccs = Vec::with_capacity(10); + let mut hs = Vec::with_capacity(10); + + for _ in 0..10 { + let mut v = RandomTable::spheric_normal(&mut seed5, 1.0); + v.normalize(); + v *= planet.radius as f64; + let w = (rand.next_f64() * 10.0 + 40.0) as f32; + els.push(([v.0, v.1, v.2], (w * w) as f64)); + + let ecc = if rand.next_f64() <= 0.5 { + remap(0.0, 1.0, 0.2, 1.0 / 3.0, rand.next_f64()) + } else { + remap(0.0, 1.0, 3.0, 5.0, rand.next_f64()) + }; + eccs.push(ecc); + + let h = remap(0.0, 1.0, 1.0, 2.0, rand.next_f64()); + hs.push(h); + } + + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + noise3: SimplexNoise::with_seed(seed3), + noise4: SimplexNoise::with_seed(seed4), + ellipses: els, + eccentricities: eccs, + heights: hs, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm10 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.007; + let freq_scale_y: f64 = 0.007; + let freq_scale_z: f64 = 0.007; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let leveled_x = levelize(world_x * 0.007, 1.0, 0.0); + let leveled_y = levelize(world_y * 0.007, 1.0, 0.0); + let leveled_z = levelize(world_z * 0.007, 1.0, 0.0); + + let xin = leveled_x + + self + .noise3 + .noise_3d(world_x * 0.05, world_y * 0.05, world_z * 0.05) + * 0.04; + let yin = leveled_y + + self + .noise3 + .noise_3d(world_y * 0.05, world_z * 0.05, world_x * 0.05) + * 0.04; + let zin = leveled_z + + self + .noise3 + .noise_3d(world_z * 0.05, world_x * 0.05, world_y * 0.05) + * 0.04; + + let cell_noise = self.noise4.noise_3d(xin, yin, zin).abs(); + let crack_depth = (0.16 - cell_noise) * 10.0; + let crack_clamped = if crack_depth > 0.0 { + if crack_depth > 1.0 { + 1.0 + } else { + crack_depth + } + } else { + 0.0 + }; + let crack_intensity = crack_clamped * crack_clamped; + + let fluid_level = (self.noise3.noise_3d_fbm( + world_y * 0.005, + world_z * 0.005, + world_x * 0.005, + 4, + 0.5, + 2.0, + ) + 0.22) + * 5.0; + let fluid_clamped = if fluid_level > 0.0 { + if fluid_level > 1.0 { + 1.0 + } else { + fluid_level + } + } else { + 0.0 + }; + + let detail_noise = self + .noise4 + .noise_3d_fbm(xin * 1.5, yin * 1.5, zin * 1.5, 2, 0.5, 2.0) + .abs(); + let x_noise_val = self.noise2.noise_3d_fbm( + world_x * freq_scale_x * 5.0, + world_y * freq_scale_y * 5.0, + world_z * freq_scale_z * 5.0, + 4, + 0.5, + 2.0, + ); + let high_freq_amp = x_noise_val * 0.2; + + let mut max_crater = 0.0; + for j in 0..10 { + let e = &self.ellipses[j]; + let ecc = self.eccentricities[j]; + let dx = e.0[0] - world_x; + let dy = e.0[1] - world_y; + let dz = e.0[2] - world_z; + let dist_ecc = ecc * dx * dx + dy * dy + dz * dz; + let dist_scaled = remap(-1.0, 1.0, 0.2, 5.0, x_noise_val) * dist_ecc; + if dist_scaled < e.1 { + let sqrt_val = (dist_scaled / e.1).sqrt(); + let crater_t = 1.0 - (1.0 - sqrt_val); + let mut crater_shape = + 1.0 - crater_t * crater_t * crater_t * crater_t + high_freq_amp * 2.0; + if crater_shape < 0.0 { + crater_shape = 0.0; + } + let candidate = self.heights[j] * crater_shape; + if candidate > max_crater { + max_crater = candidate; + } + } + } + + let warped_x = world_x + (world_y * 0.15).sin() * 2.0; + let warped_y = world_y + (world_z * 0.15).sin() * 2.0; + let warped_z = world_z + (warped_x * 0.15).sin() * 2.0; + + let warp_x_scaled = warped_x * freq_scale_x; + let warp_y_scaled = warped_y * freq_scale_y; + let warp_z_scaled = warped_z * freq_scale_z; + + let f_val = ((self.noise1.noise_3d_fbm( + warp_x_scaled * 0.6, + warp_y_scaled * 0.6, + warp_z_scaled * 0.6, + 4, + 0.5, + 1.8, + ) + 1.0) + * 0.5) + .powf(1.3); + + let remap_noise = remap( + -1.0, + 1.0, + -0.1, + 0.15, + self.noise2.noise_3d_fbm( + warp_x_scaled * 6.0, + warp_y_scaled * 6.0, + warp_z_scaled * 6.0, + 5, + 0.5, + 2.0, + ), + ); + + let turb_base = self.noise2.noise_3d_fbm( + warp_x_scaled * 5.0 * 3.0, + warp_y_scaled * 5.0, + warp_z_scaled * 5.0, + 1, + 0.5, + 2.0, + ); + let turb_detail = self.noise2.noise_3d_fbm( + warp_x_scaled * 5.0 * 3.0 + turb_base * 0.3, + warp_y_scaled * 5.0 + turb_base * 0.3, + warp_z_scaled * 5.0 + turb_base * 0.3, + 5, + 0.5, + 2.0, + ) * 0.1; + + let mut shaped = (levelize(levelize4(f_val, 1.0, 0.0), 1.0, 0.0)).min(1.0); + if shaped <= 0.8 { + if shaped > 0.4 { + shaped += turb_detail; + } else { + shaped += remap_noise; + } + } + + let crater_blend = (shaped * 2.5 - shaped * max_crater).max(remap_noise * 2.0); + let crack_scale = (2.0 - crater_blend) / 2.0; + let mut terrain_height = crater_blend - crack_intensity * 1.2 * fluid_clamped * crack_scale; + if terrain_height >= 0.0 { + terrain_height += (cell_noise * 0.25 + detail_noise * 0.6) * crack_scale; + } + let final_height = terrain_height - 0.1; + + self.radius + final_height + 0.1 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo11.rs b/inserter/src/algorithm/data/planet_algorithms/algo11.rs new file mode 100644 index 0000000..440b1f9 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo11.rs @@ -0,0 +1,99 @@ +use super::super::math::{levelize2, levelize3}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm11 - Complex noise with Remap, Levelize2/Levelize3 and modX/modY. +pub struct PlanetAlgorithm11 { + grid: &'static PlanetGrid, + radius: f64, + mod_y: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, + noise3: SimplexNoise, + mod_freq_x: f64, + mod_freq_y: f64, + mod_freq_z: f64, +} + +#[inline] +fn remap(src_min: f64, src_max: f64, tgt_min: f64, tgt_max: f64, x: f64) -> f64 { + (x - src_min) / (src_max - src_min) * (tgt_max - tgt_min) + tgt_min +} + +impl PlanetAlgorithm11 { + pub fn new(planet: &Planet) -> Self { + let mod_x = planet.get_mod_x(); + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + let seed3 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + mod_y: planet.get_mod_y(), + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + noise3: SimplexNoise::with_seed(seed3), + mod_freq_x: 0.002 * mod_x, + mod_freq_y: 0.002 * mod_x * 4.0, + mod_freq_z: 0.002 * mod_x, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm11 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.007; + let freq_scale_y: f64 = 0.007; + let freq_scale_z: f64 = 0.007; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let detail_noise = self.noise2.noise_3d_fbm( + world_x * freq_scale_x * 4.0, + world_y * freq_scale_y * 8.0, + world_z * freq_scale_z * 4.0, + 3, + 0.5, + 2.0, + ); + let primary_freq_scale = 0.6; + + let inner = self.noise1.noise_3d_fbm( + world_x * freq_scale_x * primary_freq_scale, + world_y * freq_scale_x * 1.5 * 2.5, + world_z * freq_scale_x * primary_freq_scale, + 6, + 0.45, + 1.8, + ) * 0.95 + + detail_noise * 0.05; + + let primary_shaped = levelize2( + (remap(-1.0, 1.0, 0.0, 1.0, inner)).powf(self.mod_y) + 1.0, + 1.0, + 0.0, + ); + + let inner2 = self.noise3.noise_3d_fbm( + world_x * self.mod_freq_x, + world_y * self.mod_freq_y, + world_z * self.mod_freq_z, + 5, + 0.55, + 2.0, + ); + let secondary_shaped = + levelize3((remap(-1.0, 1.0, 0.0, 1.0, inner2)).powf(0.65), 1.0, 0.0) * primary_shaped; + + let final_height = ((secondary_shaped - 0.4) * 0.9).max(-0.3); + + self.radius + final_height + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo12.rs b/inserter/src/algorithm/data/planet_algorithms/algo12.rs new file mode 100644 index 0000000..ee1d084 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo12.rs @@ -0,0 +1,104 @@ +use super::super::math::clamp01; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm12 - Latitude-based terrain with ridged noise and modX/modY. +pub struct PlanetAlgorithm12 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, + freq_scale: f64, + mod_y: f64, +} + +#[inline] +fn curve_evaluate(t: f64) -> f64 { + let t = t / 0.6; + if t >= 1.0 { + 0.0 + } else { + (1.0 - t).powi(3) + (1.0 - t).powi(2) * 3.0 * t + } +} + +#[inline] +fn remap(src_min: f64, src_max: f64, tgt_min: f64, tgt_max: f64, x: f64) -> f64 { + (x - src_min) / (src_max - src_min) * (tgt_max - tgt_min) + tgt_min +} + +impl PlanetAlgorithm12 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + freq_scale: 1.1 * planet.get_mod_x(), + mod_y: planet.get_mod_y(), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm12 { + fn get_height(&self, index: usize) -> f64 { + let ridge_amplitude = 0.2; + let height_multiplier = 8.0; + let pi = std::f64::consts::PI; + + let v = self.grid.get_vertex(index); + let latitude_factor = ((v.1 as f64).abs().asin()) * 2.0 / pi; + let x_pos = v.0 as f64; + let y_pos_mod = (v.1 as f64) * 2.5 * self.mod_y; + let z_pos = v.2 as f64; + + let warp_offset = self.noise2.noise_3d_fbm( + x_pos * self.freq_scale, + y_pos_mod * self.freq_scale, + z_pos * self.freq_scale, + 3, + 0.4, + 2.0, + ) * 0.2; + let ridged = self.noise1.ridged_noise( + x_pos * self.freq_scale, + y_pos_mod * self.freq_scale - warp_offset, + z_pos * self.freq_scale, + 6, + 0.7, + 2.0, + 0.8, + ); + let fbm_val = self.noise1.noise_3d_fbm_initial_amp( + x_pos * self.freq_scale, + y_pos_mod * self.freq_scale - warp_offset, + z_pos * self.freq_scale, + 6, + 0.6, + 2.0, + 0.7, + ); + let combined_noise = fbm_val * (ridged + fbm_val); + + let val = ((clamp01(remap( + -8.0, + 8.0, + 0.0, + 1.0, + ridge_amplitude + height_multiplier * combined_noise * ridged + 0.5, + )) + 0.5) + .powf(1.5) + - curve_evaluate(latitude_factor * 0.9)) + * 2.0; + + let final_height = val.clamp(0.0, 2.0) * 1.1 - 0.2; + + self.radius + final_height + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo13.rs b/inserter/src/algorithm/data/planet_algorithms/algo13.rs new file mode 100644 index 0000000..900dbf8 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo13.rs @@ -0,0 +1,81 @@ +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm13 - Noise-based terrain with modX/modY and piecewise height shaping. +pub struct PlanetAlgorithm13 { + grid: &'static PlanetGrid, + radius: f64, + mod_y: f64, + noise: SimplexNoise, + freq_scale_x: f64, + freq_scale_y: f64, + freq_scale_z: f64, +} + +#[inline] +fn remap(src_min: f64, src_max: f64, tgt_min: f64, tgt_max: f64, x: f64) -> f64 { + (x - src_min) / (src_max - src_min) * (tgt_max - tgt_min) + tgt_min +} + +impl PlanetAlgorithm13 { + pub fn new(planet: &Planet) -> Self { + let mod_x = planet.get_mod_x(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + mod_y: planet.get_mod_y(), + noise: SimplexNoise::with_seed(DspRandom::new(planet.seed).next_seed()), + freq_scale_x: 0.007 * mod_x, + freq_scale_y: 0.007 * mod_x, + freq_scale_z: 0.007 * mod_x, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm13 { + fn get_height(&self, index: usize) -> f64 { + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let n = self.noise.noise_3d_fbm( + world_x * self.freq_scale_x, + world_y * self.freq_scale_y, + world_z * self.freq_scale_z, + 6, + 0.5, + 2.0, + ); + let mut raw_height = remap( + 0.0, + 2.0, + 0.0, + 4.0, + remap(-1.0, 1.0, 0.0, 1.0, n).powf(self.mod_y) * (49.0 / 16.0), + ); + + if raw_height < 1.0 { + raw_height = raw_height.powi(2); + } + + let clamped_height = (raw_height - 0.2).min(4.0); + + let final_height = if clamped_height > 2.0 { + if clamped_height <= 3.0 { + 2.0 - 1.0 * (clamped_height - 2.0) + } else if clamped_height <= 3.5 { + 1.0 + } else { + 1.0 + 2.0 * (clamped_height - 3.5) + } + } else { + clamped_height + }; + + self.radius + final_height + 0.1 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo14.rs b/inserter/src/algorithm/data/planet_algorithms/algo14.rs new file mode 100644 index 0000000..292a7cb --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo14.rs @@ -0,0 +1,169 @@ +use super::super::math::{levelize, levelize2, levelize4}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm14 - Lava terrain with domain warping, levelize shaping, and fluid dynamics. +pub struct PlanetAlgorithm14 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, + noise3: SimplexNoise, + noise4: SimplexNoise, +} + +impl PlanetAlgorithm14 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + let seed3 = rand.next_seed(); + let seed4 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + noise3: SimplexNoise::with_seed(seed3), + noise4: SimplexNoise::with_seed(seed4), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm14 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.007; + let freq_scale_y: f64 = 0.007; + let freq_scale_z: f64 = 0.007; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let leveled_x = levelize(world_x * 0.007 / 2.0, 1.0, 0.0); + let leveled_y = levelize(world_y * 0.007 / 2.0, 1.0, 0.0); + let leveled_z = levelize(world_z * 0.007 / 2.0, 1.0, 0.0); + + let xin = leveled_x + + self + .noise3 + .noise_3d(world_x * 0.05, world_y * 0.05, world_z * 0.05) + * 0.04; + let yin = leveled_y + + self + .noise3 + .noise_3d(world_y * 0.05, world_z * 0.05, world_x * 0.05) + * 0.04; + let zin = leveled_z + + self + .noise3 + .noise_3d(world_z * 0.05, world_x * 0.05, world_y * 0.05) + * 0.04; + + let crack_blend = (0.12 - self.noise4.noise_3d(xin, yin, zin).abs()) * 10.0; + let crack_clamped = if crack_blend > 0.0 { + if crack_blend > 1.0 { + 1.0 + } else { + crack_blend + } + } else { + 0.0 + }; + let crack_intensity = crack_clamped * crack_clamped; + let fluid_level = (self.noise3.noise_3d_fbm( + world_y * 0.005, + world_z * 0.005, + world_x * 0.005, + 4, + 0.5, + 2.0, + ) + 0.22) + * 5.0; + let fluid_clamped = if fluid_level > 0.0 { + if fluid_level > 1.0 { + 1.0 + } else { + fluid_level + } + } else { + 0.0 + }; + + let warped_x = world_x + (world_y * 0.15).sin() * 3.0; + let warped_y = world_y + (world_z * 0.15).sin() * 3.0; + let warped_z = world_z + (warped_x * 0.15).sin() * 3.0; + + let primary_noise = self.noise1.noise_3d_fbm( + warped_x * freq_scale_x * 1.0, + warped_y * freq_scale_y * 1.1, + warped_z * freq_scale_z * 1.0, + 6, + 0.5, + 1.8, + ); + let secondary_noise = self.noise2.noise_3d_fbm( + warped_x * freq_scale_x * 1.3 + 0.5, + warped_y * freq_scale_y * 2.8 + 0.2, + warped_z * freq_scale_z * 1.3 + 0.7, + 3, + 0.5, + 2.0, + ) * 2.0; + let reference_noise = self.noise2.noise_3d_fbm( + warped_x * freq_scale_x * 0.8, + warped_y * freq_scale_y * 0.8, + warped_z * freq_scale_z * 0.8, + 2, + 0.5, + 2.0, + ) * 2.0; + + let mut f = primary_noise * 2.0 + + 0.92 + + ((secondary_noise * (reference_noise + 0.5).abs() - 0.35) * 1.0).clamp(0.0, 1.0); + if f < 0.0 { + f = 0.0; + } + + let t = levelize2(f, 1.0, 0.0); + let terrain_base = if t > 0.0 { + let t2 = levelize2(f, 1.0, 0.0); + levelize4(t2, 1.0, 0.0) + } else { + t + }; + + let height_floor = 0.0; + let shaped_height = terrain_base; + + let mut combined_height = height_floor - crack_intensity * 1.2 * fluid_clamped; + if combined_height >= 0.0 { + combined_height = shaped_height; + } + + let mut final_height = combined_height - 0.1; + + let under_ground = -0.3 - final_height; + if under_ground > 0.0 { + let crater_noise = + self.noise2 + .noise_3d(warped_x * 0.16, warped_y * 0.16, warped_z * 0.16) + - 1.0; + let depth_clamped = if under_ground > 1.0 { + 1.0 + } else { + under_ground + }; + let depth_power = (3.0 - depth_clamped - depth_clamped) * depth_clamped * depth_clamped; + final_height = -0.3 - depth_power * 10.0 + + depth_power * depth_power * depth_power * depth_power * crater_noise * 0.5; + } + + self.radius + final_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo2.rs b/inserter/src/algorithm/data/planet_algorithms/algo2.rs new file mode 100644 index 0000000..1cfee3f --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo2.rs @@ -0,0 +1,72 @@ +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm2 - Two-layer FBM noise with frequency modulation from modX/modY. +pub struct PlanetAlgorithm2 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + scaled_freq_x: f64, + scaled_freq_y: f64, + scaled_freq_z: f64, +} + +impl PlanetAlgorithm2 { + pub fn new(planet: &Planet) -> Self { + let mod_x = planet.get_mod_x(); + let mod_y = planet.get_mod_y(); + + let mod_x_transformed = (3.0 - mod_x - mod_x) * mod_x * mod_x; + + let base_freq_x: f64 = 0.0035; + let base_freq_y: f64 = 0.025 * mod_x_transformed + 0.0035 * (1.0 - mod_x_transformed); + let base_freq_z: f64 = 0.0035; + let mod_y_scale: f64 = 1.0 + 1.3 * mod_y; + + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + scaled_freq_x: base_freq_x * mod_y_scale, + scaled_freq_y: base_freq_y * mod_y_scale, + scaled_freq_z: base_freq_z * mod_y_scale, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm2 { + fn get_height(&self, index: usize) -> f64 { + let noise_amplitude: f64 = 3.0; + + let v = self.grid.get_vertex(index); + let world_x: f64 = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let base_noise = self.noise1.noise_3d_fbm( + world_x * self.scaled_freq_x, + world_y * self.scaled_freq_y, + world_z * self.scaled_freq_z, + 6, + 0.45, + 1.8, + ); + + let shaping_factor = noise_amplitude; + let shaped_terrain = + 0.6 / ((base_noise * shaping_factor + shaping_factor * 0.4).abs() + 0.6) - 0.25; + let final_height = if shaped_terrain < 0.0 { + shaped_terrain * 0.3 + } else { + shaped_terrain + }; + + self.radius + final_height + 0.1 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo3.rs b/inserter/src/algorithm/data/planet_algorithms/algo3.rs new file mode 100644 index 0000000..529e181 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo3.rs @@ -0,0 +1,140 @@ +use super::super::math::{levelize2, levelize4}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +#[inline] +fn lerp_nc(a: f64, b: f64, t: f64) -> f64 { + a + (b - a) * t +} + +/// PlanetAlgorithm3 - Complex FBM noise with domain warping and multi-level shaping. +pub struct PlanetAlgorithm3 { + grid: &'static PlanetGrid, + radius: f64, + mod_x: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm3 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + mod_x: planet.get_mod_x(), + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm3 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.007; + let freq_scale_y: f64 = 0.007; + let freq_scale_z: f64 = 0.007; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let warped_x = world_x + (world_y * 0.15).sin() * 3.0; + let warped_y = world_y + (world_z * 0.15).sin() * 3.0; + let warped_z = world_z + (warped_x * 0.15).sin() * 3.0; + + let primary_noise = self.noise1.noise_3d_fbm( + warped_x * freq_scale_x * 1.0, + warped_y * freq_scale_y * 1.1, + warped_z * freq_scale_z * 1.0, + 6, + 0.5, + 1.8, + ); + + let secondary_noise = self.noise2.noise_3d_fbm( + warped_x * freq_scale_x * 1.3 + 0.5, + warped_y * freq_scale_y * 2.8 + 0.2, + warped_z * freq_scale_z * 1.3 + 0.7, + 3, + 0.5, + 2.0, + ) * 2.0; + + let detail_noise = self.noise2.noise_3d_fbm( + warped_x * freq_scale_x * 6.0, + warped_y * freq_scale_y * 12.0, + warped_z * freq_scale_z * 6.0, + 2, + 0.5, + 2.0, + ) * 2.0; + + let blended_detail = lerp_nc(detail_noise, detail_noise * 0.1, self.mod_x); + + let reference_noise = self.noise2.noise_3d_fbm( + warped_x * freq_scale_x * 0.8, + warped_y * freq_scale_y * 0.8, + warped_z * freq_scale_z * 0.8, + 2, + 0.5, + 2.0, + ) * 2.0; + + let mut f = primary_noise * 2.0 + + 0.92 + + ((secondary_noise * (reference_noise + 0.5).abs() - 0.35) * 1.0).clamp(0.0, 1.0); + + if f < 0.0 { + f *= 2.0; + } + + let mut t = levelize2(f, 1.0, 0.0); + if t > 0.0 { + let levelized_val = levelize2(f, 1.0, 0.0); + t = lerp_nc( + levelize4(levelized_val, 1.0, 0.0), + levelized_val, + self.mod_x, + ); + } + + let height_b = if t > 0.0 { + if t > 1.0 { + if t > 2.0 { + lerp_nc(1.2, 2.0, t - 2.0) + blended_detail * 0.12 + } else { + lerp_nc(0.3, 1.2, t - 1.0) + blended_detail * 0.12 + } + } else { + lerp_nc(0.0, 0.3, t) + blended_detail * 0.1 + } + } else { + lerp_nc(-1.0, 0.0, t + 1.0) + }; + + let height_a2 = if t > 0.0 { + if t > 1.0 { + if t > 2.0 { + lerp_nc(1.4, 2.7, t - 2.0) + blended_detail * 0.12 + } else { + lerp_nc(0.3, 1.4, t - 1.0) + blended_detail * 0.12 + } + } else { + lerp_nc(0.0, 0.3, t) + blended_detail * 0.1 + } + } else { + lerp_nc(-4.0, 0.0, t + 1.0) + }; + + let final_height = lerp_nc(height_a2, height_b, self.mod_x); + + self.radius + final_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo4.rs b/inserter/src/algorithm/data/planet_algorithms/algo4.rs new file mode 100644 index 0000000..169edce --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo4.rs @@ -0,0 +1,113 @@ +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::random_table::RandomTable; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm4 - FBM noise with 80 circular crater features. +pub struct PlanetAlgorithm4 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, + circles: Vec<([f64; 3], f64)>, + heights: Vec, +} + +impl PlanetAlgorithm4 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + + let mut seed3 = rand.next_seed(); + let mut cr = Vec::with_capacity(80); + let mut hs = Vec::with_capacity(80); + + for _ in 0..80 { + let mut v = RandomTable::spheric_normal(&mut seed3, 1.0); + let w = (v.magnitude() * 8.0 + 8.0) as f32; + v.normalize(); + v *= planet.radius as f64; + cr.push(([v.0, v.1, v.2], (w * w) as f64)); + + let h = rand.next_f64() * 0.4 + 0.2; + hs.push(h); + } + + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + circles: cr, + heights: hs, + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm4 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.007; + let freq_scale_y: f64 = 0.007; + let freq_scale_z: f64 = 0.007; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let low_freq_noise = self.noise1.noise_3d_fbm( + world_x * freq_scale_x, + world_y * freq_scale_y, + world_z * freq_scale_z, + 4, + 0.45, + 1.8, + ); + let high_freq_noise = self.noise2.noise_3d_fbm( + world_x * freq_scale_x * 5.0, + world_y * freq_scale_y * 5.0, + world_z * freq_scale_z * 5.0, + 4, + 0.5, + 2.0, + ); + + let scaled_low = low_freq_noise * 1.5; + let scaled_high = high_freq_noise * 0.2; + let base_elevation = scaled_low * 0.08 + scaled_high * 2.0; + + let mut max_crater = 0.0; + for j in 0..80 { + let c = &self.circles[j]; + let dx = c.0[0] - world_x; + let dy = c.0[1] - world_y; + let dz = c.0[2] - world_z; + let dist_sq = dx * dx + dy * dy + dz * dz; + if dist_sq <= c.1 { + let mut t = dist_sq / c.1 + scaled_high * 1.2; + if t < 0.0 { + t = 0.0; + } + let t_sq = t * t; + let crater_shape = -15.0 * (t_sq * t) + (131.0 / 6.0) * t_sq - (113.0 / 15.0) * t + + 0.7 + + scaled_high; + let crater_shape = if crater_shape < 0.0 { + 0.0 + } else { + crater_shape + }; + let crater_val = crater_shape * crater_shape * self.heights[j]; + if crater_val > max_crater { + max_crater = crater_val; + } + } + } + + let final_height = max_crater + base_elevation + 0.2; + self.radius + final_height + 0.1 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo5.rs b/inserter/src/algorithm/data/planet_algorithms/algo5.rs new file mode 100644 index 0000000..524a9d4 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo5.rs @@ -0,0 +1,120 @@ +use super::super::math::levelize; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm5 - Complex noise with levelized coordinates and cell/crack patterns. +pub struct PlanetAlgorithm5 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm5 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm5 { + fn get_height(&self, index: usize) -> f64 { + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let height_base = 0.0; + let leveled_x = levelize(world_x * 0.007, 1.0, 0.0); + let leveled_y = levelize(world_y * 0.007, 1.0, 0.0); + let leveled_z = levelize(world_z * 0.007, 1.0, 0.0); + + let xin = leveled_x + + self + .noise1 + .noise_3d(world_x * 0.05, world_y * 0.05, world_z * 0.05) + * 0.04; + let yin = leveled_y + + self + .noise1 + .noise_3d(world_y * 0.05, world_z * 0.05, world_x * 0.05) + * 0.04; + let zin = leveled_z + + self + .noise1 + .noise_3d(world_z * 0.05, world_x * 0.05, world_y * 0.05) + * 0.04; + + let cell_noise = self.noise2.noise_3d(xin, yin, zin).abs(); + let crack_depth = (0.16 - cell_noise) * 10.0; + let crack_clamped = if crack_depth > 0.0 { + if crack_depth > 1.0 { + 1.0 + } else { + crack_depth + } + } else { + 0.0 + }; + let crack_intensity = crack_clamped * crack_clamped; + + let fluid_level = (self.noise1.noise_3d_fbm( + world_y * 0.005, + world_z * 0.005, + world_x * 0.005, + 4, + 0.5, + 2.0, + ) + 0.22) + * 5.0; + let fluid_clamped = if fluid_level > 0.0 { + if fluid_level > 1.0 { + 1.0 + } else { + fluid_level + } + } else { + 0.0 + }; + + let detail_noise = self + .noise2 + .noise_3d_fbm(xin * 1.5, yin * 1.5, zin * 1.5, 2, 0.5, 2.0) + .abs(); + + let mut terrain_height = height_base - crack_intensity * 1.2 * fluid_clamped; + if terrain_height >= 0.0 { + terrain_height += cell_noise * 0.25 + detail_noise * 0.6; + } + + let mut final_height = terrain_height - 0.1; + + let under_ground = -0.3 - final_height; + if under_ground > 0.0 { + let crack_noise = self + .noise2 + .noise_3d(world_x * 0.16, world_y * 0.16, world_z * 0.16) + - 1.0; + let depth_clamped = if under_ground > 1.0 { + 1.0 + } else { + under_ground + }; + let depth_power = (3.0 - depth_clamped - depth_clamped) * depth_clamped * depth_clamped; + final_height = -0.3 - depth_power * 3.7 + + depth_power * depth_power * depth_power * depth_power * crack_noise * 0.5; + } + + self.radius + final_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo6.rs b/inserter/src/algorithm/data/planet_algorithms/algo6.rs new file mode 100644 index 0000000..7aaf9eb --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo6.rs @@ -0,0 +1,137 @@ +use super::super::math::{levelize, levelize2}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm6 - Similar to algo5 but with different height/biomo formula. +pub struct PlanetAlgorithm6 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm6 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm6 { + fn get_height(&self, index: usize) -> f64 { + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let height_base = 0.0; + let leveled_x = levelize(world_x * 0.007, 1.0, 0.0); + let leveled_y = levelize(world_y * 0.007, 1.0, 0.0); + let leveled_z = levelize(world_z * 0.007, 1.0, 0.0); + + let xin = leveled_x + + self + .noise1 + .noise_3d(world_x * 0.05, world_y * 0.05, world_z * 0.05) + * 0.04; + let yin = leveled_y + + self + .noise1 + .noise_3d(world_y * 0.05, world_z * 0.05, world_x * 0.05) + * 0.04; + let zin = leveled_z + + self + .noise1 + .noise_3d(world_z * 0.05, world_x * 0.05, world_y * 0.05) + * 0.04; + + let cell_noise = self.noise2.noise_3d(xin, yin, zin).abs(); + let crack_depth = (0.16 - cell_noise) * 10.0; + let crack_clamped = if crack_depth > 0.0 { + if crack_depth > 1.0 { + 1.0 + } else { + crack_depth + } + } else { + 0.0 + }; + let crack_intensity = crack_clamped * crack_clamped; + + let fluid_level = (self.noise1.noise_3d_fbm( + world_y * 0.005, + world_z * 0.005, + world_x * 0.005, + 4, + 0.5, + 2.0, + ) + 0.22) + * 5.0; + let fluid_clamped = if fluid_level > 0.0 { + if fluid_level > 1.0 { + 1.0 + } else { + fluid_level + } + } else { + 0.0 + }; + + let detail_noise = self + .noise2 + .noise_3d_fbm(xin * 1.5, yin * 1.5, zin * 1.5, 2, 0.5, 2.0) + .abs(); + + let mut terrain_height = height_base - crack_intensity * 1.2 * fluid_clamped; + if terrain_height >= 0.0 { + terrain_height += cell_noise * 0.25 + detail_noise * 0.6; + } + + let mut final_height = terrain_height - 0.1; + + let under_ground = -0.3 - final_height; + if under_ground > 0.0 { + let depth_clamped = if under_ground > 1.0 { + 1.0 + } else { + under_ground + }; + final_height = + -0.3 - (3.0 - depth_clamped - depth_clamped) * depth_clamped * depth_clamped * 3.7; + } + + let floor_level = levelize2( + if crack_intensity > 0.3 { + crack_intensity + } else { + 0.3 + }, + 0.7, + 0.0, + ); + + let clamped_height = if final_height > -0.8 { + final_height + } else { + (-floor_level - cell_noise) * 0.9 + }; + + let result_height = if clamped_height > -1.2 { + clamped_height + } else { + -1.2 + }; + + self.radius + result_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo7.rs b/inserter/src/algorithm/data/planet_algorithms/algo7.rs new file mode 100644 index 0000000..1637232 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo7.rs @@ -0,0 +1,85 @@ +use super::super::math::{levelize2, levelize3}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm7 - Similar to algo1 but without +0.2 offset in height and different constants. +pub struct PlanetAlgorithm7 { + grid: &'static PlanetGrid, + radius: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm7 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm7 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.008; + let freq_scale_y: f64 = 0.01; + let freq_scale_z: f64 = 0.01; + let noise_amplitude: f64 = 3.0; + let noise_offset: f64 = -2.4; + let noise2_amplitude: f64 = 0.9; + let noise2_offset: f64 = 0.5; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let layer1_noise = self.noise1.noise_3d_fbm( + world_x * freq_scale_x, + world_y * freq_scale_y, + world_z * freq_scale_z, + 6, + 0.5, + 2.0, + ) * noise_amplitude + + noise_offset; + let layer2_noise = self.noise2.noise_3d_fbm( + world_x * (1.0 / 400.0), + world_y * (1.0 / 400.0), + world_z * (1.0 / 400.0), + 3, + 0.5, + 2.0, + ) * noise_amplitude + * noise2_amplitude + + noise2_offset; + + let clamped_layer2 = if layer2_noise > 0.0 { + layer2_noise * 0.5 + } else { + layer2_noise + }; + let combined_noise = layer1_noise + clamped_layer2; + let f = if combined_noise > 0.0 { + combined_noise * 0.5 + } else { + combined_noise * 1.6 + }; + + let shaped_height = if f > 0.0 { + levelize3(f, 0.7, 0.0) + } else { + levelize2(f, 0.5, 0.0) + }; + + self.radius + shaped_height + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo8.rs b/inserter/src/algorithm/data/planet_algorithms/algo8.rs new file mode 100644 index 0000000..eac0c58 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo8.rs @@ -0,0 +1,61 @@ +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm8 - Single noise layer with cosine-based terrain shaping. +pub struct PlanetAlgorithm8 { + grid: &'static PlanetGrid, + radius: f64, + noise: SimplexNoise, + freq_scale_x: f64, + freq_scale_y: f64, + freq_scale_z: f64, + mod_y: f64, +} + +impl PlanetAlgorithm8 { + pub fn new(planet: &Planet) -> Self { + let mod_x = planet.get_mod_x(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + noise: SimplexNoise::with_seed(DspRandom::new(planet.seed).next_seed()), + freq_scale_x: 0.002 * mod_x, + freq_scale_y: 0.002 * mod_x * mod_x * 6.66667, + freq_scale_z: 0.002 * mod_x, + mod_y: planet.get_mod_y(), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm8 { + fn get_height(&self, index: usize) -> f64 { + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let noise_val = (self.noise.noise_3d_fbm( + world_x * self.freq_scale_x, + world_y * self.freq_scale_y, + world_z * self.freq_scale_z, + 6, + 0.45, + 1.8, + ) + 1.0 + + self.mod_y * 0.01) + .clamp(0.0, 2.0); + + let shaped_height = if noise_val < 1.0 { + let f = (noise_val * std::f64::consts::PI).cos() * 1.1; + 1.0 - ((f.signum() * f.powi(4)).clamp(-1.0, 1.0) + 1.0) * 0.5 + } else { + let f = ((noise_val - 1.0) * std::f64::consts::PI).cos() * 1.1; + 2.0 - ((f.signum() * f.powi(4)).clamp(-1.0, 1.0) + 1.0) * 0.5 + }; + + self.radius + shaped_height + 0.1 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/algo9.rs b/inserter/src/algorithm/data/planet_algorithms/algo9.rs new file mode 100644 index 0000000..262758c --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/algo9.rs @@ -0,0 +1,135 @@ +use super::super::math::{levelize2, levelize3}; +use super::super::planet::Planet; +use super::super::random::DspRandom; +use super::super::simplex_noise::SimplexNoise; +use super::PlanetAlgorithm; +use crate::algorithm::data::planet_grid::{get_planet_grid, PlanetGrid}; + +/// PlanetAlgorithm9 - Complex multi-layer noise with modX/modY blending. +pub struct PlanetAlgorithm9 { + grid: &'static PlanetGrid, + radius: f64, + mod_x: f64, + mod_y: f64, + noise1: SimplexNoise, + noise2: SimplexNoise, +} + +impl PlanetAlgorithm9 { + pub fn new(planet: &Planet) -> Self { + let mut rand = DspRandom::new(planet.seed); + let seed1 = rand.next_seed(); + let seed2 = rand.next_seed(); + Self { + grid: get_planet_grid(), + radius: planet.radius as f64, + mod_x: planet.get_mod_x(), + mod_y: planet.get_mod_y(), + noise1: SimplexNoise::with_seed(seed1), + noise2: SimplexNoise::with_seed(seed2), + } + } +} + +impl PlanetAlgorithm for PlanetAlgorithm9 { + fn get_height(&self, index: usize) -> f64 { + let freq_scale_x: f64 = 0.01; + let freq_scale_y: f64 = 0.012; + let freq_scale_z: f64 = 0.01; + let noise_amplitude: f64 = 3.0; + let noise_offset: f64 = -0.2; + let noise2_amplitude: f64 = 0.9; + let noise2_offset: f64 = 0.5; + + let v = self.grid.get_vertex(index); + let world_x = (v.0 as f64) * self.radius; + let world_y = (v.1 as f64) * self.radius; + let world_z = (v.2 as f64) * self.radius; + + let layer1_noise = self.noise1.noise_3d_fbm( + world_x * freq_scale_x * 0.75, + world_y * freq_scale_y * 0.5, + world_z * freq_scale_z * 0.75, + 6, + 0.5, + 2.0, + ) * noise_amplitude + + noise_offset; + let layer2_noise = self.noise2.noise_3d_fbm( + world_x * (1.0 / 400.0), + world_y * (1.0 / 400.0), + world_z * (1.0 / 400.0), + 3, + 0.5, + 2.0, + ) * noise_amplitude + * noise2_amplitude + + noise2_offset; + + let clamped_layer2 = if layer2_noise > 0.0 { + layer2_noise * 0.5 + } else { + layer2_noise + }; + let combined_noise = layer1_noise + clamped_layer2; + let f = if combined_noise > 0.0 { + combined_noise * 0.5 + } else { + combined_noise * 1.6 + }; + + let shaped_height = if f > 0.0 { + levelize3(f, 0.7, 0.0) + } else { + levelize2(f, 0.5, 0.0) + } + 0.618; + + let stretched_height = if shaped_height > -1.0 { + shaped_height * 1.5 + } else { + shaped_height * 4.0 + }; + + let layer3_noise = self.noise1.noise_3d_fbm( + world_x * freq_scale_x * self.mod_x, + world_y * freq_scale_y * self.mod_x, + world_z * freq_scale_z * self.mod_x, + 6, + 0.5, + 2.0, + ) * noise_amplitude + + noise_offset; + let layer4_noise = self.noise2.noise_3d_fbm( + world_x * (1.0 / 400.0), + world_y * (1.0 / 400.0), + world_z * (1.0 / 400.0), + 3, + 0.5, + 2.0, + ) * noise_amplitude + * noise2_amplitude + + noise2_offset; + let clamped_layer4 = if layer4_noise > 0.0 { + layer4_noise * 0.5 + } else { + layer4_noise + }; + + let alt_height = ((layer3_noise + clamped_layer4 + 5.0) * 0.13).powf(6.0) * 24.0 - 24.0; + + let blend_factor = if stretched_height >= -self.mod_y { + 0.0 + } else { + (((stretched_height + self.mod_y).abs() / 5.0).min(1.0)).powf(1.0) + }; + + let blended_height = stretched_height * (1.0 - blend_factor) + alt_height * blend_factor; + let final_height = if blended_height > 0.0 { + blended_height * 0.5 + } else { + blended_height + }; + + self.radius + final_height + 0.2 + } +} diff --git a/inserter/src/algorithm/data/planet_algorithms/mod.rs b/inserter/src/algorithm/data/planet_algorithms/mod.rs new file mode 100644 index 0000000..5966964 --- /dev/null +++ b/inserter/src/algorithm/data/planet_algorithms/mod.rs @@ -0,0 +1,71 @@ +/// Planet algorithm implementations ported from PlanetAlgorithm0 through PlanetAlgorithm14. +/// Each algorithm lazily computes height data for a planet based on its seed and theme. +use super::planet::Planet; + +use algo0::PlanetAlgorithm0; +use algo1::PlanetAlgorithm1; +use algo10::PlanetAlgorithm10; +use algo11::PlanetAlgorithm11; +use algo12::PlanetAlgorithm12; +use algo13::PlanetAlgorithm13; +use algo14::PlanetAlgorithm14; +use algo2::PlanetAlgorithm2; +use algo3::PlanetAlgorithm3; +use algo4::PlanetAlgorithm4; +use algo5::PlanetAlgorithm5; +use algo6::PlanetAlgorithm6; +use algo7::PlanetAlgorithm7; +use algo8::PlanetAlgorithm8; +use algo9::PlanetAlgorithm9; + +mod algo0; +mod algo1; +mod algo10; +mod algo11; +mod algo12; +mod algo13; +mod algo14; +mod algo2; +mod algo3; +mod algo4; +mod algo5; +mod algo6; +mod algo7; +mod algo8; +mod algo9; + +/// Trait for planet algorithms. Each algorithm lazily computes height for individual vertices. +pub trait PlanetAlgorithm { + /// Compute the height for a single vertex index. + /// + /// # Arguments + /// * `index` - The vertex index (0..planet_raw_data::data_length()) + /// + /// # Returns + /// Height in game units (f64). + fn get_height(&self, index: usize) -> f64; +} + +/// Construct the algorithm matching the planet's algo ID. +/// Returns a boxed, fully-initialized algorithm ready for lazy height queries. +pub fn create_and_prepare_algo(planet: &Planet) -> Box { + let algo_id = planet.get_algo_id(); + match algo_id { + 0 => Box::new(PlanetAlgorithm0::new(planet)), + 1 => Box::new(PlanetAlgorithm1::new(planet)), + 2 => Box::new(PlanetAlgorithm2::new(planet)), + 3 => Box::new(PlanetAlgorithm3::new(planet)), + 4 => Box::new(PlanetAlgorithm4::new(planet)), + 5 => Box::new(PlanetAlgorithm5::new(planet)), + 6 => Box::new(PlanetAlgorithm6::new(planet)), + 7 => Box::new(PlanetAlgorithm7::new(planet)), + 8 => Box::new(PlanetAlgorithm8::new(planet)), + 9 => Box::new(PlanetAlgorithm9::new(planet)), + 10 => Box::new(PlanetAlgorithm10::new(planet)), + 11 => Box::new(PlanetAlgorithm11::new(planet)), + 12 => Box::new(PlanetAlgorithm12::new(planet)), + 13 => Box::new(PlanetAlgorithm13::new(planet)), + 14 => Box::new(PlanetAlgorithm14::new(planet)), + _ => panic!("Unknown planet algorithm ID: {}", algo_id), + } +} diff --git a/inserter/src/algorithm/data/planet_grid.rs b/inserter/src/algorithm/data/planet_grid.rs new file mode 100644 index 0000000..b7d28b9 --- /dev/null +++ b/inserter/src/algorithm/data/planet_grid.rs @@ -0,0 +1,166 @@ +use super::vector_f3::VectorF3; +use std::sync::OnceLock; + +// --------------------------------------------------------------------------- +// Constants – hardcoded for precision = 200 +// --------------------------------------------------------------------------- +pub const PRECISION: usize = 200; +pub const DATA_LENGTH: usize = (PRECISION + 1) * (PRECISION + 1) * 4; // 161604 +pub const STRIDE: i32 = ((PRECISION + 1) * 2) as i32; // 402 +const SUBSTRIDE: usize = PRECISION + 1; // 201 +const INDEX_MAP_PRECISION: usize = PRECISION >> 2; // 50 +const INDEX_MAP_FACE_STRIDE: usize = INDEX_MAP_PRECISION * INDEX_MAP_PRECISION; // 2500 +const INDEX_MAP_CORNER_STRIDE: usize = INDEX_MAP_FACE_STRIDE * 3; // 7500 +const INDEX_MAP_DATA_LENGTH: usize = INDEX_MAP_CORNER_STRIDE * 8; // 60000 + +// --------------------------------------------------------------------------- +// Pole constants (C# public static Vector3[] poles) +// --------------------------------------------------------------------------- +static POLES: [VectorF3; 6] = [ + VectorF3::right(), + VectorF3::left(), + VectorF3::up(), + VectorF3::down(), + VectorF3::forward(), + VectorF3::back(), +]; + +pub struct PlanetGrid { + pub vertices: Vec, + pub index_map: Vec, +} + +impl PlanetGrid { + pub fn get_vertex(&self, index: usize) -> &VectorF3 { + unsafe { &self.vertices.get_unchecked(index) } + } +} + +static PLANET_GRID: OnceLock = OnceLock::new(); + +pub fn get_planet_grid() -> &'static PlanetGrid { + PLANET_GRID.get_or_init(|| { + let mut vertices = vec![VectorF3::zero(); DATA_LENGTH]; + let mut index_map = vec![-1i32; INDEX_MAP_DATA_LENGTH]; + + for i in 0..DATA_LENGTH { + // C#: int num3 = index1 % num1; (num1 = stride) + let n3 = i % STRIDE as usize; + // C#: int num4 = index1 / num1; + let n4 = i / STRIDE as usize; + // C#: int num5 = num3 % num2; (num2 = substride) + let n5 = n3 % SUBSTRIDE; + // C#: int num6 = num4 % num2; + let n6 = n4 % SUBSTRIDE; + + // C#: int num7 = ((num3 >= num2 ? 1 : 0) + (num4 >= num2 ? 1 : 0) * 2) * 2 + // + (num5 >= num6 ? 0 : 1); + let n7 = (((n3 >= SUBSTRIDE) as usize) + ((n4 >= SUBSTRIDE) as usize) * 2) * 2 + + if n5 >= n6 { 0 } else { 1 }; + + let n8: f32 = if n5 >= n6 { + (PRECISION - n5) as f32 + } else { + n5 as f32 + }; + let n9: f32 = if n5 >= n6 { + n6 as f32 + } else { + (PRECISION - n6) as f32 + }; + + let n10: f32 = PRECISION as f32 - n9; + let t1: f32 = n9 / PRECISION as f32; + let t2: f32 = if n10 > 0.0f32 { n8 / n10 } else { 0.0f32 }; + + let (pole1, pole2, pole3, corner): (&VectorF3, &VectorF3, &VectorF3, usize) = match n7 { + 0 => (&POLES[2], &POLES[0], &POLES[4], 7), + 1 => (&POLES[3], &POLES[4], &POLES[0], 5), + 2 => (&POLES[2], &POLES[4], &POLES[1], 6), + 3 => (&POLES[3], &POLES[1], &POLES[4], 4), + 4 => (&POLES[2], &POLES[1], &POLES[5], 2), + 5 => (&POLES[3], &POLES[5], &POLES[1], 0), + 6 => (&POLES[2], &POLES[5], &POLES[0], 3), + 7 => (&POLES[3], &POLES[0], &POLES[5], 1), + _ => (&POLES[2], &POLES[0], &POLES[4], 7), + }; + + let slerp_a = VectorF3::slerp(pole1, pole3, t1); + let slerp_b = VectorF3::slerp(pole2, pole3, t1); + let vert = VectorF3::slerp(&slerp_a, &slerp_b, t2); + + // C#: int index2 = this.PositionHash(this.vertices[index1], corner); + let idx2 = position_hash(&vert, corner); + if index_map[idx2] == -1 { + index_map[idx2] = i as i32; + } + + vertices[i] = vert; + } + + // C#: forward-fill empty slots + for i in 1..INDEX_MAP_DATA_LENGTH { + if index_map[i] == -1 { + index_map[i] = index_map[i - 1]; + } + } + + PlanetGrid { + vertices, + index_map, + } + }) +} + +// --------------------------------------------------------------------------- +// trans (C# lines 433–439) +// --------------------------------------------------------------------------- +fn trans(x: f32, pr: usize) -> usize { + let num = ((x as f64 + 0.23f64).sqrt() - 0.47958320379257204f64) / 0.62947052717208862f64 + * (pr as f64); + let idx = num as usize; + if idx >= pr { + pr - 1 + } else { + idx + } +} + +// --------------------------------------------------------------------------- +// position_hash (internal) (C# lines 441–475) +// --------------------------------------------------------------------------- +pub fn position_hash(v: &VectorF3, corner: usize) -> usize { + let corner = if corner == 0 { + ((v.0 > 0.0) as usize) + ((v.1 > 0.0) as usize) * 2 + ((v.2 > 0.0) as usize) * 4 + } else { + corner + }; + + let vx = if v.0 < 0.0 { -v.0 } else { v.0 }; + let vy = if v.1 < 0.0 { -v.1 } else { v.1 }; + let vz = if v.2 < 0.0 { -v.2 } else { v.2 }; + + if vx < 1e-6 && vy < 1e-6 && vz < 1e-6 { + return 0; + } + + let n1: usize; + let n2: usize; + let n3: usize; + + if vx >= vy && vx >= vz { + n1 = 0; + n2 = trans((vz / vx) as f32, INDEX_MAP_PRECISION); + n3 = trans((vy / vx) as f32, INDEX_MAP_PRECISION); + } else if vy >= vx && vy >= vz { + n1 = 1; + n2 = trans((vx / vy) as f32, INDEX_MAP_PRECISION); + n3 = trans((vz / vy) as f32, INDEX_MAP_PRECISION); + } else { + n1 = 2; + n2 = trans((vx / vz) as f32, INDEX_MAP_PRECISION); + n3 = trans((vy / vz) as f32, INDEX_MAP_PRECISION); + } + + n2 + n3 * INDEX_MAP_PRECISION + n1 * INDEX_MAP_FACE_STRIDE + corner * INDEX_MAP_CORNER_STRIDE +} diff --git a/inserter/src/algorithm/data/planet_raw_data.rs b/inserter/src/algorithm/data/planet_raw_data.rs new file mode 100644 index 0000000..9f69138 --- /dev/null +++ b/inserter/src/algorithm/data/planet_raw_data.rs @@ -0,0 +1,75 @@ +use crate::algorithm::data::planet::Planet; +use crate::algorithm::data::planet_algorithms::{create_and_prepare_algo, PlanetAlgorithm}; +use crate::algorithm::data::planet_grid::{ + get_planet_grid, position_hash, PlanetGrid, DATA_LENGTH, PRECISION, STRIDE, +}; + +use super::vector_f3::VectorF3; +use std::f64::consts::PI; + +pub struct PlanetRawData { + grid: &'static PlanetGrid, + algo: Box, + cache: Vec, +} + +impl PlanetRawData { + pub fn new(planet: &Planet) -> Self { + Self { + grid: get_planet_grid(), + algo: create_and_prepare_algo(planet), + cache: vec![f32::NAN; DATA_LENGTH], + } + } + + #[inline] + fn get_height(&mut self, index: usize) -> f32 { + let val = self.cache[index]; + if val.is_nan() { + let h = (self.algo.get_height(index) * 100.0) as u16 as f32; + self.cache[index] = h; + h + } else { + val + } + } + + pub fn query_height_normalized(&mut self, vpos_normalized: &VectorF3) -> f32 { + let index1 = self.grid.index_map[position_hash(vpos_normalized, 0)]; + + let num1: f64 = (PI / (PRECISION as f64 * 2.0)) * 1.2_f64; + let num2: f64 = num1 * num1; + + let mut num3: f32 = 0.0f32; + let mut num4: f32 = 0.0f32; + + for i3 in -1..=3 { + let i4 = index1 + i3 * STRIDE; + for i2 in -1_i32..=3 { + let idx4 = (i4 + i2) as usize; + if idx4 < DATA_LENGTH { + let sqr_mag = self.grid.vertices[idx4].distance_sq_from(vpos_normalized); + if (sqr_mag as f64) <= num2 { + let num5 = 1.0f32 - (sqr_mag.sqrt() / num1 as f32); + let num6 = self.get_height(idx4); + num3 += num5; + num4 += num6 * num5; + } + } + } + } + + if num3 != 0.0f32 { + num4 / num3 * 0.01 + } else { + self.get_height(0) * 0.01 + } + } + + #[inline] + pub fn query_height(&mut self, vpos: &VectorF3) -> f32 { + let mut vpos = *vpos; + vpos.normalize(); + self.query_height_normalized(&vpos) + } +} diff --git a/inserter/src/algorithm/data/pose.rs b/inserter/src/algorithm/data/pose.rs new file mode 100644 index 0000000..55b2985 --- /dev/null +++ b/inserter/src/algorithm/data/pose.rs @@ -0,0 +1,16 @@ +use super::quaternion::Quaternion; +use super::vector_f3::VectorF3; + +/// Port of Unity's `Pose(Vector3 position, Quaternion rotation)`. +/// Uses VectorF3 for position (f32-based, matching VectorLF3 → VectorF3 mapping). +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct Pose { + pub position: VectorF3, + pub rotation: Quaternion, +} + +impl Pose { + pub fn new(position: VectorF3, rotation: Quaternion) -> Self { + Self { position, rotation } + } +} diff --git a/inserter/src/algorithm/data/quaternion.rs b/inserter/src/algorithm/data/quaternion.rs new file mode 100644 index 0000000..a250ab6 --- /dev/null +++ b/inserter/src/algorithm/data/quaternion.rs @@ -0,0 +1,209 @@ +use super::vector_f3::VectorF3; +use std::ops::Mul; + +/// Port of Unity's Quaternion (x, y, z, w) using f32. +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct Quaternion { + pub x: f32, + pub y: f32, + pub z: f32, + pub w: f32, +} + +impl Quaternion { + pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self { + Self { x, y, z, w } + } + + pub fn identity() -> Self { + Self { + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + } + } + + /// Port of Unity's `Quaternion.AngleAxis(angle_degrees, axis)`. + /// `angle_degrees` is in degrees (same as Unity's AngleAxis). + pub fn angle_axis(angle_degrees: f32, axis: &VectorF3) -> Self { + let half_rad = (angle_degrees * std::f32::consts::PI / 180.0) * 0.5; + let s = half_rad.sin(); + let axis_norm = axis.normalized(); + Self { + x: axis_norm.0 * s, + y: axis_norm.1 * s, + z: axis_norm.2 * s, + w: half_rad.cos(), + } + } + + /// Port of `Maths.QInvRotateLF(Quaternion, VectorLF3)` — inverse rotates a VectorF3. + /// Since the user specified VectorLF3 maps to VectorF3 in Rust, this uses f32. + pub fn q_inv_rotate_lf(&self, v: &VectorF3) -> VectorF3 { + let vx = v.0 * 2.0; + let vy = v.1 * 2.0; + let vz = v.2 * 2.0; + let num1 = self.w * self.w - 0.5; + let num2 = self.x * vx + self.y * vy + self.z * vz; + VectorF3( + vx * num1 - (self.y * vz - self.z * vy) * self.w + self.x * num2, + vy * num1 - (self.z * vx - self.x * vz) * self.w + self.y * num2, + vz * num1 - (self.x * vy - self.y * vx) * self.w + self.z * num2, + ) + } + + /// Port of Unity's `Quaternion.FromToRotation(fromDirection, toDirection)`. + /// + /// Creates a rotation that rotates `from` to align with `to`. + pub fn from_to_rotation(from: &VectorF3, to: &VectorF3) -> Self { + let from_mag_sq = from.magnitude_sq(); + let to_mag_sq = to.magnitude_sq(); + if from_mag_sq < 1e-10 || to_mag_sq < 1e-10 { + return Self::identity(); + } + + let from_norm = from.normalized(); + let to_norm = to.normalized(); + + let dot = VectorF3::dot(&from_norm, &to_norm); + + // Vectors are nearly opposite — 180° rotation around an orthogonal axis + if dot < -0.999999 { + // Find an orthogonal axis to rotate around + let abs_x = from_norm.0.abs(); + let abs_y = from_norm.1.abs(); + let abs_z = from_norm.2.abs(); + + let axis = if abs_x < abs_y && abs_x < abs_z { + VectorF3::cross(&from_norm, &VectorF3(1.0, 0.0, 0.0)) + } else if abs_y < abs_z { + VectorF3::cross(&from_norm, &VectorF3(0.0, 1.0, 0.0)) + } else { + VectorF3::cross(&from_norm, &VectorF3(0.0, 0.0, 1.0)) + }; + + let axis_norm = axis.normalized(); + Self { + x: axis_norm.0, + y: axis_norm.1, + z: axis_norm.2, + w: 0.0, + } + } else { + // Standard case: rotation axis = cross(from, to), angle = acos(dot) + let cross = VectorF3::cross(&from_norm, &to_norm); + let s = ((1.0 + dot) * 2.0).sqrt(); + let inv_s = 1.0 / s; + let q = Self { + x: cross.0 * inv_s, + y: cross.1 * inv_s, + z: cross.2 * inv_s, + w: s * 0.5, + }; + // Normalize + let mag = (q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w).sqrt(); + Self { + x: q.x / mag, + y: q.y / mag, + z: q.z / mag, + w: q.w / mag, + } + } + } + + /// Port of `Maths.QRotateLF(Quaternion, VectorLF3)` — rotates a VectorF3. + pub fn q_rotate_lf(&self, v: &VectorF3) -> VectorF3 { + let vx = v.0 * 2.0; + let vy = v.1 * 2.0; + let vz = v.2 * 2.0; + let num1 = self.w * self.w - 0.5; + let num2 = self.x * vx + self.y * vy + self.z * vz; + VectorF3( + vx * num1 + (self.y * vz - self.z * vy) * self.w + self.x * num2, + vy * num1 + (self.z * vx - self.x * vz) * self.w + self.y * num2, + vz * num1 + (self.x * vy - self.y * vx) * self.w + self.z * num2, + ) + } +} + +/// Port of Unity's `Quaternion * Vector3` — rotates a VectorF3 by a Quaternion. +/// +/// Uses the full 3×3 rotation matrix derived from the quaternion, matching Unity's +/// internal implementation (which is Fused in IL2CPP builds). +impl Mul<&VectorF3> for &Quaternion { + type Output = VectorF3; + + fn mul(self, point: &VectorF3) -> VectorF3 { + let num1 = self.x * 2.0; + let num2 = self.y * 2.0; + let num3 = self.z * 2.0; + let num4 = self.x * num1; + let num5 = self.y * num2; + let num6 = self.z * num3; + let num7 = self.x * num2; + let num8 = self.x * num3; + let num9 = self.y * num3; + let num10 = self.w * num1; + let num11 = self.w * num2; + let num12 = self.w * num3; + VectorF3( + (1.0 - (num5 + num6)) * point.0 + (num7 - num12) * point.1 + (num8 + num11) * point.2, + (num7 + num12) * point.0 + (1.0 - (num4 + num6)) * point.1 + (num9 - num10) * point.2, + (num8 - num11) * point.0 + (num9 + num10) * point.1 + (1.0 - (num4 + num5)) * point.2, + ) + } +} + +/// Port of `Maths.QMultiply` for quaternion composition (equivalent to Unity's `q1 * q2`). +impl Mul for Quaternion { + type Output = Quaternion; + + fn mul(self, rhs: Quaternion) -> Quaternion { + Quaternion { + x: self.x * rhs.w + self.y * rhs.z - self.z * rhs.y + self.w * rhs.x, + y: -self.x * rhs.z + self.y * rhs.w + self.z * rhs.x + self.w * rhs.y, + z: self.x * rhs.y - self.y * rhs.x + self.z * rhs.w + self.w * rhs.z, + w: -self.x * rhs.x - self.y * rhs.y - self.z * rhs.z + self.w * rhs.w, + } + } +} + +impl Mul<&Quaternion> for Quaternion { + type Output = Quaternion; + + fn mul(self, rhs: &Quaternion) -> Quaternion { + Quaternion { + x: self.x * rhs.w + self.y * rhs.z - self.z * rhs.y + self.w * rhs.x, + y: -self.x * rhs.z + self.y * rhs.w + self.z * rhs.x + self.w * rhs.y, + z: self.x * rhs.y - self.y * rhs.x + self.z * rhs.w + self.w * rhs.z, + w: -self.x * rhs.x - self.y * rhs.y - self.z * rhs.z + self.w * rhs.w, + } + } +} + +impl Mul for &Quaternion { + type Output = Quaternion; + + fn mul(self, rhs: Quaternion) -> Quaternion { + Quaternion { + x: self.x * rhs.w + self.y * rhs.z - self.z * rhs.y + self.w * rhs.x, + y: -self.x * rhs.z + self.y * rhs.w + self.z * rhs.x + self.w * rhs.y, + z: self.x * rhs.y - self.y * rhs.x + self.z * rhs.w + self.w * rhs.z, + w: -self.x * rhs.x - self.y * rhs.y - self.z * rhs.z + self.w * rhs.w, + } + } +} + +impl Mul<&Quaternion> for &Quaternion { + type Output = Quaternion; + + fn mul(self, rhs: &Quaternion) -> Quaternion { + Quaternion { + x: self.x * rhs.w + self.y * rhs.z - self.z * rhs.y + self.w * rhs.x, + y: -self.x * rhs.z + self.y * rhs.w + self.z * rhs.x + self.w * rhs.y, + z: self.x * rhs.y - self.y * rhs.x + self.z * rhs.w + self.w * rhs.z, + w: -self.x * rhs.x - self.y * rhs.y - self.z * rhs.z + self.w * rhs.w, + } + } +} diff --git a/inserter/src/algorithm/data/random.rs b/inserter/src/algorithm/data/random.rs index 6a7aac2..34343dd 100644 --- a/inserter/src/algorithm/data/random.rs +++ b/inserter/src/algorithm/data/random.rs @@ -1,3 +1,4 @@ +#[derive(Debug)] pub struct DspRandom { inext: usize, inextp: usize, @@ -7,26 +8,44 @@ pub struct DspRandom { impl DspRandom { pub fn new(seed: i32) -> Self { let mut seed_array = [0; 56]; - let mut num1 = 161803398_i32.wrapping_sub(seed.wrapping_abs()); + let mut num1 = 161803398 - seed.abs(); seed_array[55] = num1; let mut num2 = 1; - for index1 in 1..55 { - let index2 = (21 * index1) % 55; + let mut index2 = 0; + for _ in 1..55 { + index2 += 21; + if index2 >= 55 { + index2 -= 55; + } seed_array[index2] = num2; - num2 = num1.wrapping_sub(num2); + num2 = num1 - num2; if num2 < 0 { num2 += i32::MAX; } num1 = seed_array[index2] } - for _index3 in 1..5 { - for index4 in 1..56 { - let mut val = seed_array[index4].wrapping_sub(seed_array[1 + (index4 + 30) % 55]); - if val < 0 { - val += i32::MAX; - } - seed_array[index4] = val; + + let ptr = seed_array.as_mut_ptr(); + + let (chunk1_lhs, chunk1_rhs, chunk2_lhs, chunk2_rhs) = unsafe { + ( + &mut *ptr.add(1).cast::<[i32; 24]>(), + &*ptr.add(32).cast::<[i32; 24]>(), + &mut *ptr.add(25).cast::<[i32; 31]>(), + &*ptr.add(1).cast::<[i32; 31]>(), + ) + }; + + let update = |(lhs, rhs): (&mut i32, &i32)| { + *lhs = lhs.wrapping_sub(*rhs); + if lhs.is_negative() { + *lhs += i32::MAX; } + }; + + for _ in 1..5 { + chunk1_lhs.iter_mut().zip(chunk1_rhs).for_each(update); + chunk2_lhs.iter_mut().zip(chunk2_rhs).for_each(update); } Self { @@ -36,6 +55,17 @@ impl DspRandom { } } + pub fn new_system_random(seed: i32) -> Self { + // Somehow System.Random mistyped inextp + // https://github.com/dotnet/runtime/issues/23198 + let r = Self::new(seed); + Self { + inext: 0, + inextp: 21, + seed_array: r.seed_array, + } + } + fn sample(&mut self) -> f64 { self.inext += 1; if self.inext >= 56 { @@ -45,7 +75,7 @@ impl DspRandom { if self.inextp >= 56 { self.inextp = 1 } - let mut num = self.seed_array[self.inext].wrapping_sub(self.seed_array[self.inextp]); + let mut num = self.seed_array[self.inext] - self.seed_array[self.inextp]; if num < 0 { num += i32::MAX; } @@ -65,7 +95,11 @@ impl DspRandom { #[inline] pub fn next_i32(&mut self, max_value: i32) -> i32 { - (self.sample() * (max_value as f64)) as i32 + if max_value <= 1 { + 0 + } else { + (self.sample() * (max_value as f64)) as i32 + } } #[inline] diff --git a/inserter/src/algorithm/data/random_table.rs b/inserter/src/algorithm/data/random_table.rs new file mode 100644 index 0000000..e98cdcf --- /dev/null +++ b/inserter/src/algorithm/data/random_table.rs @@ -0,0 +1,63 @@ +use std::sync::LazyLock; + +use super::random::DspRandom; +use crate::algorithm::data::vector3::Vector3; + +const TABLE_SIZE: usize = 65536; + +/// Pre-generated random table matching C# `RandomTable`. +/// `SphericNormal` generates normally-distributed 3D points (Box-Muller + rejection sampling) +/// for use in planet terrain algorithms (algo4, algo10, etc.). +pub struct RandomTable { + spheric_normal: Box<[Vector3; TABLE_SIZE]>, +} + +static RANDOM_TABLE: LazyLock = LazyLock::new(|| RandomTable::generate()); + +/// Box-Muller transform matching C# `RandomTable.Normal(System.Random rand)`. +/// Returns a standard normally-distributed value N(0,1). +fn normal(rand: &mut DspRandom) -> f64 { + let num = rand.next_f64(); + let a = rand.next_f64() * std::f64::consts::PI * 2.0; + (-2.0 * (1.0 - num).ln()).sqrt() * a.sin() +} + +impl RandomTable { + /// Generates the pre-computed spheric_normal table using DspRandom with seed 1001, + /// exactly matching C# `RandomTable.GenerateSphericNormal()`. + fn generate() -> Self { + let mut rand = DspRandom::new_system_random(1001); + let mut spheric_normal = Box::new([Vector3(0.0, 0.0, 0.0); TABLE_SIZE]); + + for entry in spheric_normal.iter_mut() { + let result = loop { + let n1 = rand.next_f64() * 2.0 - 1.0; + let n2 = rand.next_f64() * 2.0 - 1.0; + let n3 = rand.next_f64() * 2.0 - 1.0; + let n4 = normal(&mut rand); + + if n4 <= 5.0 && n4 >= -5.0 { + let d = n1 * n1 + n2 * n2 + n3 * n3; + if d <= 1.0 && d >= 1e-6 { + let num5 = n4 / d.sqrt(); + break (n1 * num5, n2 * num5, n3 * num5); + } + } + }; + + *entry = Vector3(result.0, result.1, result.2); + } + + Self { spheric_normal } + } + + /// Returns a normally-distributed 3D point scaled by `scale`, advancing the seed + /// as an index into the pre-generated table (matching C# `RandomTable.SphericNormal`). + /// + /// The `seed` is incremented and masked to 16 bits (0..65535) on each call. + pub fn spheric_normal(seed: &mut i32, scale: f64) -> Vector3 { + *seed = seed.wrapping_add(1) & 0xFFFF; + let v = RANDOM_TABLE.spheric_normal[*seed as usize]; + v * scale + } +} diff --git a/inserter/src/algorithm/data/rule.rs b/inserter/src/algorithm/data/rule.rs new file mode 100644 index 0000000..4624951 --- /dev/null +++ b/inserter/src/algorithm/data/rule.rs @@ -0,0 +1,141 @@ +use super::galaxy::Galaxy; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(tag = "type", content = "value")] +pub enum Condition { + Eq(f32), + Neq(f32), + Lt(f32), + Lte(f32), + Gt(f32), + Gte(f32), + Between(f32, f32), + NotBetween(f32, f32), +} + +impl Condition { + pub fn eval(&self, value: f32) -> bool { + match self { + Condition::Eq(f) => value == *f, + Condition::Neq(f) => value != *f, + Condition::Lt(f) => value < *f, + Condition::Lte(f) => value <= *f, + Condition::Gt(f) => value > *f, + Condition::Gte(f) => value >= *f, + Condition::Between(f1, f2) => *f1 <= value && value <= *f2, + Condition::NotBetween(f1, f2) => *f1 > value || value > *f2, + } + } +} + +#[macro_export] +macro_rules! evaluate_safe { + ($galaxy:expr, $evaluation:expr, |$sp:ident| $logic:expr) => {{ + let mut result: u64 = 0; + for (index, $sp) in $galaxy.stars.iter().take($evaluation.get_len()).enumerate() { + if $evaluation.is_known(index) { + continue; + } + if $logic { + result |= 1 << index; + } + } + result + }}; +} + +#[macro_export] +macro_rules! evaluate_unsafe { + ($galaxy:expr, $evaluation:expr, |$sp:ident| $logic:expr) => {{ + let mut result: u64 = 0; + for (index, $sp) in $galaxy.stars.iter().take($evaluation.get_len()).enumerate() { + if $evaluation.is_known(index) { + if !$sp.is_safe() { + $sp.load_planets() + } + continue; + } + + if $logic { + result |= 1 << index; + } + $sp.mark_safe(); + } + result + }}; +} + +#[allow(unused_variables)] +pub trait Rule { + fn get_priority(&self) -> i32 { + 0 + } + + fn evaluate(&self, galaxy: &Galaxy, evaluation: &Evaluation) -> u64 { + 0 + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Evaluation { + value: u64, + unknown: u64, + max_len: usize, +} + +impl Evaluation { + pub fn new(size: usize) -> Self { + Self { + value: 0, + unknown: u64::MAX >> (u64::BITS - size as u32), + max_len: size, + } + } + + #[inline] + pub fn is_known(&self, index: usize) -> bool { + (self.unknown & (1 << index)) == 0 + } + + /// Returns the minium number of stars that still needs to be evaluated + /// Useful for unsafe rules + #[inline] + pub fn get_len(&self) -> usize { + self.max_len + } + + #[inline] + fn load_max_len(&mut self) { + self.max_len = (u64::BITS - self.unknown.leading_zeros()) as usize; + } + + #[inline] + pub fn accept_many(&mut self, indices: u64) { + self.value |= self.unknown & indices; + self.unknown &= !indices; + self.load_max_len(); + } + + #[inline] + pub fn reject_others(&mut self, indices: u64) { + self.value &= !self.unknown | indices; + self.unknown &= indices; + self.load_max_len(); + } + + #[inline] + pub fn collect_known(&self) -> u64 { + !self.unknown & self.value + } + + #[inline] + pub fn collect_unknown(&self) -> u64 { + self.unknown | self.value + } + + #[inline] + pub fn is_done(&self) -> bool { + self.max_len == 0 + } +} diff --git a/inserter/src/algorithm/data/simplex_noise.rs b/inserter/src/algorithm/data/simplex_noise.rs new file mode 100644 index 0000000..41913a0 --- /dev/null +++ b/inserter/src/algorithm/data/simplex_noise.rs @@ -0,0 +1,253 @@ +/// Port of SimplexNoise from DSP game (SimpleNoise.cs) +/// Uses DspRandom for seed-based permutation shuffling. +use super::random::DspRandom; + +// ---- Static gradient tables (constant) ---- + +const GRAD3: [[f64; 3]; 12] = [ + [1.0, 1.0, 0.0], + [-1.0, 1.0, 0.0], + [1.0, -1.0, 0.0], + [-1.0, -1.0, 0.0], + [1.0, 0.0, 1.0], + [-1.0, 0.0, 1.0], + [1.0, 0.0, -1.0], + [-1.0, 0.0, -1.0], + [0.0, 1.0, 1.0], + [0.0, -1.0, 1.0], + [0.0, 1.0, -1.0], + [0.0, -1.0, -1.0], +]; + +// ---- Constants ---- +const F3: f64 = 1.0 / 3.0; +const G3: f64 = 1.0 / 6.0; + +/// SimplexNoise implementation with DSP-specific permutation. +pub struct SimplexNoise { + perm: [i16; 512], + perm_mod12: [i16; 512], +} + +impl SimplexNoise { + /// Create with seed-shuffled permutation. + pub fn with_seed(seed: i32) -> Self { + let mut noise = Self { + perm: [0; 512], + perm_mod12: [0; 512], + }; + noise.init_member_seed(seed); + noise + } + + /// Initialize with seed-shuffled permutation (using DspRandom). + fn init_member_seed(&mut self, seed: i32) { + let mut p: [i16; 256] = std::array::from_fn(|i| i as i16); + + // Only shuffle when seed != 0 (matching C# behavior where seed 0 might be special) + // Actually the C# always shuffles. Let's shuffle based on seed. + let mut rand = DspRandom::new(seed); + for i1 in 0..256 { + let i2 = rand.next_i32(256) as usize; + let num = p[i1]; + p[i1] = p[i2]; + p[i2] = num; + } + + for i in 0..512 { + self.perm[i] = p[i & 255]; + self.perm_mod12[i] = (self.perm[i] % 12) as i16; + } + } + + /// 3D simplex noise. + pub fn noise_3d(&self, xin: f64, yin: f64, zin: f64) -> f64 { + let num1 = (xin + yin + zin) * F3; + let num2 = fastfloor(xin + num1); + let num3 = fastfloor(yin + num1); + let num4 = fastfloor(zin + num1); + let num5 = (num2 + num3 + num4) as f64 * G3; + let num6 = num2 as f64 - num5; + let num7 = num3 as f64 - num5; + let num8 = num4 as f64 - num5; + let x1 = xin - num6; + let y1 = yin - num7; + let z1 = zin - num8; + + let (n9, n10, n11, n12, n13, n14) = if x1 >= y1 { + if y1 >= z1 { + (1, 0, 0, 1, 1, 0) + } else if x1 >= z1 { + (1, 0, 0, 1, 0, 1) + } else { + (0, 0, 1, 1, 0, 1) + } + } else if y1 < z1 { + (0, 0, 1, 0, 1, 1) + } else if x1 < z1 { + (0, 1, 0, 0, 1, 1) + } else { + (0, 1, 0, 1, 1, 0) + }; + + let x2 = x1 - n9 as f64 + G3; + let y2 = y1 - n10 as f64 + G3; + let z2 = z1 - n11 as f64 + G3; + let x3 = x1 - n12 as f64 + 2.0 * G3; + let y3 = y1 - n13 as f64 + 2.0 * G3; + let z3 = z1 - n14 as f64 + 2.0 * G3; + let x4 = x1 - 1.0 + 3.0 * G3; + let y4 = y1 - 1.0 + 3.0 * G3; + let z4 = z1 - 1.0 + 3.0 * G3; + + let n15 = (num2 & 255) as usize; + let n16 = (num3 & 255) as usize; + let idx1 = (num4 & 255) as usize; + + let index2 = self.perm_mod12 + [(n15 + self.perm[(n16 + self.perm[idx1] as usize) as usize] as usize) as usize] + as usize; + let index3 = self.perm_mod12[(n15 + + n9 + + self.perm[(n16 + n10 + self.perm[(idx1 + n11) as usize] as usize) as usize] as usize) + as usize] as usize; + let index4 = self.perm_mod12[(n15 + + n12 + + self.perm[(n16 + n13 + self.perm[(idx1 + n14) as usize] as usize) as usize] as usize) + as usize] as usize; + let index5 = self.perm_mod12[(n15 + + 1 + + self.perm[(n16 + 1 + self.perm[(idx1 + 1) as usize] as usize) as usize] as usize) + as usize] as usize; + + let n17 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; + let n18 = if n17 < 0.0 { + 0.0 + } else { + let n19 = n17 * n17; + n19 * n19 * dot3_3d(&GRAD3[index2], x1, y1, z1) + }; + + let n20 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; + let n21 = if n20 < 0.0 { + 0.0 + } else { + let n22 = n20 * n20; + n22 * n22 * dot3_3d(&GRAD3[index3], x2, y2, z2) + }; + + let n23 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; + let n24 = if n23 < 0.0 { + 0.0 + } else { + let n25 = n23 * n23; + n25 * n25 * dot3_3d(&GRAD3[index4], x3, y3, z3) + }; + + let n26 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4; + let n27 = if n26 < 0.0 { + 0.0 + } else { + let n28 = n26 * n26; + n28 * n28 * dot3_3d(&GRAD3[index5], x4, y4, z4) + }; + + 32.696434 * (n18 + n21 + n24 + n27) + } + + // ---- FBM (Fractal Brownian Motion) variants ---- + + /// 3D FBM noise. + pub fn noise_3d_fbm( + &self, + x: f64, + y: f64, + z: f64, + n_octaves: i32, + delta_amp: f64, + delta_wlen: f64, + ) -> f64 { + let mut num1 = 0.0; + let mut num2 = 0.5; + let mut x = x; + let mut y = y; + let mut z = z; + for _ in 0..n_octaves { + num1 += self.noise_3d(x, y, z) * num2; + num2 *= delta_amp; + x *= delta_wlen; + y *= delta_wlen; + z *= delta_wlen; + } + num1 + } + + /// 3D FBM noise with custom initial amplitude. + pub fn noise_3d_fbm_initial_amp( + &self, + x: f64, + y: f64, + z: f64, + n_octaves: i32, + delta_amp: f64, + delta_wlen: f64, + initial_amp: f64, + ) -> f64 { + let mut num1 = 0.0; + let mut num2 = initial_amp; + let mut x = x; + let mut y = y; + let mut z = z; + for _ in 0..n_octaves { + num1 += self.noise_3d(x, y, z) * num2; + num2 *= delta_amp; + x *= delta_wlen; + y *= delta_wlen; + z *= delta_wlen; + } + num1 + } + + /// 3D ridged noise. + pub fn ridged_noise( + &self, + x: f64, + y: f64, + z: f64, + n_octaves: i32, + delta_amp: f64, + delta_wlen: f64, + initial_amp: f64, + ) -> f64 { + let mut num1 = 0.0; + let mut num2 = initial_amp; + let mut x = x; + let mut y = y; + let mut z = z; + for _ in 0..n_octaves { + num1 += (self.noise_3d(x, y, z) * num2).abs(); + num2 *= delta_amp; + x *= delta_wlen; + y *= delta_wlen; + z *= delta_wlen; + } + num1 + } +} + +// ---- Helper functions ---- + +#[inline] +fn fastfloor(x: f64) -> i32 { + let num = x as i32; + if x >= num as f64 { + num + } else { + num - 1 + } +} + +#[inline] +fn dot3_3d(g: &[f64; 3], x: f64, y: f64, z: f64) -> f64 { + g[0] * x + g[1] * y + g[2] * z +} diff --git a/inserter/src/algorithm/data/star.rs b/inserter/src/algorithm/data/star.rs index b099673..abb4388 100644 --- a/inserter/src/algorithm/data/star.rs +++ b/inserter/src/algorithm/data/star.rs @@ -1,19 +1,16 @@ use super::enums::{SpectrType, StarType}; use super::game_desc::GameDesc; -use super::macros::macros::{lazy_getter, lazy_getter_ref}; use super::random::DspRandom; use super::vector3::Vector3; use serde::ser::{Serialize, SerializeStruct, Serializer}; -use std::cell::{RefCell, UnsafeCell}; +use std::cell::{OnceCell, RefCell}; use std::f64::consts::PI; -#[allow(dead_code)] #[derive(Debug)] pub struct Star<'a> { pub game_desc: &'a GameDesc, pub used_theme_ids: RefCell>, pub index: usize, - pub seed: i32, pub name_seed: i32, pub position: Vector3, pub level: f32, @@ -25,24 +22,27 @@ pub struct Star<'a> { lifetime_factor: f64, radius_factor: f64, pub planets_seed: i32, + safety_factor_modifier: f64, + max_hive_count_modifier: i32, mass_params: (f64, f64, f64, f64, f32), - get_unmodified_mass: UnsafeCell>, - get_resource_coef: UnsafeCell>, - get_lifetime: UnsafeCell>, - get_age: UnsafeCell>, - get_temperature_factor: UnsafeCell>, - get_unmodified_temperature: UnsafeCell>, - get_temperature: UnsafeCell>, - get_class_factor: UnsafeCell>, - get_spectr: UnsafeCell>, - // get_color: UnsafeCell>, - get_luminosity: UnsafeCell>, - get_radius: UnsafeCell>, - get_light_balance_radius: UnsafeCell>, - get_habitable_radius: UnsafeCell>, - get_mass: UnsafeCell>, - get_orbit_scaler: UnsafeCell>, - get_dyson_radius: UnsafeCell>, + unmodified_mass: OnceCell, + resource_coef: OnceCell, + age: OnceCell, + temperature_factor: OnceCell, + unmodified_temperature: OnceCell, + temperature: OnceCell, + class_factor: OnceCell, + spectr: OnceCell, + luminosity: OnceCell, + radius: OnceCell, + light_balance_radius: OnceCell, + habitable_radius: OnceCell, + mass: OnceCell, + orbit_scaler: OnceCell, + dyson_radius: OnceCell, + hive_rand: RefCell, + max_hive_count: OnceCell, + initial_hive_count: OnceCell, } impl<'a> Star<'a> { @@ -59,22 +59,26 @@ impl<'a> Star<'a> { let mut rand2 = DspRandom::new(rand1.next_seed()); rand1.next_f64(); let planets_seed = rand1.next_seed(); - let r1_1 = rand2.next_f64(); - let r2_1 = rand2.next_f64(); + let mass_random1 = rand2.next_f64(); + let mass_random2 = rand2.next_f64(); let age_factor = rand2.next_f64(); - let rn = rand2.next_f64(); - let rt = rand2.next_f64(); - let age_num1 = (rn * 0.1 + 0.95) as f32; - let age_num2 = (rt * 0.4 + 0.8) as f32; - let age_num3 = (rt * 9.0 + 1.0) as f32; + let age_num1_rand = rand2.next_f64(); + let age_factor_rand = rand2.next_f64(); + let age_num1 = (age_num1_rand * 0.1 + 0.95) as f32; + let age_num2 = (age_factor_rand * 0.4 + 0.8) as f32; + let age_num3 = (age_factor_rand * 9.0 + 1.0) as f32; let mass_factor = if index == 0 { 0.0 } else { rand2.next_f64() }; let lifetime_factor = rand2.next_f64(); - let y = rand2.next_f64() * 0.4 - 0.2; - let radius_factor = 2_f64.powf(y); + let radius_exponent = rand2.next_f64() * 0.4 - 0.2; + let radius_factor = 2_f64.powf(radius_exponent); + let hive_seed = rand2.next_seed(); + let mut hive_rand = DspRandom::new(hive_seed); + let safety_factor_modifier = hive_rand.next_f64(); + let max_hive_count_modifier = hive_rand.next_i32(1000); let mass_params = ( - r1_1, - r2_1, - y, + mass_random1, + mass_random2, + radius_exponent, mass_factor, match need_spectr { SpectrType::M => -3_f32, @@ -87,7 +91,6 @@ impl<'a> Star<'a> { game_desc, used_theme_ids: RefCell::new(vec![]), index, - seed, name_seed, position, level: (index as f32) / ((game_desc.star_count - 1) as f32), @@ -100,85 +103,106 @@ impl<'a> Star<'a> { radius_factor, planets_seed, mass_params, - get_unmodified_mass: UnsafeCell::new(None), - get_resource_coef: UnsafeCell::new(None), - get_lifetime: UnsafeCell::new(None), - get_age: UnsafeCell::new(None), - get_temperature_factor: UnsafeCell::new(None), - get_unmodified_temperature: UnsafeCell::new(None), - get_temperature: UnsafeCell::new(None), - get_class_factor: UnsafeCell::new(None), - get_spectr: UnsafeCell::new(None), - // get_color: UnsafeCell::new(None), - get_luminosity: UnsafeCell::new(None), - get_radius: UnsafeCell::new(None), - get_light_balance_radius: UnsafeCell::new(None), - get_habitable_radius: UnsafeCell::new(None), - get_mass: UnsafeCell::new(None), - get_orbit_scaler: UnsafeCell::new(None), - get_dyson_radius: UnsafeCell::new(None), + safety_factor_modifier, + max_hive_count_modifier, + unmodified_mass: OnceCell::new(), + resource_coef: OnceCell::new(), + age: OnceCell::new(), + temperature_factor: OnceCell::new(), + unmodified_temperature: OnceCell::new(), + temperature: OnceCell::new(), + class_factor: OnceCell::new(), + spectr: OnceCell::new(), + luminosity: OnceCell::new(), + radius: OnceCell::new(), + light_balance_radius: OnceCell::new(), + habitable_radius: OnceCell::new(), + mass: OnceCell::new(), + orbit_scaler: OnceCell::new(), + dyson_radius: OnceCell::new(), + hive_rand: RefCell::new(hive_rand), + max_hive_count: OnceCell::new(), + initial_hive_count: OnceCell::new(), } } pub fn is_birth(&self) -> bool { - return self.index == 0; + self.index == 0 } - lazy_getter!(self, get_unmodified_mass, f32, { - let (r1_1, r2_1, y, mass_factor, spectr_factor) = self.mass_params; - if self.is_birth() { - let p1 = rand_normal(0.0, 0.08, r1_1, r2_1).clamp(-0.2, 0.2); - 2_f32.powf(p1) - } else { - match self.star_type { - StarType::WhiteDwarf => (1.0 + r2_1 * 5.0) as f32, - StarType::NeutronStar => (7.0 + r1_1 * 11.0) as f32, - StarType::BlackHole => (18.0 + r1_1 * r2_1 * 30.0) as f32, - _ => { - let num8 = if spectr_factor != 0.0 { - spectr_factor - } else { - let num7 = -0.98 + (0.88 + 0.98) * self.level.clamp(0.0, 1.0); - let average_value = if self.star_type == StarType::GiantStar { - if y > -0.08 { - -1.5 - } else { - 1.6 - } - } else if num7 >= 0.0 { - num7 + 0.65 - } else { - num7 - 0.65 - }; - let standard_deviation = if self.star_type == StarType::GiantStar { - 0.3_f32 + pub fn get_unmodified_mass(&self) -> f32 { + *self.unmodified_mass.get_or_init(|| { + let (mass_random1, mass_random2, radius_exponent, mass_factor, spectr_factor) = + self.mass_params; + if self.is_birth() { + let birth_mass_exponent = + rand_normal(0.0, 0.08, mass_random1, mass_random2).clamp(-0.2, 0.2); + 2_f32.powf(birth_mass_exponent) + } else { + match self.star_type { + StarType::WhiteDwarf => (1.0 + mass_random2 * 5.0) as f32, + StarType::NeutronStar => (7.0 + mass_random1 * 11.0) as f32, + StarType::BlackHole => (18.0 + mass_random1 * mass_random2 * 30.0) as f32, + _ => { + let mass_exponent = if spectr_factor != 0.0 { + spectr_factor } else { - 0.33_f32 + let base_spectr_exponent = lerp(-0.98, 0.88, self.level); + let average_value = if self.star_type == StarType::GiantStar { + if radius_exponent > -0.08 { + -1.5 + } else { + 1.6 + } + } else if base_spectr_exponent >= 0.0 { + base_spectr_exponent + 0.65 + } else { + base_spectr_exponent - 0.65 + }; + let standard_deviation = if self.star_type == StarType::GiantStar { + 0.3_f32 + } else { + 0.33_f32 + }; + let random_mass_exponent = rand_normal( + average_value, + standard_deviation, + mass_random1, + mass_random2, + ); + (if random_mass_exponent <= 0.0 { + random_mass_exponent + } else { + random_mass_exponent * 2.0 + }) + .clamp(-2.4, 4.65) }; - let num = rand_normal(average_value, standard_deviation, r1_1, r2_1); - (if num <= 0.0 { num } else { num * 2.0 }).clamp(-2.4, 4.65) - }; - 2_f32.powf((num8 as f64 + (mass_factor - 0.5) * 0.2 + 1.0) as f32) + 2_f32.powf((mass_exponent as f64 + (mass_factor - 0.5) * 0.2 + 1.0) as f32) + } } } - } - }); + }) + } - lazy_getter!(self, get_resource_coef, f32, { - if self.is_birth() { - 0.6 - } else { - let mut num1 = (self.position.magnitude() as f32) / 32.0; - if (num1 as f64) > 1.0 { - num1 = ((((num1.ln() + 1.0).ln() + 1.0).ln() + 1.0).ln() + 1.0).ln() + 1.0 + pub fn get_resource_coef(&self) -> f32 { + *self.resource_coef.get_or_init(|| { + if self.is_birth() { + 0.6 + } else { + let mut distance_factor = (self.position.magnitude() as f32) / 32.0; + if (distance_factor as f64) > 1.0 { + distance_factor = + ((((distance_factor.ln() + 1.0).ln() + 1.0).ln() + 1.0).ln() + 1.0).ln() + + 1.0 + } + 7.0_f32.powf(distance_factor) * 0.6 } - 7.0_f32.powf(num1) * 0.6 - } - }); + }) + } - lazy_getter!(self, get_lifetime, f32, { + fn get_lifetime(&self) -> f32 { let unmodified_mass = self.get_unmodified_mass(); - let d = if unmodified_mass < 2.0 { + let lifetime_exponent_base = if unmodified_mass < 2.0 { 2.0 + 0.4 * (1.0 - (unmodified_mass as f64)) } else { 5.0 @@ -194,7 +218,10 @@ impl<'a> Star<'a> { _ => 0.0, }; let lifetime = (10000.0 - * 0.1_f64.powf(((self.get_unmodified_mass() as f64) * mass_multiplier).log(d) + 1.0) + * 0.1_f64.powf( + ((self.get_unmodified_mass() as f64) * mass_multiplier).log(lifetime_exponent_base) + + 1.0, + ) * (self.lifetime_factor * 0.2 + 0.9)) + lifetime_delta; @@ -202,190 +229,369 @@ impl<'a> Star<'a> { lifetime as f32 } else { let age = self.get_age(); - let mut num9 = (lifetime as f32) * age; - if num9 > 5000.0 { - num9 = (((num9 / 5000.0).ln() as f64 + 1.0) * 5000.0) as f32; + let mut adjusted_lifetime = (lifetime as f32) * age; + if adjusted_lifetime > 5000.0 { + adjusted_lifetime = + (((adjusted_lifetime / 5000.0).ln() as f64 + 1.0) * 5000.0) as f32; } - if num9 > 8000.0 { - num9 = - (((((num9 / 8000.0).ln() + 1.0).ln() + 1.0).ln() as f64 + 1.0) * 8000.0) as f32; + if adjusted_lifetime > 8000.0 { + adjusted_lifetime = + (((((adjusted_lifetime / 8000.0).ln() + 1.0).ln() + 1.0).ln() as f64 + 1.0) + * 8000.0) as f32; } - num9 / age + adjusted_lifetime / age } - }); + } - lazy_getter!(self, get_age, f32, { - (if self.is_birth() { - self.age_factor * 0.4 + 0.3 - } else { - match self.star_type { - StarType::GiantStar => self.age_factor * 0.04 + 0.96, - StarType::WhiteDwarf | StarType::NeutronStar | StarType::BlackHole => { - self.age_factor * 0.4 + 1.0 - } - _ => { - let unmodified_mass = self.get_unmodified_mass(); - if unmodified_mass >= 0.8 { - self.age_factor * 0.7 + 0.2 - } else if unmodified_mass >= 0.5 { - self.age_factor * 0.4 + 0.1 - } else { - self.age_factor * 0.12 + 0.02 + pub fn get_age(&self) -> f32 { + *self.age.get_or_init(|| { + (if self.is_birth() { + self.age_factor * 0.4 + 0.3 + } else { + match self.star_type { + StarType::GiantStar => self.age_factor * 0.04 + 0.96, + StarType::WhiteDwarf | StarType::NeutronStar | StarType::BlackHole => { + self.age_factor * 0.4 + 1.0 + } + _ => { + let unmodified_mass = self.get_unmodified_mass(); + if unmodified_mass >= 0.8 { + self.age_factor * 0.7 + 0.2 + } else if unmodified_mass >= 0.5 { + self.age_factor * 0.4 + 0.1 + } else { + self.age_factor * 0.12 + 0.02 + } } } - } - }) as f32 - }); + }) as f32 + }) + } - lazy_getter!(self, get_temperature_factor, f32, { - ((1.0 - (self.get_age().clamp(0.0, 1.0).powf(20.0) as f64) * 0.5) as f32) - * self.get_unmodified_mass() - }); + pub fn get_temperature_factor(&self) -> f32 { + *self.temperature_factor.get_or_init(|| { + ((1.0 - (self.get_age().clamp(0.0, 1.0).powf(20.0) as f64) * 0.5) as f32) + * self.get_unmodified_mass() + }) + } - lazy_getter!(self, get_unmodified_temperature, f32, { - let f1 = self.get_temperature_factor() as f64; - (f1.powf(0.56 + 0.14 / (f1 + 4.0).log(5.0)) * 4450.0 + 1300.0) as f32 - }); + pub fn get_unmodified_temperature(&self) -> f32 { + *self.unmodified_temperature.get_or_init(|| { + let temperature_factor_f64 = self.get_temperature_factor() as f64; + (temperature_factor_f64.powf(0.56 + 0.14 / (temperature_factor_f64 + 4.0).log(5.0)) + * 4450.0 + + 1300.0) as f32 + }) + } - lazy_getter!(self, get_temperature, f32, { - match self.star_type { + pub fn get_temperature(&self) -> f32 { + *self.temperature.get_or_init(|| match self.star_type { StarType::BlackHole => 0.0, StarType::NeutronStar => self.age_num3 * 1e+7, StarType::WhiteDwarf => self.age_num2 * 150000.0, _ => { let temperature = self.get_unmodified_temperature(); if self.star_type == StarType::GiantStar { - let num5 = 1.0 - self.get_age().powf(30.0) * 0.5; - temperature * num5 + let age_mass_factor = 1.0 - self.get_age().powf(30.0) * 0.5; + temperature * age_mass_factor } else { temperature } } - } - }); + }) + } - lazy_getter!(self, get_class_factor, f64, { - let temperature = self.get_unmodified_temperature() as f64; - let mut spectr_factor = ((temperature - 1300.0) / 4500.0).log(2.6) - 0.5; - if spectr_factor < 0.0 { - spectr_factor *= 4.0; - } - spectr_factor.clamp(-4.0, 2.0) - }); + pub fn get_class_factor(&self) -> f64 { + *self.class_factor.get_or_init(|| { + let temperature = self.get_unmodified_temperature() as f64; + let mut spectr_factor = ((temperature - 1300.0) / 4500.0).log(2.6) - 0.5; + if spectr_factor < 0.0 { + spectr_factor *= 4.0; + } + spectr_factor.clamp(-4.0, 2.0) + }) + } - lazy_getter_ref!(self, get_spectr, SpectrType, { - if matches!( - self.star_type, - StarType::WhiteDwarf | StarType::NeutronStar | StarType::BlackHole - ) { - SpectrType::X - } else { - unsafe { ::std::mem::transmute(self.get_class_factor().round() as i32) } - } - }); + pub fn get_spectr(&self) -> SpectrType { + *self.spectr.get_or_init(|| { + if matches!( + self.star_type, + StarType::WhiteDwarf | StarType::NeutronStar | StarType::BlackHole + ) { + SpectrType::X + } else { + unsafe { + ::std::mem::transmute::( + self.get_class_factor().round_ties_even() as i32, + ) + } + } + }) + } - // lazy_getter!(self, get_color, f32, { - // match self.star_type { - // StarType::BlackHole | StarType::NeutronStar => 1.0, - // StarType::WhiteDwarf => 0.7, - // _ => (((self.get_class_factor() + 3.5) * 0.2) as f32).clamp(0.0, 1.0), - // } - // }); + fn get_color(&self) -> f32 { + match self.star_type { + StarType::BlackHole | StarType::NeutronStar => 1.0, + StarType::WhiteDwarf => 0.7, + _ => (((self.get_class_factor() + 3.5) * 0.2) as f32).clamp(0.0, 1.0), + } + } - lazy_getter!(self, get_luminosity, f32, { - let base = self.get_temperature_factor().powf(0.7); - let factor = match self.star_type { - StarType::BlackHole => 1.0 / 1000.0 * self.age_num1, - StarType::NeutronStar => 0.1 * self.age_num1, - StarType::WhiteDwarf => 0.04 * self.age_num1, - StarType::GiantStar => 1.6, - _ => 1.0, - }; - let real = base * factor; - // displayed - (real.powf(0.33) * 1000.0).round() / 1000.0 - }); + pub fn get_luminosity(&self) -> f32 { + *self.luminosity.get_or_init(|| { + let base = self.get_temperature_factor().powf(0.7); + let factor = match self.star_type { + StarType::BlackHole => 1.0 / 1000.0 * self.age_num1, + StarType::NeutronStar => 0.1 * self.age_num1, + StarType::WhiteDwarf => 0.04 * self.age_num1, + StarType::GiantStar => 1.6, + _ => 1.0, + }; + let real = base * factor; + // displayed + (real.powf(0.33) * 1000.0).round_ties_even() / 1000.0 + }) + } - lazy_getter!(self, get_radius, f32, { - if self.star_type == StarType::GiantStar { - let mut num4 = (5.0_f64.powf(((self.get_unmodified_mass() as f64).log10() - 0.7).abs()) - * 5.0) as f32; - if num4 > 10.0 { - num4 = ((num4 * 0.1).ln() + 1.0) * 10.0; + pub fn get_radius(&self) -> f32 { + *self.radius.get_or_init(|| { + if self.star_type == StarType::GiantStar { + let mut giant_radius = (5.0_f64 + .powf(((self.get_unmodified_mass() as f64).log10() - 0.7).abs()) + * 5.0) as f32; + if giant_radius > 10.0 { + giant_radius = ((giant_radius * 0.1).ln() + 1.0) * 10.0; + } + giant_radius * self.age_num2 + } else { + (((self.get_unmodified_mass() as f64).powf(0.4) * self.radius_factor) as f32) + * (match self.star_type { + StarType::NeutronStar => 0.15, + StarType::WhiteDwarf => 0.2, + _ => 1.0, + }) } - num4 * self.age_num2 - } else { - (((self.get_unmodified_mass() as f64).powf(0.4) * self.radius_factor) as f32) - * (match self.star_type { - StarType::NeutronStar => 0.15, - StarType::WhiteDwarf => 0.2, + }) + } + + pub fn get_light_balance_radius(&self) -> f32 { + *self.light_balance_radius.get_or_init(|| { + if self.star_type == StarType::GiantStar { + 3.0 * self.get_habitable_radius() + } else { + let r = 1.7_f32.powf((self.get_class_factor() as f32) + 2.0); + let factor = match self.star_type { + StarType::BlackHole => 0.4 * self.age_num1, + StarType::NeutronStar => 3.0 * self.age_num1, + StarType::WhiteDwarf => 0.2 * self.age_num1, _ => 1.0, - }) - } - }); + }; + r * factor + } + }) + } - lazy_getter!(self, get_light_balance_radius, f32, { - if self.star_type == StarType::GiantStar { - 3.0 * self.get_habitable_radius() - } else { - let r = 1.7_f32.powf((self.get_class_factor() as f32) + 2.0); + pub fn get_habitable_radius(&self) -> f32 { + *self.habitable_radius.get_or_init(|| { let factor = match self.star_type { - StarType::BlackHole => 0.4 * self.age_num1, - StarType::NeutronStar => 3.0 * self.age_num1, - StarType::WhiteDwarf => 0.2 * self.age_num1, + StarType::BlackHole | StarType::NeutronStar => 0.0, + StarType::WhiteDwarf => 0.15 * self.age_num2, + StarType::GiantStar => 9.0, _ => 1.0, }; - r * factor - } - }); - - lazy_getter!(self, get_habitable_radius, f32, { - let factor = match self.star_type { - StarType::BlackHole | StarType::NeutronStar => 0.0, - StarType::WhiteDwarf => 0.15 * self.age_num2, - StarType::GiantStar => 9.0, - _ => 1.0, - }; - if factor == 0.0 { - 0.0 - } else { - (1.7_f32.powf((self.get_class_factor() as f32) + 2.0) - + if self.is_birth() { 0.2 } else { 0.25 }) - * factor - } - }); + if factor == 0.0 { + 0.0 + } else { + (1.7_f32.powf((self.get_class_factor() as f32) + 2.0) + + if self.is_birth() { 0.2 } else { 0.25 }) + * factor + } + }) + } - lazy_getter!(self, get_mass, f32, { - match self.star_type { + pub fn get_mass(&self) -> f32 { + *self.mass.get_or_init(|| match self.star_type { StarType::BlackHole => self.get_unmodified_mass() * 2.5 * self.age_num2, StarType::NeutronStar | StarType::WhiteDwarf => { self.get_unmodified_mass() * 0.2 * self.age_num1 } StarType::GiantStar => { - let num5 = 1.0 - self.get_age().powf(30.0) * 0.5; - self.get_unmodified_mass() * num5 + let age_mass_factor = 1.0 - self.get_age().powf(30.0) * 0.5; + self.get_unmodified_mass() * age_mass_factor } _ => self.get_unmodified_mass(), - } - }); + }) + } - lazy_getter!(self, get_orbit_scaler, f32, { - let mut orbit_scaler = 1.35_f32.powf((self.get_class_factor() as f32) + 2.0); - if orbit_scaler < 1.0 { - orbit_scaler += (1.0 - orbit_scaler) * 0.6; + pub fn get_orbit_scaler(&self) -> f32 { + *self.orbit_scaler.get_or_init(|| { + let mut orbit_scaler = 1.35_f32.powf((self.get_class_factor() as f32) + 2.0); + if orbit_scaler < 1.0 { + orbit_scaler += (1.0 - orbit_scaler) * 0.6; + } + orbit_scaler + * (match self.star_type { + StarType::NeutronStar => 1.5 * self.age_num1, + StarType::GiantStar => 3.3, + _ => 1.0, + }) + }) + } + + pub fn get_dyson_radius(&self) -> i32 { + *self.dyson_radius.get_or_init(|| { + (((self.get_orbit_scaler() * 0.28).max(self.get_radius() * 0.045) * 800.0) + .round_ties_even() as i32) + * 100 + }) + } + + fn get_safety_factor(&self) -> f32 { + if self.is_birth() { + return (0.847 + self.safety_factor_modifier * 0.026) as f32; + } + let mut adjusted_distance = + (((self.position.magnitude() - 2.0) / 20.0) as f32).clamp(0.0, 2.5); + if adjusted_distance > 1.0 { + adjusted_distance = (adjusted_distance.ln() + 1.0).ln() + 1.0 } - orbit_scaler - * (match self.star_type { - StarType::NeutronStar => 1.5 * self.age_num1, - StarType::GiantStar => 3.3, + let normalized_distance = adjusted_distance / 1.4; + let star_type_color_factor: f32 = match self.star_type { + StarType::BlackHole => 5.0, + StarType::NeutronStar => 1.7, + StarType::WhiteDwarf => 1.2, + _ => { + let base_color_factor = self.get_color().powf(1.3); + if self.star_type == StarType::GiantStar { + base_color_factor.max(0.6) + } else if self.get_spectr() == SpectrType::O { + base_color_factor + 0.05 + } else { + base_color_factor + } + } + }; + ((1.0 + - ((star_type_color_factor * 0.9 + 0.07).powf(0.73) as f64) + * (normalized_distance.powf(0.27) as f64) + + self.safety_factor_modifier * 0.08 + - 0.04) as f32) + .clamp(0.0, 1.0) + } + + pub fn get_max_hive_count(&self) -> i32 { + *self.max_hive_count.get_or_init(|| { + let star_type_hive_multiplier = match self.star_type { + StarType::BlackHole | StarType::NeutronStar => 2.0, _ => 1.0, - }) - }); + }; + ((self.game_desc.hive_max_density * star_type_hive_multiplier * 1000.0 + + (self.max_hive_count_modifier as f64) + + 0.5) as i32) + / 1000 + }) + } - lazy_getter!(self, get_dyson_radius, i32, { - (((self.get_orbit_scaler() * 0.28).max(self.get_radius() * 0.045) * 800.0).round() as i32) - * 100 - }); + pub fn get_initial_hive_count(&self) -> i32 { + *self.initial_hive_count.get_or_init(|| { + let initial_colonize = self.game_desc.hive_initial_colonize; + if initial_colonize < 0.015 { + return 0; + } + let max_hive_count = self.get_max_hive_count(); + if self.is_birth() { + let birth_min_hives = if initial_colonize * (max_hive_count as f64) < 0.7 { + 0 + } else { + 1 + }; + let mut birth_avg_hives = 0.6 * (initial_colonize as f32) * (max_hive_count as f32); + let mut birth_std_dev = 0.5; + if birth_avg_hives < 1.0 { + birth_std_dev = ((birth_avg_hives.sqrt() as f64) * 0.29 + 0.21) as f32; + } else if birth_avg_hives > max_hive_count as f32 { + birth_avg_hives = max_hive_count as f32; + } + let mut rand3 = self.hive_rand.borrow_mut(); + let mut initial_hive_count: i32 = -1; + for _ in 0..17 { + let r1_2 = rand3.next_f64(); + let r2_2 = rand3.next_f64(); + initial_hive_count = + (rand_normal(birth_avg_hives, birth_std_dev, r1_2, r2_2) + 0.5) as i32; + if initial_hive_count >= 0 && initial_hive_count <= max_hive_count { + break; + } + } + return initial_hive_count.clamp(birth_min_hives, max_hive_count); + } + let hive_probability_base = ((1.0 + - (((self.get_safety_factor() * 1.05 - 0.15) as f32) + .clamp(0.0, 1.0) + .powf(0.82) as f64) + - (((max_hive_count - 1) as f64) * 0.05)) + as f32) + .clamp(0.0, 1.0) + * ((1.1 - (max_hive_count as f64) * 0.1) as f32); + let raw_probability = if initial_colonize > 1.0 { + lerp( + hive_probability_base, + (1.0 + (initial_colonize - 1.0) * 0.2) as f32, + ((initial_colonize - 1.0) * 0.5) as f32, + ) + } else { + hive_probability_base * (initial_colonize as f32) + }; + let star_type_adjusted_probability = match self.star_type { + StarType::GiantStar => raw_probability * 1.2, + StarType::WhiteDwarf => raw_probability * 1.4, + StarType::NeutronStar => raw_probability * 1.6, + StarType::BlackHole => raw_probability * 1.8, + _ => { + if self.get_spectr() == SpectrType::O { + raw_probability * 1.1 + } else { + raw_probability + } + } + }; + let expected_hive_count: f32 = (star_type_adjusted_probability + * (max_hive_count as f32)) + .min((max_hive_count as f32) + 0.75); + let hive_std_dev: f32 = if expected_hive_count <= 0.01 { + 0.0 + } else if expected_hive_count < 1.0 { + expected_hive_count.sqrt() * 2.9 + 2.1 + } else if expected_hive_count > 1.0 { + 0.3 + 0.2 * expected_hive_count + } else { + 0.5 + }; + let mut rand3 = self.hive_rand.borrow_mut(); + let mut initial_hive_count: i32 = -1; + for _ in 0..65 { + let r1_2 = rand3.next_f64(); + let r2_2 = rand3.next_f64(); + initial_hive_count = + (rand_normal(expected_hive_count, hive_std_dev, r1_2, r2_2) + 0.5) as i32; + if initial_hive_count >= 0 && initial_hive_count <= max_hive_count { + break; + } + } + initial_hive_count = initial_hive_count.clamp(0, max_hive_count); + + if self.star_type == StarType::BlackHole { + (((self.game_desc.hive_max_density * 1000.0 + + (self.max_hive_count_modifier as f64) + + 0.5) as i32) + / 1000) + .max(initial_hive_count) + .max(1) + } else { + initial_hive_count + } + }) + } } impl Serialize for Star<'_> { @@ -393,7 +599,7 @@ impl Serialize for Star<'_> { where S: Serializer, { - let mut state = serializer.serialize_struct("Star", 11)?; + let mut state = serializer.serialize_struct("Star", 14)?; state.serialize_field("index", &self.index)?; state.serialize_field("position", &self.position)?; state.serialize_field("mass", &self.get_mass())?; @@ -405,6 +611,9 @@ impl Serialize for Star<'_> { state.serialize_field("luminosity", &self.get_luminosity())?; state.serialize_field("radius", &self.get_radius())?; state.serialize_field("dysonRadius", &self.get_dyson_radius())?; + state.serialize_field("initialHiveCount", &self.get_initial_hive_count())?; + state.serialize_field("maxHiveCount", &self.get_max_hive_count())?; + state.serialize_field("color", &self.get_color())?; state.end() } } @@ -413,3 +622,8 @@ fn rand_normal(average_value: f32, standard_deviation: f32, r1: f64, r2: f64) -> average_value + standard_deviation * ((-2.0 * (1.0 - r1).ln()).sqrt() * (2.0 * PI * r2).sin()) as f32 } + +#[inline] +fn lerp(a: f32, b: f32, t: f32) -> f32 { + a + (b - a) * t.clamp(0.0, 1.0) +} diff --git a/inserter/src/algorithm/data/star_planets.rs b/inserter/src/algorithm/data/star_planets.rs index 3d71a4d..b443823 100644 --- a/inserter/src/algorithm/data/star_planets.rs +++ b/inserter/src/algorithm/data/star_planets.rs @@ -1,34 +1,61 @@ -use std::cell::UnsafeCell; -use std::collections::HashMap; +use std::cell::{Cell, UnsafeCell}; use std::rc::Rc; +use crate::algorithm::data::game_desc::GameDesc; + use super::enums::{SpectrType, StarType, VeinType}; use super::planet::Planet; use super::random::DspRandom; use super::star::Star; -use serde::ser::SerializeStruct; -use serde::{Serialize, Serializer}; +use serde::Serialize; + +pub fn serialize_planets( + planets: &UnsafeCell>>, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + unsafe { &*planets.get() }.serialize(serializer) +} -#[allow(dead_code)] -#[derive(Debug)] +const MAX_VEIN_COUNT: usize = VeinType::Max as usize; + +#[derive(Debug, Serialize)] pub struct StarWithPlanets<'a> { + pub name: String, + #[serde(flatten)] pub star: Rc>, + #[serde(serialize_with = "serialize_planets")] planets: UnsafeCell>>, + + #[serde(skip)] safe: UnsafeCell, - avg_veins: UnsafeCell>, - avg_vein_amounts: HashMap, // for serde to serialize - pub name: String, + #[serde(skip)] + avg_veins: UnsafeCell<[f32; MAX_VEIN_COUNT]>, + #[serde(skip)] + actual_veins: UnsafeCell<[f32; MAX_VEIN_COUNT]>, + #[serde(skip)] + game_desc: &'a GameDesc, + #[serde(skip)] + habitable_count: &'a Cell, } impl<'a> StarWithPlanets<'a> { - pub fn new(star: Rc>) -> Self { + pub fn new( + star: Rc>, + game_desc: &'a GameDesc, + habitable_count: &'a Cell, + ) -> Self { Self { star, - planets: UnsafeCell::new(vec![]), + planets: UnsafeCell::new(Vec::with_capacity(6)), safe: UnsafeCell::new(false), - avg_veins: UnsafeCell::new(HashMap::new()), - avg_vein_amounts: HashMap::new(), + avg_veins: UnsafeCell::new([f32::NAN; MAX_VEIN_COUNT]), + actual_veins: UnsafeCell::new([f32::NAN; MAX_VEIN_COUNT]), name: Default::default(), + game_desc, + habitable_count, } } @@ -55,68 +82,79 @@ impl<'a> StarWithPlanets<'a> { && self.star.star_type != StarType::BlackHole && self.star.star_type != StarType::NeutronStar { + if !self.is_safe() { + self.load_planets(); + } return 0.0; } - let map = unsafe { &mut *self.avg_veins.get() }; - if let Some(val) = map.get(vein_type) { - return *val; + let index = *vein_type as usize; + let cached_value = unsafe { + let arr = &mut *self.avg_veins.get(); + arr.get_unchecked_mut(index) + }; + if !cached_value.is_nan() { + return *cached_value; } let mut count = 0_f32; - let is_rare = vein_type.is_rare(); - let is_safe = self.is_safe(); for planet in self.get_planets() { - if planet.is_gas_giant() { - if !is_safe { - planet.get_theme(); - } + if !planet.can_have_vein(vein_type) { continue; } - if is_rare { - let theme = planet.get_theme(); - // skip vein generation if possible - if !theme.rare_veins.contains(vein_type) { - continue; + if planet.is_acutal_veins_generated() { + for vein in planet.get_actual_veins() { + if &vein.vein_type == vein_type { + count += vein.amount as f32; + } } - } - for vein in planet.get_veins() { - if &vein.vein_type == vein_type { - let avg_patches = ((vein.min_patch + vein.max_patch) as f32) - * ((vein.min_group + vein.max_group) as f32) - * ((vein.min_amount + vein.max_amount) as f32) - / 8.0; - count += avg_patches; + } else { + for vein in planet.get_estimated_veins() { + if &vein.vein_type == vein_type { + let avg_patches = ((vein.min_patch + vein.max_patch) as f32) + * ((vein.min_group + vein.max_group) as f32) + * ((vein.min_amount + vein.max_amount) as f32) + / 8.0; + count += avg_patches; + } } } } - map.insert(vein_type.clone(), count); + *cached_value = count; self.mark_safe(); count } - fn all_avg_veins(&self) -> HashMap { - let mut results = HashMap::::new(); - let vts: Vec = vec![ - VeinType::Iron, - VeinType::Copper, - VeinType::Silicium, - VeinType::Titanium, - VeinType::Stone, - VeinType::Coal, - VeinType::Oil, - VeinType::Fireice, - VeinType::Diamond, - VeinType::Fractal, - VeinType::Crysrub, - VeinType::Grat, - VeinType::Bamboo, - VeinType::Mag, - VeinType::Max, - ]; - for vein_type in vts { - results.insert(vein_type.clone(), self.get_avg_vein(&vein_type)); + pub fn get_actual_vein(&self, vein_type: &VeinType) -> f32 { + if vein_type == &VeinType::Mag + && self.star.star_type != StarType::BlackHole + && self.star.star_type != StarType::NeutronStar + { + if !self.is_safe() { + self.load_planets(); + } + return 0.0; } - - results + let index = *vein_type as usize; + let cached_value = unsafe { + let arr = &mut *self.actual_veins.get(); + arr.get_unchecked_mut(index) + }; + if !cached_value.is_nan() { + return *cached_value; + } + let mut count = 0; + for planet in self.get_planets() { + if !planet.can_have_vein(vein_type) { + continue; + } + for vein in planet.get_actual_veins() { + if &vein.vein_type == vein_type { + count += vein.amount; + } + } + } + *cached_value = count as f32; + self.mark_safe(); + count as f32 } pub fn get_planets(&self) -> &Vec> { @@ -125,9 +163,9 @@ impl<'a> StarWithPlanets<'a> { return planets; } let mut rand2 = DspRandom::new(self.star.planets_seed); - let num1 = rand2.next_f64(); - let num2 = rand2.next_f64(); - let num3 = if rand2.next_f64() > 0.5 { 1 } else { 0 }; + let planet_count_rand = rand2.next_f64(); + let planet_config_rand = rand2.next_f64(); + let orbit_offset = if rand2.next_f64() > 0.5 { 1 } else { 0 }; rand2.next_f64(); rand2.next_f64(); rand2.next_f64(); @@ -137,8 +175,10 @@ impl<'a> StarWithPlanets<'a> { let info_seed = rand2.next_seed(); let gen_seed = rand2.next_seed(); Planet::new( + self.game_desc, self.star.clone(), index, + self.habitable_count, orbit_index, gas_giant, info_seed, @@ -151,9 +191,9 @@ impl<'a> StarWithPlanets<'a> { if star_type == &StarType::BlackHole || star_type == &StarType::NeutronStar { planets.push(make_planet(0, 3, false)); } else if star_type == &StarType::WhiteDwarf { - if num1 < 0.7 { + if planet_count_rand < 0.7 { planets.push(make_planet(0, 3, false)); - } else if num2 < 0.3 { + } else if planet_config_rand < 0.3 { planets.push(make_planet(0, 3, false)); planets.push(make_planet(1, 4, false)); } else { @@ -164,12 +204,12 @@ impl<'a> StarWithPlanets<'a> { planet2.orbit_around.replace(Some(planet1)); } } else if star_type == &StarType::GiantStar { - if num1 < 0.3 { - planets.push(make_planet(0, 2 + num3, false)); - } else if num1 < 0.8 { - if num2 < 0.25 { - planets.push(make_planet(0, 2 + num3, false)); - planets.push(make_planet(1, 3 + num3, false)); + if planet_count_rand < 0.3 { + planets.push(make_planet(0, 2 + orbit_offset, false)); + } else if planet_count_rand < 0.8 { + if planet_config_rand < 0.25 { + planets.push(make_planet(0, 2 + orbit_offset, false)); + planets.push(make_planet(1, 3 + orbit_offset, false)); } else { planets.push(make_planet(0, 3, true)); planets.push(make_planet(1, 1, false)); @@ -178,19 +218,19 @@ impl<'a> StarWithPlanets<'a> { planet2.orbit_around.replace(Some(planet1)); } } else { - if num2 < 0.15 { - planets.push(make_planet(0, 2 + num3, false)); - planets.push(make_planet(1, 3 + num3, false)); - planets.push(make_planet(2, 4 + num3, false)); - } else if num2 < 0.75 { - planets.push(make_planet(0, 2 + num3, false)); + if planet_config_rand < 0.15 { + planets.push(make_planet(0, 2 + orbit_offset, false)); + planets.push(make_planet(1, 3 + orbit_offset, false)); + planets.push(make_planet(2, 4 + orbit_offset, false)); + } else if planet_config_rand < 0.75 { + planets.push(make_planet(0, 2 + orbit_offset, false)); planets.push(make_planet(1, 4, true)); planets.push(make_planet(2, 1, false)); let planet2 = &planets[1]; let planet3 = &planets[2]; planet3.orbit_around.replace(Some(planet2)); } else { - planets.push(make_planet(0, 3 + num3, true)); + planets.push(make_planet(0, 3 + orbit_offset, true)); planets.push(make_planet(1, 1, false)); planets.push(make_planet(2, 2, false)); let planet1 = &planets[0]; @@ -206,11 +246,11 @@ impl<'a> StarWithPlanets<'a> { } else { match self.star.get_spectr() { SpectrType::M => { - let planet_count = if num1 >= 0.8 { + let planet_count = if planet_count_rand >= 0.8 { 4 - } else if num1 >= 0.3 { + } else if planet_count_rand >= 0.3 { 3 - } else if num1 >= 0.1 { + } else if planet_count_rand >= 0.1 { 2 } else { 1 @@ -225,13 +265,13 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::K => { - let planet_count = if num1 >= 0.95 { + let planet_count = if planet_count_rand >= 0.95 { 5 - } else if num1 >= 0.7 { + } else if planet_count_rand >= 0.7 { 4 - } else if num1 >= 0.2 { + } else if planet_count_rand >= 0.2 { 3 - } else if num1 >= 0.1 { + } else if planet_count_rand >= 0.1 { 2 } else { 1 @@ -246,9 +286,9 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::G => { - let planet_count = if num1 >= 0.9 { + let planet_count = if planet_count_rand >= 0.9 { 5 - } else if num1 >= 0.4 { + } else if planet_count_rand >= 0.4 { 4 } else { 3 @@ -263,9 +303,9 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::F => { - let planet_count = if num1 >= 0.8 { + let planet_count = if planet_count_rand >= 0.8 { 5 - } else if num1 >= 0.35 { + } else if planet_count_rand >= 0.35 { 4 } else { 3 @@ -280,9 +320,9 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::A => { - let planet_count = if num1 >= 0.75 { + let planet_count = if planet_count_rand >= 0.75 { 5 - } else if num1 >= 0.3 { + } else if planet_count_rand >= 0.3 { 4 } else { 3 @@ -297,9 +337,9 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::B => { - let planet_count = if num1 >= 0.75 { + let planet_count = if planet_count_rand >= 0.75 { 6 - } else if num1 >= 0.3 { + } else if planet_count_rand >= 0.3 { 5 } else { 4 @@ -314,7 +354,7 @@ impl<'a> StarWithPlanets<'a> { ) } SpectrType::O => { - let planet_count = if num1 >= 0.5 { 6 } else { 5 }; + let planet_count = if planet_count_rand >= 0.5 { 6 } else { 5 }; (planet_count, P_GASES[9]) } _ => (1, P_GASES[0]), @@ -322,31 +362,37 @@ impl<'a> StarWithPlanets<'a> { }; let mut satellite_count = 0; let mut orbit_around: Option = None; - let mut num10: usize = 1; - let mut orbits: Vec<(usize, usize)> = vec![]; + let mut current_orbit_index: usize = 1; + let mut orbits: Vec<(usize, usize)> = Vec::with_capacity(4); for index in 0..planet_count as usize { let info_seed = rand2.next_seed(); let gen_seed = rand2.next_seed(); - let num11 = rand2.next_f64(); - let num12 = rand2.next_f64(); + let gas_giant_chance_rand = rand2.next_f64(); + let stop_satellite_chance_rand = rand2.next_f64(); let mut gas_giant = false; if orbit_around.is_none() { - if index < planet_count - 1 && num11 < p_gas[index] { + if index < planet_count - 1 && gas_giant_chance_rand < p_gas[index] { gas_giant = true; - if num10 < 3 { - num10 = 3; + if current_orbit_index < 3 { + current_orbit_index = 3; } } let mut broke_from_loop = false; - while !self.star.is_birth() || num10 != 3 { - let num13 = planet_count - index; - let num14 = 9 - num10; - if num14 > num13 { - let a = (num13 as f32) / (num14 as f32); - let a2 = if num10 <= 3 { 0.15_f32 } else { 0.45_f32 }; - let num15 = a + (1.0 - a) * a2 + 0.01; - if rand2.next_f64() < num15 as f64 { + while !self.star.is_birth() || current_orbit_index != 3 { + let remaining_planets = planet_count - index; + let remaining_orbit_slots = 9 - current_orbit_index; + if remaining_orbit_slots > remaining_planets { + let remaining_ratio = + (remaining_planets as f32) / (remaining_orbit_slots as f32); + let skip_chance_base = if current_orbit_index <= 3 { + 0.15_f32 + } else { + 0.45_f32 + }; + let orbit_skip_threshold = + remaining_ratio + (1.0 - remaining_ratio) * skip_chance_base + 0.01; + if rand2.next_f64() < orbit_skip_threshold as f64 { broke_from_loop = true; break; } @@ -354,7 +400,7 @@ impl<'a> StarWithPlanets<'a> { broke_from_loop = true; break; } - num10 += 1; + current_orbit_index += 1; } if !broke_from_loop { gas_giant = true; @@ -363,10 +409,12 @@ impl<'a> StarWithPlanets<'a> { satellite_count += 1; } let planet = Planet::new( + self.game_desc, self.star.clone(), index, + self.habitable_count, if orbit_around.is_none() { - num10 + current_orbit_index } else { satellite_count }, @@ -377,12 +425,12 @@ impl<'a> StarWithPlanets<'a> { if let Some(around) = orbit_around { orbits.push((index, around)) } - num10 += 1; + current_orbit_index += 1; if gas_giant { orbit_around = Some(index); satellite_count = 0; } - if satellite_count >= 1 && num12 < 0.8 { + if satellite_count >= 1 && stop_satellite_chance_rand < 0.8 { orbit_around = None; satellite_count = 0; } @@ -398,20 +446,7 @@ impl<'a> StarWithPlanets<'a> { planets } } -impl<'a> Serialize for StarWithPlanets<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("StarWithPlanets", 7)?; - state.serialize_field("star", &self.star)?; - state.serialize_field("planets", unsafe { &*self.planets.get() })?; - state.serialize_field("avg_veins", &self.all_avg_veins())?; - state.serialize_field("name", &self.name)?; - state.end() - } -} const P_GASES: [[f64; 6]; 10] = [ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], // birth [0.2, 0.2, 0.0, 0.0, 0.0, 0.0], // M / F / A / B, n <= 3 diff --git a/inserter/src/algorithm/data/theme_proto.rs b/inserter/src/algorithm/data/theme_proto.rs index fb137f3..61e0038 100644 --- a/inserter/src/algorithm/data/theme_proto.rs +++ b/inserter/src/algorithm/data/theme_proto.rs @@ -1,3 +1,5 @@ +use crate::algorithm::data::vector2::Vector2; + use super::enums::{PlanetType, ThemeDistribute, VeinType}; use once_cell::sync::Lazy; use serde::Serialize; @@ -11,7 +13,9 @@ pub struct ThemeProto { pub water_item_id: i32, #[serde(skip)] pub distribute: ThemeDistribute, + #[serde(skip)] pub temperature: f32, + #[serde(skip)] pub planet_type: PlanetType, #[serde(skip)] pub vein_spot: Vec, @@ -27,6 +31,12 @@ pub struct ThemeProto { pub gas_items: Vec, #[serde(skip)] pub gas_speeds: Vec, + #[serde(skip)] + pub algos: Vec, + #[serde(skip)] + pub mod_x: Vector2, + #[serde(skip)] + pub mod_y: Vector2, } pub const DEFAULT_THEME_PROTO: &'static ThemeProto = &ThemeProto { @@ -44,6 +54,9 @@ pub const DEFAULT_THEME_PROTO: &'static ThemeProto = &ThemeProto { rare_settings: vec![], gas_items: vec![], gas_speeds: vec![], + algos: vec![], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }; impl Default for ThemeProto { @@ -69,6 +82,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 1.0, 0.3, 0.3], gas_items: vec![], gas_speeds: vec![], + algos: vec![1], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 2, @@ -85,6 +101,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![1120, 1121], gas_speeds: vec![0.96, 0.04], + algos: vec![0], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 3, @@ -101,6 +120,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![1120, 1121], gas_speeds: vec![0.96, 0.04], + algos: vec![0], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 4, @@ -117,6 +139,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![1011, 1120], gas_speeds: vec![0.7, 0.3], + algos: vec![0], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 5, @@ -133,6 +158,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![1011, 1120], gas_speeds: vec![0.7, 0.3], + algos: vec![0], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 6, @@ -149,6 +177,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.18, 0.2, 0.3], gas_items: vec![], gas_speeds: vec![], + algos: vec![2], + mod_x: Vector2(1.0, 1.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 7, @@ -165,6 +196,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.3, 0.5, 0.7, 0.5, 0.0, 0.3, 0.2, 0.6], gas_items: vec![], gas_speeds: vec![], + algos: vec![2], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 8, @@ -181,6 +215,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 1.0, 0.3, 1.0, 0.0, 0.5, 0.2, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![1], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 9, @@ -189,7 +226,7 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { wind: 0.7, distribute: ThemeDistribute::Default, temperature: 5.0, - planet_type: PlanetType::Vocano, + planet_type: PlanetType::Volcano, vein_spot: vec![15, 15, 2, 9, 4, 2, 0], vein_count: vec![1.0, 1.0, 0.6, 1.0, 0.6, 0.3, 0.0], vein_opacity: vec![1.0, 1.0, 0.6, 1.0, 0.5, 0.3, 0.0], @@ -197,6 +234,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.2, 0.6, 0.7, 0.0, 0.2, 0.6, 0.7, 0.0, 0.1, 0.2, 0.8], gas_items: vec![], gas_speeds: vec![], + algos: vec![5], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 10, @@ -213,6 +253,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.3, 1.0, 0.8, 1.0, 0.0, 0.2, 0.6, 0.4, 0.0, 0.1, 0.2, 0.4], gas_items: vec![], gas_speeds: vec![], + algos: vec![3], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.34, -0.15), }, ThemeProto { id: 11, @@ -225,10 +268,20 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { vein_spot: vec![3, 3, 3, 6, 12, 0, 0], vein_count: vec![0.5, 0.5, 0.5, 1.0, 1.2, 0.0, 0.0], vein_opacity: vec![0.6, 0.6, 0.9, 0.9, 1.5, 0.0, 0.0], - rare_veins: vec![VeinType::Fireice, VeinType::Diamond, VeinType::Grat], - rare_settings: vec![0.25, 0.5, 0.6, 0.6, 0.0, 0.2, 0.6, 0.7, 0.0, 0.1, 0.2, 0.5], + rare_veins: vec![ + VeinType::Fireice, + VeinType::Diamond, + VeinType::Grat, + VeinType::Bamboo, + ], + rare_settings: vec![ + 0.25, 0.5, 0.6, 0.8, 0.0, 0.2, 0.6, 0.7, 0.0, 0.2, 0.3, 0.7, 0.1, 0.2, 0.2, 0.7, + ], gas_items: vec![], gas_speeds: vec![], + algos: vec![4], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 12, @@ -245,6 +298,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.25, 0.6, 0.6, 0.0, 0.25, 0.6, 0.6, 0.0, 0.1, 0.2, 0.5], gas_items: vec![], gas_speeds: vec![], + algos: vec![3], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 13, @@ -253,7 +309,7 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { wind: 0.8, distribute: ThemeDistribute::Interstellar, temperature: 4.0, - planet_type: PlanetType::Vocano, + planet_type: PlanetType::Volcano, vein_spot: vec![10, 10, 2, 7, 4, 1, 0], vein_count: vec![1.0, 1.0, 0.6, 1.0, 0.6, 0.3, 0.0], vein_opacity: vec![1.0, 1.0, 0.6, 1.0, 0.5, 0.3, 0.0], @@ -261,6 +317,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![], gas_speeds: vec![], + algos: vec![3], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 14, @@ -277,6 +336,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.4, 0.3, 0.5, 0.0, 1.0, 0.3, 0.8, 0.0, 0.5, 0.2, 0.8], gas_items: vec![], gas_speeds: vec![], + algos: vec![1], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 15, @@ -293,6 +355,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 1.0, 0.3, 1.0, 0.0, 0.5, 0.2, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![6], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 16, @@ -309,6 +374,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![1.0, 1.0, 1.0, 0.9], gas_items: vec![], gas_speeds: vec![], + algos: vec![7], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 17, @@ -325,6 +393,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.7, 0.7, 0.5, 0.0, 0.1, 0.2, 0.7], gas_items: vec![], gas_speeds: vec![], + algos: vec![2], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 18, @@ -341,6 +412,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 1.0, 0.3, 1.0, 0.0, 0.5, 0.2, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![1], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 19, @@ -357,6 +431,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.25, 0.6, 0.6, 0.0, 0.25, 0.6, 0.6, 0.0, 0.4, 0.3, 0.9], gas_items: vec![], gas_speeds: vec![], + algos: vec![8], + mod_x: Vector2(1.5, 1.5), + mod_y: Vector2(-5.0, -5.0), }, ThemeProto { id: 20, @@ -373,6 +450,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.25, 1.0, 0.6, 0.7, 0.0, 0.2, 0.6, 0.9, 0.0, 0.3, 0.4, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![9], + mod_x: Vector2(6.0, 6.0), + mod_y: Vector2(8.0, 8.0), }, ThemeProto { id: 21, @@ -389,6 +469,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![], gas_items: vec![1120, 1121], gas_speeds: vec![0.84, 0.16], + algos: vec![0], + mod_x: Vector2(0.0, 0.0), + mod_y: Vector2(0.0, 0.0), }, ThemeProto { id: 22, @@ -405,6 +488,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 1.0, 0.5, 1.0, 0.0, 0.6, 0.25, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![10], + mod_x: Vector2(1.0, 1.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 23, @@ -421,6 +507,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.7, 0.2, 0.6, 0.0, 1.0, 1.0, 0.84], gas_items: vec![], gas_speeds: vec![], + algos: vec![11], + mod_x: Vector2(1.5, 1.5), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 24, @@ -437,11 +526,14 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.3, 1.0, 0.8, 1.0, 0.0, 1.0, 0.7, 1.0, 0.0, 0.4, 0.5, 0.7], gas_items: vec![], gas_speeds: vec![], + algos: vec![12], + mod_x: Vector2(1.0, 1.0), + mod_y: Vector2(1.0, 1.0), }, ThemeProto { id: 25, name: "Desert 11", - water_item_id: -2, + water_item_id: 0, wind: 1.0, distribute: ThemeDistribute::Interstellar, temperature: 0.0, @@ -453,6 +545,9 @@ pub static THEME_PROTOS: Lazy> = Lazy::new(|| { rare_settings: vec![0.0, 0.5, 0.3, 1.0, 0.0, 1.0, 0.3, 1.0, 0.0, 0.5, 0.2, 1.0], gas_items: vec![], gas_speeds: vec![], + algos: vec![13], + mod_x: Vector2(1.0, 1.0), + mod_y: Vector2(3.0, 3.0), }, ] }); diff --git a/inserter/src/algorithm/data/vector2.rs b/inserter/src/algorithm/data/vector2.rs new file mode 100644 index 0000000..c280122 --- /dev/null +++ b/inserter/src/algorithm/data/vector2.rs @@ -0,0 +1,4 @@ +use serde::Serialize; + +#[derive(Debug, PartialEq, Clone, Copy, Serialize)] +pub struct Vector2(pub f64, pub f64); diff --git a/inserter/src/algorithm/data/vector3.rs b/inserter/src/algorithm/data/vector3.rs index 910058e..44cb648 100644 --- a/inserter/src/algorithm/data/vector3.rs +++ b/inserter/src/algorithm/data/vector3.rs @@ -1,6 +1,6 @@ use serde::Serialize; -#[derive(Debug, PartialEq, Clone, Serialize)] +#[derive(Debug, PartialEq, Clone, Copy, Serialize)] pub struct Vector3(pub f64, pub f64, pub f64); impl Vector3 { @@ -29,7 +29,7 @@ impl Vector3 { pub fn normalize(&mut self) -> &mut Self { let num = self.magnitude(); - if num > 9.99999974737875e-6 { + if num > 1e-5 { *self /= num; } else { *self = Self::zero(); @@ -82,6 +82,14 @@ impl std::ops::SubAssign<&Vector3> for Vector3 { } } +impl std::ops::Mul for Vector3 { + type Output = Vector3; + + fn mul(self, rhs: f64) -> Vector3 { + return Vector3(self.0 * rhs, self.1 * rhs, self.2 * rhs); + } +} + impl std::ops::Mul for &Vector3 { type Output = Vector3; diff --git a/inserter/src/algorithm/data/vector_f2.rs b/inserter/src/algorithm/data/vector_f2.rs new file mode 100644 index 0000000..cbacb7f --- /dev/null +++ b/inserter/src/algorithm/data/vector_f2.rs @@ -0,0 +1,204 @@ +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +/// f32-based 2D vector, port of Unity's Vector2. +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct VectorF2(pub f32, pub f32); + +impl VectorF2 { + pub fn new(x: f32, y: f32) -> Self { + Self(x, y) + } + + pub fn zero() -> Self { + Self(0.0, 0.0) + } + + pub fn up() -> Self { + Self(0.0, 1.0) + } + + pub fn down() -> Self { + Self(0.0, -1.0) + } + + pub fn right() -> Self { + Self(1.0, 0.0) + } + + pub fn left() -> Self { + Self(-1.0, 0.0) + } + + pub fn magnitude_sq(&self) -> f32 { + self.0 * self.0 + self.1 * self.1 + } + + pub fn magnitude(&self) -> f32 { + self.magnitude_sq().sqrt() + } + + pub fn normalize(&mut self) -> &mut Self { + let mag = self.magnitude(); + if mag > 1e-10 { + *self /= mag; + } else { + *self = Self::zero(); + } + self + } + + pub fn normalized(&self) -> Self { + let mag = self.magnitude(); + if mag > 1e-10 { + *self / mag + } else { + Self::zero() + } + } + + pub fn dot(a: &Self, b: &Self) -> f32 { + a.0 * b.0 + a.1 * b.1 + } + + pub fn distance_sq_from(&self, pt: &Self) -> f32 { + let dx = pt.0 - self.0; + let dy = pt.1 - self.1; + dx * dx + dy * dy + } +} + +impl Add for VectorF2 { + type Output = VectorF2; + + fn add(self, rhs: VectorF2) -> VectorF2 { + VectorF2(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl Add<&VectorF2> for VectorF2 { + type Output = VectorF2; + + fn add(self, rhs: &VectorF2) -> VectorF2 { + VectorF2(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl Add<&VectorF2> for &VectorF2 { + type Output = VectorF2; + + fn add(self, rhs: &VectorF2) -> VectorF2 { + VectorF2(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl AddAssign for VectorF2 { + fn add_assign(&mut self, rhs: VectorF2) { + self.0 += rhs.0; + self.1 += rhs.1; + } +} + +impl AddAssign<&VectorF2> for VectorF2 { + fn add_assign(&mut self, rhs: &VectorF2) { + self.0 += rhs.0; + self.1 += rhs.1; + } +} + +impl Sub for VectorF2 { + type Output = VectorF2; + + fn sub(self, rhs: VectorF2) -> VectorF2 { + VectorF2(self.0 - rhs.0, self.1 - rhs.1) + } +} + +impl Sub<&VectorF2> for VectorF2 { + type Output = VectorF2; + + fn sub(self, rhs: &VectorF2) -> VectorF2 { + VectorF2(self.0 - rhs.0, self.1 - rhs.1) + } +} + +impl Sub<&VectorF2> for &VectorF2 { + type Output = VectorF2; + + fn sub(self, rhs: &VectorF2) -> VectorF2 { + VectorF2(self.0 - rhs.0, self.1 - rhs.1) + } +} + +impl SubAssign for VectorF2 { + fn sub_assign(&mut self, rhs: VectorF2) { + self.0 -= rhs.0; + self.1 -= rhs.1; + } +} + +impl SubAssign<&VectorF2> for VectorF2 { + fn sub_assign(&mut self, rhs: &VectorF2) { + self.0 -= rhs.0; + self.1 -= rhs.1; + } +} + +impl Mul for VectorF2 { + type Output = VectorF2; + + fn mul(self, rhs: f32) -> VectorF2 { + VectorF2(self.0 * rhs, self.1 * rhs) + } +} + +impl Mul for &VectorF2 { + type Output = VectorF2; + + fn mul(self, rhs: f32) -> VectorF2 { + VectorF2(self.0 * rhs, self.1 * rhs) + } +} + +impl MulAssign for VectorF2 { + fn mul_assign(&mut self, rhs: f32) { + self.0 *= rhs; + self.1 *= rhs; + } +} + +impl DivAssign for VectorF2 { + fn div_assign(&mut self, rhs: f32) { + self.0 /= rhs; + self.1 /= rhs; + } +} + +impl Div for VectorF2 { + type Output = VectorF2; + + fn div(self, rhs: f32) -> VectorF2 { + VectorF2(self.0 / rhs, self.1 / rhs) + } +} + +impl Div for &VectorF2 { + type Output = VectorF2; + + fn div(self, rhs: f32) -> VectorF2 { + VectorF2(self.0 / rhs, self.1 / rhs) + } +} + +impl Neg for VectorF2 { + type Output = VectorF2; + + fn neg(self) -> VectorF2 { + VectorF2(-self.0, -self.1) + } +} + +impl std::fmt::Display for VectorF2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({}, {})", self.0, self.1) + } +} diff --git a/inserter/src/algorithm/data/vector_f3.rs b/inserter/src/algorithm/data/vector_f3.rs new file mode 100644 index 0000000..e14104a --- /dev/null +++ b/inserter/src/algorithm/data/vector_f3.rs @@ -0,0 +1,235 @@ +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +/// f32-based 3D vector, used for birth point generation +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct VectorF3(pub f32, pub f32, pub f32); + +impl VectorF3 { + pub fn new(x: f32, y: f32, z: f32) -> Self { + Self(x, y, z) + } + + pub const fn zero() -> Self { + Self(0.0, 0.0, 0.0) + } + + pub const fn up() -> Self { + Self(0.0, 1.0, 0.0) + } + + pub const fn forward() -> Self { + Self(0.0, 0.0, 1.0) + } + + pub const fn back() -> Self { + Self(0.0, 0.0, -1.0) + } + + pub const fn right() -> Self { + Self(1.0, 0.0, 0.0) + } + + pub const fn left() -> Self { + Self(-1.0, 0.0, 0.0) + } + + pub const fn down() -> Self { + Self(0.0, -1.0, 0.0) + } + + pub fn magnitude_sq(&self) -> f32 { + self.0 * self.0 + self.1 * self.1 + self.2 * self.2 + } + + pub fn magnitude(&self) -> f32 { + self.magnitude_sq().sqrt() + } + + pub fn normalize(&mut self) -> &mut Self { + let mag = self.magnitude(); + if mag > 1e-10 { + *self /= mag; + } else { + *self = Self::zero(); + } + self + } + + pub fn normalized(&self) -> Self { + let mag = self.magnitude(); + if mag > 1e-10 { + *self / mag + } else { + Self::zero() + } + } + + pub fn distance_sq_from(&self, pt: &Self) -> f32 { + let dx = pt.0 - self.0; + let dy = pt.1 - self.1; + let dz = pt.2 - self.2; + dx * dx + dy * dy + dz * dz + } + + pub fn cross(a: &Self, b: &Self) -> Self { + Self( + a.1 * b.2 - a.2 * b.1, + a.2 * b.0 - a.0 * b.2, + a.0 * b.1 - a.1 * b.0, + ) + } + + pub fn dot(a: &Self, b: &Self) -> f32 { + a.0 * b.0 + a.1 * b.1 + a.2 * b.2 + } + + pub fn slerp(lhs: &VectorF3, rhs: &VectorF3, percent: f32) -> VectorF3 { + let dot = VectorF3::dot(lhs, rhs).clamp(-1.0, 1.0); + let theta = dot.acos() * percent; + let mut relative_vec = rhs - &(lhs * dot); + relative_vec.normalize(); + &(lhs * theta.cos()) + &(&relative_vec * theta.sin()) + } +} + +impl Add for VectorF3 { + type Output = VectorF3; + + fn add(self, rhs: VectorF3) -> VectorF3 { + VectorF3(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) + } +} + +impl Add<&VectorF3> for VectorF3 { + type Output = VectorF3; + + fn add(self, rhs: &VectorF3) -> VectorF3 { + VectorF3(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) + } +} + +impl Add<&VectorF3> for &VectorF3 { + type Output = VectorF3; + + fn add(self, rhs: &VectorF3) -> VectorF3 { + VectorF3(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) + } +} + +impl AddAssign for VectorF3 { + fn add_assign(&mut self, rhs: VectorF3) { + self.0 += rhs.0; + self.1 += rhs.1; + self.2 += rhs.2; + } +} + +impl AddAssign<&VectorF3> for VectorF3 { + fn add_assign(&mut self, rhs: &VectorF3) { + self.0 += rhs.0; + self.1 += rhs.1; + self.2 += rhs.2; + } +} + +impl Sub for VectorF3 { + type Output = VectorF3; + + fn sub(self, rhs: VectorF3) -> VectorF3 { + VectorF3(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2) + } +} + +impl Sub<&VectorF3> for VectorF3 { + type Output = VectorF3; + + fn sub(self, rhs: &VectorF3) -> VectorF3 { + VectorF3(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2) + } +} + +impl Sub<&VectorF3> for &VectorF3 { + type Output = VectorF3; + + fn sub(self, rhs: &VectorF3) -> VectorF3 { + VectorF3(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2) + } +} + +impl SubAssign for VectorF3 { + fn sub_assign(&mut self, rhs: VectorF3) { + self.0 -= rhs.0; + self.1 -= rhs.1; + self.2 -= rhs.2; + } +} + +impl SubAssign<&VectorF3> for VectorF3 { + fn sub_assign(&mut self, rhs: &VectorF3) { + self.0 -= rhs.0; + self.1 -= rhs.1; + self.2 -= rhs.2; + } +} + +impl Mul for VectorF3 { + type Output = VectorF3; + + fn mul(self, rhs: f32) -> VectorF3 { + VectorF3(self.0 * rhs, self.1 * rhs, self.2 * rhs) + } +} + +impl Mul for &VectorF3 { + type Output = VectorF3; + + fn mul(self, rhs: f32) -> VectorF3 { + VectorF3(self.0 * rhs, self.1 * rhs, self.2 * rhs) + } +} + +impl MulAssign for VectorF3 { + fn mul_assign(&mut self, rhs: f32) { + self.0 *= rhs; + self.1 *= rhs; + self.2 *= rhs; + } +} + +impl DivAssign for VectorF3 { + fn div_assign(&mut self, rhs: f32) { + self.0 /= rhs; + self.1 /= rhs; + self.2 /= rhs; + } +} + +impl Div for VectorF3 { + type Output = VectorF3; + + fn div(self, rhs: f32) -> VectorF3 { + VectorF3(self.0 / rhs, self.1 / rhs, self.2 / rhs) + } +} + +impl Div for &VectorF3 { + type Output = VectorF3; + + fn div(self, rhs: f32) -> VectorF3 { + VectorF3(self.0 / rhs, self.1 / rhs, self.2 / rhs) + } +} + +impl Neg for VectorF3 { + type Output = VectorF3; + + fn neg(self) -> VectorF3 { + VectorF3(-self.0, -self.1, -self.2) + } +} + +impl std::fmt::Display for VectorF3 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({}, {}, {})", self.0, self.1, self.2) + } +} diff --git a/inserter/src/algorithm/data/vein.rs b/inserter/src/algorithm/data/vein.rs index b51c282..1fc81a8 100644 --- a/inserter/src/algorithm/data/vein.rs +++ b/inserter/src/algorithm/data/vein.rs @@ -3,7 +3,7 @@ use serde::Serialize; #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct Vein { +pub struct EstimatedVein { pub vein_type: VeinType, pub min_group: i32, pub max_group: i32, @@ -13,7 +13,7 @@ pub struct Vein { pub max_amount: i32, } -impl Default for Vein { +impl Default for EstimatedVein { fn default() -> Self { Self { vein_type: VeinType::None, @@ -27,7 +27,7 @@ impl Default for Vein { } } -impl Vein { +impl EstimatedVein { pub fn new() -> Self { Default::default() } @@ -44,3 +44,19 @@ impl Vein { / 8i64 } } + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActualVein { + pub vein_type: VeinType, + pub amount: i32, // times 4e-5 for oil +} + +impl Default for ActualVein { + fn default() -> Self { + Self { + vein_type: VeinType::None, + amount: 0, + } + } +} diff --git a/inserter/src/algorithm/mod.rs b/inserter/src/algorithm/mod.rs index d271c82..e2567f7 100644 --- a/inserter/src/algorithm/mod.rs +++ b/inserter/src/algorithm/mod.rs @@ -1,2 +1,2 @@ pub mod data; -pub mod worldgen; \ No newline at end of file +pub mod worldgen; diff --git a/inserter/src/algorithm/tests/mod.rs b/inserter/src/algorithm/tests/mod.rs new file mode 100644 index 0000000..9410494 --- /dev/null +++ b/inserter/src/algorithm/tests/mod.rs @@ -0,0 +1 @@ +pub mod worldgen_test; diff --git a/inserter/src/algorithm/tests/worldgen_test.rs b/inserter/src/algorithm/tests/worldgen_test.rs new file mode 100644 index 0000000..e345868 --- /dev/null +++ b/inserter/src/algorithm/tests/worldgen_test.rs @@ -0,0 +1,28 @@ +#[cfg(test)] +mod tests { + use std::cell::Cell; + + use crate::data::game_desc::GameDesc; + use crate::worldgen::galaxy_gen::create_galaxy; + + #[test] + fn test_worldgen() { + let game = GameDesc { + star_count: 64, + resource_multiplier: 1.0, + hive_initial_colonize: 1.0, + hive_max_density: 1.0, + use_actual_veins: true, + }; + let habitable_count = Cell::new(0_i32); + let galaxy = create_galaxy(1, &game, &habitable_count); + let _result = galaxy + .stars + .get(0) + .unwrap() + .get_planets() + .get(3) + .unwrap() + .get_actual_veins(); + } +} diff --git a/inserter/src/algorithm/worldgen/galaxy_gen.rs b/inserter/src/algorithm/worldgen/galaxy_gen.rs index ba827b5..998d6e7 100644 --- a/inserter/src/algorithm/worldgen/galaxy_gen.rs +++ b/inserter/src/algorithm/worldgen/galaxy_gen.rs @@ -3,33 +3,29 @@ use crate::algorithm::data::enums::{SpectrType, StarType}; use crate::algorithm::data::galaxy::Galaxy; use crate::algorithm::data::game_desc::GameDesc; use crate::algorithm::data::random::DspRandom; +use crate::algorithm::data::rule::{Evaluation, Rule}; use crate::algorithm::data::star::Star; use crate::algorithm::data::star_planets::StarWithPlanets; use crate::algorithm::data::vector3::Vector3; +use std::cell::Cell; use std::rc::Rc; -fn generate_temp_poses( - seed: i32, - target_count: usize, - iter_count: usize, - min_dist: f64, - min_step_len: f64, - max_step_len: f64, - flatten: f64, -) -> Vec { - let mut tmp_poses: Vec = vec![]; - let actual_iter_count = iter_count.clamp(1, 16); - random_poses( - &mut tmp_poses, - seed, - target_count * actual_iter_count, - min_dist, - max_step_len - min_step_len, - flatten, - ); +const ITER_COUNT: usize = 4; +const MIN_DIST: f64 = 2.0; +const MIN_DIST_SQ: f64 = MIN_DIST * MIN_DIST; +const STEP_DIFF: f64 = 3.5 - 2.3; +const FLATTEN: f64 = 0.18; +const MIN_DRUNK_NUM: i32 = 6; +const MAX_DRUNK_NUM: i32 = 8; +const DRUNK_NUM_RANGE: f64 = (MAX_DRUNK_NUM - MIN_DRUNK_NUM) as f64; + +fn generate_temp_poses(seed: i32, target_count: usize) -> Vec { + let max_count = target_count * ITER_COUNT; + let mut tmp_poses = Vec::with_capacity(max_count); + random_poses(&mut tmp_poses, seed, max_count); for index in (0..tmp_poses.len()).rev() { - if index % iter_count != 0 { + if index % ITER_COUNT != 0 { tmp_poses.remove(index); } if tmp_poses.len() <= target_count { @@ -40,35 +36,25 @@ fn generate_temp_poses( tmp_poses } -fn random_poses( - tmp_poses: &mut Vec, - seed: i32, - max_count: usize, - min_dist: f64, - step_diff: f64, - flatten: f64, -) { +fn random_poses(tmp_poses: &mut Vec, seed: i32, max_count: usize) { let mut rand = DspRandom::new(seed); - let num1 = rand.next_f64(); - let mut tmp_drunk: Vec = vec![]; + let drunk_walk_count_rand = rand.next_f64(); + let mut tmp_drunk: Vec = Vec::with_capacity(max_count); tmp_poses.push(Vector3::zero()); - let num2 = 6; - let num3 = 8; - let num4 = (num3 - num2) as f64; - let num5 = (num1 * num4 + (num2 as f64)) as i32; - for _ in 0..num5 { + let drunk_num = (drunk_walk_count_rand * DRUNK_NUM_RANGE + (MIN_DRUNK_NUM as f64)) as i32; + for _ in 0..drunk_num { for _ in 0..256 { - let num7 = rand.next_f64() * 2.0 - 1.0; - let num8 = (rand.next_f64() * 2.0 - 1.0) * flatten; - let num9 = rand.next_f64() * 2.0 - 1.0; - let num10 = rand.next_f64(); - let d = num7 * num7 + num8 * num8 + num9 * num9; - if d <= 1.0 && d >= 1e-8 { - let num11 = d.sqrt(); - let num12 = (num10 * step_diff + min_dist) / num11; - let pt = Vector3(num7 * num12, num8 * num12, num9 * num12); - if !check_collision(tmp_poses, &pt, min_dist) { - tmp_drunk.push(pt.clone()); + let u = rand.next_f64() * 2.0 - 1.0; + let w = (rand.next_f64() * 2.0 - 1.0) * FLATTEN; + let v = rand.next_f64() * 2.0 - 1.0; + let first_step_len_rand = rand.next_f64(); + let squared_length = u * u + w * w + v * v; + if squared_length <= 1.0 && squared_length >= 1e-8 { + let distance = squared_length.sqrt(); + let step_len_mult = (first_step_len_rand * STEP_DIFF + MIN_DIST) / distance; + let pt = Vector3(u * step_len_mult, w * step_len_mult, v * step_len_mult); + if !check_collision(tmp_poses, &pt) { + tmp_drunk.push(pt); tmp_poses.push(pt); if tmp_poses.len() >= max_count { return; @@ -82,21 +68,21 @@ fn random_poses( for pt in tmp_drunk.iter_mut() { if rand.next_f64() <= 0.7 { for _ in 0..256 { - let num15 = rand.next_f64() * 2.0 - 1.0; - let num16 = (rand.next_f64() * 2.0 - 1.0) * flatten; - let num17 = rand.next_f64() * 2.0 - 1.0; - let num18 = rand.next_f64(); - let d = num15 * num15 + num16 * num16 + num17 * num17; - if d <= 1.0 && d >= 1e-8 { - let num19 = d.sqrt(); - let num20 = (num18 * step_diff + min_dist) / num19; + let u = rand.next_f64() * 2.0 - 1.0; + let w = (rand.next_f64() * 2.0 - 1.0) * FLATTEN; + let v = rand.next_f64() * 2.0 - 1.0; + let step_len_rand = rand.next_f64(); + let squared_length2 = u * u + w * w + v * v; + if squared_length2 <= 1.0 && squared_length2 >= 1e-8 { + let distance = squared_length2.sqrt(); + let step_len_mult = (step_len_rand * STEP_DIFF + MIN_DIST) / distance; let new_pt = Vector3( - pt.0 + num15 * num20, - pt.1 + num16 * num20, - pt.2 + num17 * num20, + pt.0 + u * step_len_mult, + pt.1 + w * step_len_mult, + pt.2 + v * step_len_mult, ); - if !check_collision(tmp_poses, &new_pt, min_dist) { - *pt = new_pt.clone(); + if !check_collision(tmp_poses, &new_pt) { + *pt = new_pt; tmp_poses.push(new_pt); if tmp_poses.len() >= max_count { return; @@ -110,100 +96,122 @@ fn random_poses( } } -fn check_collision(tmp_poses: &Vec, pt: &Vector3, min_dist: f64) -> bool { - let min_dist_sq = min_dist * min_dist; +fn check_collision(tmp_poses: &Vec, pt: &Vector3) -> bool { tmp_poses .iter() - .any(|pt1| pt1.distance_sq_from(pt) < min_dist_sq) + .any(|existing_point| existing_point.distance_sq_from(pt) < MIN_DIST_SQ) } -fn generate_stars(game_desc: &GameDesc) -> Vec { - let galaxy_seed = game_desc.seed; - - let mut rand = DspRandom::new(galaxy_seed); - let tmp_poses = generate_temp_poses( - rand.next_seed(), - game_desc.star_count, - 4, - 2.0, - 2.3, - 3.5, - 0.18, - ); +fn generate_stars<'a>( + seed: i32, + game_desc: &'a GameDesc, + habitable_count: &'a Cell, +) -> Vec> { + let mut rand = DspRandom::new(seed); + let tmp_poses = generate_temp_poses(rand.next_seed(), game_desc.star_count); let star_count = tmp_poses.len(); - let num1 = rand.next_f32(); - let num2 = rand.next_f32(); - let num3 = rand.next_f32(); - let num4 = rand.next_f32(); - let num5 = (0.01 * (star_count as f64) + (num1 as f64) * 0.3).ceil() as usize; - let num6 = (0.01 * (star_count as f64) + (num2 as f64) * 0.3).ceil() as usize; - let num7 = (0.016 * (star_count as f64) + (num3 as f64) * 0.4).ceil() as usize; - let num8 = (0.013 * (star_count as f64) + (num4 as f64) * 1.3).ceil() as usize; - let num9 = star_count - num5; - let num10 = num9 - num6; - let num11 = num10 - num7; - let num12 = (num11 - 1) / num8; - let num13 = num12 / 2; + let black_hole_count_rand = rand.next_f32(); + let neutron_star_count_rand = rand.next_f32(); + let white_dwarf_count_rand = rand.next_f32(); + let giant_star_count_rand = rand.next_f32(); + let black_hole_num = ((0.01 * (star_count as f64) + (black_hole_count_rand as f64) * 0.3) + as f32) + .ceil() as usize; + let neutro_star_num = ((0.01 * (star_count as f64) + (neutron_star_count_rand as f64) * 0.3) + as f32) + .ceil() as usize; + let white_dwarf_num = ((0.016 * (star_count as f64) + (white_dwarf_count_rand as f64) * 0.4) + as f32) + .ceil() as usize; + let giant_star_num = ((0.013 * (star_count as f64) + (giant_star_count_rand as f64) * 1.4) + as f32) + .ceil() as usize; + let black_hole_start = star_count - black_hole_num; + let neutron_star_start = black_hole_start - neutro_star_num; + let white_dwarf_start = neutron_star_start - white_dwarf_num; + let giant_group_num = (white_dwarf_start - 1) / giant_star_num; + let giant_offset = giant_group_num / 2; - let mut stars: Vec = vec![]; + let mut stars: Vec = Vec::with_capacity(star_count); for (index, position) in tmp_poses.into_iter().enumerate() { let seed = rand.next_seed(); if index == 0 { - stars.push(StarWithPlanets::new(Rc::new(Star::new( + stars.push(StarWithPlanets::new( + Rc::new(Star::new( + game_desc, + 0, + seed, + Vector3::zero(), + StarType::MainSeqStar, + &SpectrType::X, + )), game_desc, - 0, - seed, - Vector3::zero(), - StarType::MainSeqStar, - &SpectrType::X, - )))); + habitable_count, + )); } else { let need_spectr = if index == 3 { SpectrType::M - } else if index == num11 - 1 { + } else if index == white_dwarf_start - 1 { SpectrType::O } else { SpectrType::X }; - let need_type = if index % num12 == num13 { - StarType::GiantStar - } else if index >= num9 { + let need_type = if index >= black_hole_start { StarType::BlackHole - } else if index >= num10 { + } else if index >= neutron_star_start { StarType::NeutronStar - } else if index >= num11 { + } else if index >= white_dwarf_start { StarType::WhiteDwarf + } else if index % giant_group_num == giant_offset { + StarType::GiantStar } else { StarType::MainSeqStar }; - stars.push(StarWithPlanets::new(Rc::new(Star::new( + stars.push(StarWithPlanets::new( + Rc::new(Star::new( + game_desc, + index, + seed, + position, + need_type, + &need_spectr, + )), game_desc, - index, - seed, - position, - need_type, - &need_spectr, - )))); + habitable_count, + )); } } stars } -pub fn create_galaxy(game_desc: &GameDesc) -> Galaxy { - let mut stars = generate_stars(game_desc); - let mut names: Vec<&str> = vec![]; +pub fn create_galaxy<'a>( + seed: i32, + game_desc: &'a GameDesc, + habitable_count: &'a Cell, +) -> Galaxy<'a> { + let mut stars = generate_stars(seed, game_desc, habitable_count); + let mut names: Vec<&str> = Vec::with_capacity(game_desc.star_count); for sp in stars.iter_mut() { let name = random_name(sp.star.name_seed, &sp.star, names.iter()); sp.name = name; - sp.load_planets(); names.push(&sp.name); + sp.load_planets(); } - Galaxy { - seed: game_desc.seed, - stars, - } + Galaxy { seed, stars } +} + +pub fn find_stars(seed: i32, game_desc: &GameDesc, rule: &Box) -> u64 { + let habitable_count = Cell::new(0_i32); + let galaxy = Galaxy { + seed, + stars: generate_stars(seed, game_desc, &habitable_count), + }; + + let evaluation = Evaluation::new(game_desc.star_count); + let result = rule.evaluate(&galaxy, &evaluation); + result } diff --git a/inserter/src/generate_csv.rs b/inserter/src/generate_csv.rs index 6a7b176..afd7411 100644 --- a/inserter/src/generate_csv.rs +++ b/inserter/src/generate_csv.rs @@ -3,15 +3,19 @@ use crate::algorithm::data::enums::{PlanetType, ORES}; use crate::algorithm::data::game_desc::GameDesc; use crate::algorithm::worldgen::galaxy_gen::create_galaxy; -pub fn gen_formatted(seed: i32, star_count: usize, resource_multiplier: f32) -> Result<(String, String), Box> { - let mut game_desc: GameDesc = GameDesc::default(); - game_desc.seed = seed; - game_desc.star_count = star_count; - game_desc.resource_multiplier = resource_multiplier; - let galaxy = create_galaxy(&game_desc); +pub fn gen_formatted(seed: i32) -> Result<(String, String), Box> { + let game_desc: GameDesc = GameDesc { + star_count: crate::STAR_COUNT, + resource_multiplier: crate::REC_MULTIPLIER, + hive_initial_colonize: crate::INITIAL_COLONIZE, + hive_max_density: crate::DF_MAX_DENSITY, + use_actual_veins: false + }; + let hab_count = std::cell::Cell::new(0i32); + let galaxy = create_galaxy(seed, &game_desc, &hab_count); - let mut stars: String = String::with_capacity(star_count * 128); - let mut planets: String = String::with_capacity(star_count * 256 * 5); + let mut stars: String = String::with_capacity(crate::STAR_COUNT * 128); + let mut planets: String = String::with_capacity(crate::STAR_COUNT * 256 * 5); for solar_system in galaxy.stars { let star = solar_system.star.clone(); @@ -25,7 +29,7 @@ pub fn gen_formatted(seed: i32, star_count: usize, resource_multiplier: f32) -> star.get_luminosity(), star.get_dyson_radius(), star.star_type as i32 + 1, - *star.get_spectr() as i32 + star.get_spectr() as i32 ).as_str()); for (index, ore) in ORES[1..15].iter().enumerate() { @@ -82,12 +86,12 @@ pub fn gen_formatted(seed: i32, star_count: usize, resource_multiplier: f32) -> planet.get_rotation_period() == planet.get_orbital_period() ).as_str()); - let veins = planet.get_veins(); + let veins = planet.get_estimated_veins(); // OPTIMIZATION: Use fixed-size array indexed by VeinType discriminant instead of HashMap. // VeinType is #[repr(i32)] with variants None=0..Max=15, so a 16-element array gives // O(1) lookup with zero heap allocation, vs HashMap which allocates per-planet. // Impact: Eliminates ~256K HashMap allocations per 1000 seeds in the hot loop. - let mut vein_map: [Option<&data::vein::Vein>; 16] = [None; 16]; + let mut vein_map: [Option<&data::vein::EstimatedVein>; 16] = [None; 16]; for v in veins.iter() { vein_map[v.vein_type as usize] = Some(v); } diff --git a/inserter/src/main.rs b/inserter/src/main.rs index 83ade0e..0a8d420 100644 --- a/inserter/src/main.rs +++ b/inserter/src/main.rs @@ -29,6 +29,8 @@ use std::{ const MAIN_INTERVAL: f32 = 0.1; // in seconds const STAR_COUNT: usize = 64; const REC_MULTIPLIER: f32 = 1.0; +const INITIAL_COLONIZE: f64 = 1.0; +const DF_MAX_DENSITY: f64 = 1.0; // The maximum amount of workers the binary supports. 32 should be way higher than what is reasonable on most machines const MAX_WORKERS: usize = 32; diff --git a/inserter/src/threads.rs b/inserter/src/threads.rs index 2bf3baf..ecc4f61 100644 --- a/inserter/src/threads.rs +++ b/inserter/src/threads.rs @@ -11,7 +11,7 @@ use crate::misc::{COPY_PLANET, COPY_STAR}; pub fn worker_thread(seeds: Range, send: Sender<(String, String)>, id: usize) -> Result<()> { for seed in seeds { - let entry = gen_formatted(seed, STAR_COUNT, REC_MULTIPLIER).expect("gen_formatted failed"); + let entry = gen_formatted(seed).expect("gen_formatted failed"); send.send(entry)?; PROGRESS_WORKERS[id].fetch_add(1, Relaxed); diff --git a/inserter/src/wasm.rs b/inserter/src/wasm.rs new file mode 100644 index 0000000..cc75385 --- /dev/null +++ b/inserter/src/wasm.rs @@ -0,0 +1,79 @@ +#![cfg(target_arch = "wasm32")] + +mod data; +mod rules; +mod transform_rules; +mod worldgen; + +use data::game_desc::GameDesc; +use serde::Serialize; +use std::cell::Cell; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::spawn_local; +use worldgen::galaxy_gen::{create_galaxy, find_stars}; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = worldgen)] + async fn found(value: JsValue) -> JsValue; +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn generate(seed: JsValue, gameDesc: JsValue) -> Result { + let seed: i32 = serde_wasm_bindgen::from_value(seed)?; + let game_desc: GameDesc = serde_wasm_bindgen::from_value(gameDesc)?; + let habitable_count = Cell::new(0_i32); + let galaxy = create_galaxy(seed, &game_desc, &habitable_count); + galaxy.serialize(&serde_wasm_bindgen::Serializer::json_compatible()) +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn searchStar( + seed: JsValue, + gameDesc: JsValue, + rule: JsValue, +) -> Result { + let seed: i32 = serde_wasm_bindgen::from_value(seed)?; + let game_desc: GameDesc = serde_wasm_bindgen::from_value(gameDesc)?; + let rule = serde_wasm_bindgen::from_value(rule).unwrap(); + let transformed = transform_rules::transform_rules(rule); + let star_indexes = find_stars(seed, &game_desc, &transformed); + let indexes: Vec = (0..64) + .filter(|&i| (star_indexes & (1_u64 << i)) != 0) + .collect(); + serde_wasm_bindgen::to_value(&indexes) +} + +#[wasm_bindgen] +#[allow(non_snake_case)] +pub fn findStars(gameDesc: JsValue, rule: JsValue, seeds: JsValue) { + spawn_local(async { + let game_desc: GameDesc = serde_wasm_bindgen::from_value(gameDesc).unwrap(); + let mut seeds: Vec = serde_wasm_bindgen::from_value(seeds).unwrap(); + let rule = serde_wasm_bindgen::from_value(rule).unwrap(); + let transformed = transform_rules::transform_rules(rule); + loop { + let mut results: Vec = vec![]; + for seed in seeds { + let star_indexes = find_stars(seed, &game_desc, &transformed); + if star_indexes != 0 { + results.push(seed); + } + } + let result = serde_wasm_bindgen::to_value(&results).unwrap(); + let next_batch: JsValue = found(result).await; + let next_seeds: Result, serde_wasm_bindgen::Error> = + serde_wasm_bindgen::from_value(next_batch); + match next_seeds { + Ok(f) => { + seeds = f; + } + Err(_) => { + break; + } + } + } + }) +}