use std::f64::consts::PI;
use rand::Rng;
use symbios_genetics::Genotype;
use crate::material::TextureConfig;
use crate::{
ashlar::AshlarConfig,
asphalt::AsphaltConfig,
bark::BarkConfig,
brick::BrickConfig,
chain_link::ChainLinkConfig,
cobblestone::CobblestoneConfig,
concrete::ConcreteConfig,
corrugated::CorrugatedConfig,
encaustic::{EncausticConfig, EncausticPattern},
fabric::FabricConfig,
flame::FlameConfig,
flower::FlowerConfig,
ground::GroundConfig,
ice::IceConfig,
iron_grille::IronGrilleConfig,
lava::LavaConfig,
leaf::LeafConfig,
leaf_sprite::LeafSpriteConfig,
log_end::LogEndConfig,
marble::MarbleConfig,
metal::MetalConfig,
pavers::PaversConfig,
petal::PetalConfig,
plank::PlankConfig,
puff::PuffConfig,
ring::RingConfig,
rock::RockConfig,
sand::SandConfig,
shard::ShardConfig,
shingle::ShingleConfig,
snow::SnowConfig,
snowflake::SnowflakeConfig,
soft_disc::SoftDiscConfig,
spark::SparkConfig,
stained_glass::StainedGlassConfig,
stucco::StuccoConfig,
thatch::ThatchConfig,
twig::TwigConfig,
wainscoting::WainscotingConfig,
window::WindowConfig,
};
#[inline]
fn mutate_f64<R: Rng>(
val: f64,
rng: &mut R,
rate: f32,
half_range: f64,
min: f64,
max: f64,
) -> f64 {
if rng.random::<f32>() < rate {
(val + (rng.random::<f64>() - 0.5) * 2.0 * half_range).clamp(min, max)
} else {
val
}
}
#[inline]
fn mutate_f32<R: Rng>(
val: f32,
rng: &mut R,
rate: f32,
half_range: f32,
min: f32,
max: f32,
) -> f32 {
if rng.random::<f32>() < rate {
(val + (rng.random::<f32>() - 0.5) * 2.0 * half_range).clamp(min, max)
} else {
val
}
}
#[inline]
fn mutate_usize<R: Rng>(val: usize, rng: &mut R, rate: f32, min: usize, max: usize) -> usize {
if rng.random::<f32>() < rate {
if rng.random::<bool>() {
val.saturating_add(1).min(max)
} else {
val.saturating_sub(1).max(min)
}
} else {
val
}
}
#[inline]
fn mutate_seed<R: Rng>(val: u32, rng: &mut R, rate: f32) -> u32 {
if rng.random::<f32>() < rate {
rng.random::<u32>()
} else {
val
}
}
#[inline]
fn mutate_color3<R: Rng>(color: [f32; 3], rng: &mut R, rate: f32, half_range: f32) -> [f32; 3] {
[
mutate_f32(color[0], rng, rate, half_range, 0.0, 1.0),
mutate_f32(color[1], rng, rate, half_range, 0.0, 1.0),
mutate_f32(color[2], rng, rate, half_range, 0.0, 1.0),
]
}
#[inline]
fn crossover_color3<R: Rng>(a: [f32; 3], b: [f32; 3], rng: &mut R) -> [f32; 3] {
[
if rng.random::<bool>() { a[0] } else { b[0] },
if rng.random::<bool>() { a[1] } else { b[1] },
if rng.random::<bool>() { a[2] } else { b[2] },
]
}
macro_rules! impl_genotype {
(
$Config:ty {
$( $field:ident : $kind:tt $( ( $($param:tt),* ) )? ),+ $(,)?
}
$( post_mutate: |$ms:ident| $mutate_fix:block )?
$( post_crossover: |$cs:ident| $crossover_fix:block )?
) => {
impl Genotype for $Config {
fn mutate<R: Rng>(&mut self, rng: &mut R, rate: f32) {
$( impl_genotype!(@mutate self, rng, rate, $field, $kind $( ( $($param),* ) )? ); )+
$( let $ms = self; $mutate_fix )?
}
fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
#[allow(unused_mut)]
let mut child = Self {
$( $field: impl_genotype!(@crossover self, other, rng, $field, $kind $( ( $($param),* ) )? ), )+
};
$( let $cs = &mut child; $crossover_fix )?
child
}
}
};
(@mutate $s:ident, $rng:ident, $rate:ident, $f:ident, seed) => {
$s.$f = mutate_seed($s.$f, $rng, $rate);
};
(@mutate $s:ident, $rng:ident, $rate:ident, $f:ident, f64 ($hr:expr, $min:expr, $max:expr)) => {
$s.$f = mutate_f64($s.$f, $rng, $rate, $hr, $min, $max);
};
(@mutate $s:ident, $rng:ident, $rate:ident, $f:ident, f64_round ($hr:expr, $min:expr, $max:expr)) => {
$s.$f = mutate_f64($s.$f, $rng, $rate, $hr, $min, $max).round();
};
(@mutate $s:ident, $rng:ident, $rate:ident, $f:ident, f32 ($hr:expr, $min:expr, $max:expr)) => {
$s.$f = mutate_f32($s.$f, $rng, $rate, $hr, $min, $max);
};
(@mutate $s:ident, $rng:ident, $rate:ident, $f:ident, usize ($min:expr, $max:expr)) => {
$s.$f = mutate_usize($s.$f, $rng, $rate, $min, $max);
};
(@mutate $s:ident, $rng:ident, $rate:ident, $f:ident, color3 ($hr:expr)) => {
$s.$f = mutate_color3($s.$f, $rng, $rate, $hr);
};
(@mutate $s:ident, $rng:ident, $rate:ident, $f:ident, bool) => {
if $rng.random::<f32>() < $rate { $s.$f = !$s.$f; }
};
(@mutate $s:ident, $rng:ident, $rate:ident, $f:ident, enum_cycle ([ $($variant:expr),+ ])) => {
if $rng.random::<f32>() < $rate {
let variants = [ $($variant),+ ];
let cur = variants.iter().position(|v| *v == $s.$f).unwrap_or(0);
$s.$f = variants[(cur + 1) % variants.len()].clone();
}
};
(@mutate $s:ident, $rng:ident, $rate:ident, $f:ident, genotype) => {
$s.$f.mutate($rng, $rate);
};
(@crossover $a:ident, $b:ident, $rng:ident, $f:ident, seed) => {
if $rng.random::<bool>() { $a.$f } else { $b.$f }
};
(@crossover $a:ident, $b:ident, $rng:ident, $f:ident, f64 ($($p:tt)*)) => {
if $rng.random::<bool>() { $a.$f } else { $b.$f }
};
(@crossover $a:ident, $b:ident, $rng:ident, $f:ident, f64_round ($($p:tt)*)) => {
if $rng.random::<bool>() { $a.$f } else { $b.$f }
};
(@crossover $a:ident, $b:ident, $rng:ident, $f:ident, f32 ($($p:tt)*)) => {
if $rng.random::<bool>() { $a.$f } else { $b.$f }
};
(@crossover $a:ident, $b:ident, $rng:ident, $f:ident, usize ($($p:tt)*)) => {
if $rng.random::<bool>() { $a.$f } else { $b.$f }
};
(@crossover $a:ident, $b:ident, $rng:ident, $f:ident, color3 ($($p:tt)*)) => {
crossover_color3($a.$f, $b.$f, $rng)
};
(@crossover $a:ident, $b:ident, $rng:ident, $f:ident, bool) => {
if $rng.random::<bool>() { $a.$f } else { $b.$f }
};
(@crossover $a:ident, $b:ident, $rng:ident, $f:ident, enum_cycle ([ $($variant:expr),+ ])) => {
if $rng.random::<bool>() { $a.$f.clone() } else { $b.$f.clone() }
};
(@crossover $a:ident, $b:ident, $rng:ident, $f:ident, genotype) => {
$a.$f.crossover(&$b.$f, $rng)
};
}
impl_genotype!(BarkConfig {
seed: seed,
scale: f64(1.0, 0.5, 16.0),
octaves: usize(1, 12),
warp_octaves: usize(1, 6),
warp_u: f64(0.1, 0.0, 1.0),
warp_v: f64(0.2, 0.0, 2.0),
color_light: color3(0.07),
color_dark: color3(0.07),
normal_strength: f32(0.5, 0.5, 8.0),
furrow_multiplier: f64(0.2, 0.0, 1.0),
furrow_scale_u: f64(0.5, 0.5, 6.0),
furrow_scale_v: f64(0.1, 0.05, 1.0),
furrow_shape: f64(0.15, 0.1, 2.0),
});
impl_genotype!(RockConfig {
seed: seed,
scale: f64(0.75, 0.5, 12.0),
octaves: usize(1, 14),
attenuation: f64(0.25, 1.0, 4.0),
color_light: color3(0.07),
color_dark: color3(0.07),
normal_strength: f32(0.5, 0.5, 8.0),
});
impl_genotype!(GroundConfig {
seed: seed,
macro_scale: f64(0.5, 0.5, 8.0),
macro_octaves: usize(1, 10),
micro_scale: f64(1.0, 1.0, 20.0),
micro_octaves: usize(1, 10),
micro_weight: f64(0.1, 0.0, 1.0),
color_dry: color3(0.07),
color_moist: color3(0.07),
normal_strength: f32(0.5, 0.5, 8.0),
});
impl_genotype!(LeafConfig {
seed: seed,
color_base: color3(0.07),
color_edge: color3(0.07),
serration_strength: f64(0.01, 0.0, 0.15),
vein_angle: f64(0.3, 0.5, 6.0),
micro_detail: f64(0.1, 0.0, 1.0),
normal_strength: f32(0.3, 0.5, 6.0),
lobe_count: f64(1.0, 0.0, 10.0),
lobe_depth: f64(0.15, 0.0, 1.0),
lobe_sharpness: f64(0.4, 0.1, 5.0),
petiole_length: f64(0.02, 0.0, 0.25),
petiole_width: f64(0.003, 0.008, 0.05),
midrib_width: f64(0.02, 0.03, 0.35),
vein_count: f64(1.0, 2.0, 14.0),
venule_strength: f64(0.1, 0.0, 1.0),
});
impl_genotype!(TwigConfig {
leaf: genotype,
stem_color: color3(0.07),
stem_half_width: f64(0.005, 0.005, 0.05),
leaf_pairs: usize(1, 8),
leaf_angle: f64(0.15, 0.1, PI),
leaf_scale: f64(0.05, 0.15, 0.6),
stem_curve: f64(0.02, 0.0, 0.2),
sympodial: bool,
});
impl_genotype!(BrickConfig {
seed: seed,
scale: f64_round(1.0, 1.0, 12.0),
row_offset: f64(0.1, 0.0, 1.0),
aspect_ratio: f64(0.3, 1.0, 4.0),
mortar_size: f64(0.03, 0.01, 0.35),
bevel: f64(0.2, 0.0, 1.0),
cell_variance: f64(0.1, 0.0, 0.8),
roughness: f64(0.1, 0.0, 1.0),
color_brick: color3(0.07),
color_mortar: color3(0.07),
normal_strength: f32(0.5, 0.5, 8.0),
}
post_mutate: |s| {
if s.scale > 0.0 {
s.row_offset = (s.scale * s.row_offset).round() / s.scale;
}
}
post_crossover: |c| {
if c.scale > 0.0 {
c.row_offset = (c.scale * c.row_offset).round() / c.scale;
}
});
impl_genotype!(WindowConfig {
seed: seed,
frame_width: f64(0.02, 0.02, 0.4),
panes_x: usize(1, 6),
panes_y: usize(1, 8),
mullion_thickness: f64(0.005, 0.005, 0.15),
corner_radius: f64(0.01, 0.0, 0.35),
glass_opacity: f64(0.1, 0.0, 1.0),
grime_level: f64(0.1, 0.0, 1.0),
color_frame: color3(0.07),
normal_strength: f32(0.5, 0.5, 6.0),
});
impl_genotype!(PlankConfig {
seed: seed,
plank_count: f64_round(1.0, 2.0, 12.0),
grain_scale: f64(2.0, 4.0, 24.0),
joint_width: f64(0.02, 0.01, 0.25),
stagger: f64(0.15, 0.0, 1.0),
knot_density: f64(0.1, 0.0, 1.0),
grain_warp: f64(0.1, 0.0, 1.0),
color_wood_light: color3(0.07),
color_wood_dark: color3(0.07),
normal_strength: f32(0.5, 0.5, 6.0),
});
impl_genotype!(ShingleConfig {
seed: seed,
scale: f64_round(1.0, 2.0, 12.0),
shape_profile: f64(0.2, 0.0, 1.0),
overlap: f64(0.1, 0.0, 0.8),
stagger: f64(0.15, 0.0, 1.0),
moss_level: f64(0.1, 0.0, 1.0),
color_tile: color3(0.07),
color_grout: color3(0.07),
normal_strength: f32(0.5, 0.5, 8.0),
}
post_mutate: |s| {
if s.scale > 0.0 {
s.stagger = (s.scale * s.stagger).round() / s.scale;
}
}
post_crossover: |c| {
if c.scale > 0.0 {
c.stagger = (c.scale * c.stagger).round() / c.scale;
}
});
impl_genotype!(StuccoConfig {
seed: seed,
scale: f64(1.5, 1.0, 20.0),
octaves: usize(1, 10),
roughness: f64(0.1, 0.0, 1.0),
color_base: color3(0.07),
color_shadow: color3(0.07),
normal_strength: f32(0.5, 0.5, 6.0),
});
impl_genotype!(ConcreteConfig {
seed: seed,
scale: f64(1.0, 1.0, 16.0),
octaves: usize(1, 10),
roughness: f64(0.1, 0.0, 1.0),
formwork_lines: f64_round(1.0, 0.0, 12.0),
formwork_depth: f64(0.05, 0.0, 0.5),
pit_density: f64(0.04, 0.0, 0.45),
color_base: color3(0.07),
color_pit: color3(0.07),
normal_strength: f32(0.5, 0.5, 6.0),
});
impl_genotype!(MetalConfig {
seed: seed,
style: enum_cycle([
crate::metal::MetalStyle::Brushed,
crate::metal::MetalStyle::StandingSeam,
crate::metal::MetalStyle::Hammered,
crate::metal::MetalStyle::DiamondPlate
]),
scale: f64(1.0, 1.0, 16.0),
seam_count: f64_round(1.0, 1.0, 16.0),
seam_sharpness: f64(0.5, 0.5, 6.0),
brush_stretch: f64(1.5, 1.0, 20.0),
roughness: f64(0.1, 0.0, 1.0),
metallic: f32(0.1, 0.0, 1.0),
rust_level: f64(0.1, 0.0, 1.0),
color_metal: color3(0.07),
color_rust: color3(0.07),
normal_strength: f32(0.5, 0.5, 6.0),
});
impl_genotype!(PaversConfig {
seed: seed,
layout: enum_cycle([
crate::pavers::PaversLayout::Square,
crate::pavers::PaversLayout::Hexagonal
]),
scale: f64_round(1.0, 1.0, 16.0),
aspect_ratio: f64(0.2, 0.5, 3.0),
grout_width: f64(0.02, 0.01, 0.35),
bevel: f64(0.15, 0.0, 1.0),
cell_variance: f64(0.08, 0.0, 0.8),
roughness: f64(0.1, 0.0, 1.0),
color_stone: color3(0.07),
color_grout: color3(0.07),
normal_strength: f32(0.5, 0.5, 8.0),
});
impl_genotype!(AshlarConfig {
seed: seed,
rows: usize(2, 8),
cols: usize(2, 6),
mortar_size: f64(0.02, 0.005, 0.15),
bevel: f64(0.2, 0.0, 1.0),
cell_variance: f64(0.1, 0.0, 0.8),
chisel_depth: f64(0.1, 0.0, 1.0),
roughness: f64(0.1, 0.0, 1.0),
color_stone: color3(0.07),
color_mortar: color3(0.07),
normal_strength: f32(0.5, 0.5, 8.0),
});
impl_genotype!(CobblestoneConfig {
seed: seed,
scale: f64(1.0, 2.0, 14.0),
gap_width: f64(0.03, 0.01, 0.3),
cell_variance: f64(0.1, 0.0, 0.8),
roundness: f64(0.15, 0.3, 2.5),
color_stone: color3(0.07),
color_mud: color3(0.07),
normal_strength: f32(0.5, 0.5, 8.0),
});
impl_genotype!(ThatchConfig {
seed: seed,
density: f64(2.0, 3.0, 24.0),
anisotropy: f64(1.0, 2.0, 20.0),
warp_strength: f64(0.05, 0.0, 0.6),
layer_count: f64(1.0, 2.0, 20.0),
layer_shadow: f64(0.1, 0.0, 1.0),
color_straw: color3(0.07),
color_shadow: color3(0.07),
normal_strength: f32(0.5, 0.5, 6.0),
});
impl_genotype!(MarbleConfig {
seed: seed,
scale: f64(1.0, 0.5, 10.0),
octaves: usize(2, 10),
warp_octaves: usize(1, 6),
warp_strength: f64(0.15, 0.0, 2.0),
vein_frequency: f64(0.5, 0.5, 10.0),
vein_sharpness: f64(0.5, 0.3, 8.0),
roughness: f64(0.05, 0.0, 0.4),
color_base: color3(0.07),
color_vein: color3(0.07),
normal_strength: f32(0.3, 0.0, 4.0),
});
impl_genotype!(CorrugatedConfig {
seed: seed,
ridges: f64_round(2.0, 2.0, 20.0),
ridge_depth: f64(0.2, 0.3, 2.5),
roughness: f64(0.1, 0.0, 1.0),
rust_level: f64(0.1, 0.0, 1.0),
metallic: f32(0.1, 0.0, 1.0),
color_metal: color3(0.07),
color_rust: color3(0.07),
normal_strength: f32(0.5, 0.5, 6.0),
});
impl_genotype!(AsphaltConfig {
seed: seed,
scale: f64(1.0, 1.0, 14.0),
aggregate_density: f64(0.05, 0.02, 0.5),
aggregate_scale: f64(3.0, 4.0, 40.0),
roughness: f64(0.05, 0.5, 1.0),
stain_level: f64(0.1, 0.0, 1.0),
color_base: color3(0.05),
color_aggregate: color3(0.07),
normal_strength: f32(0.3, 0.0, 4.0),
});
impl_genotype!(WainscotingConfig {
seed: seed,
panels_x: usize(1, 4),
panels_y: usize(1, 4),
frame_width: f64(0.05, 0.05, 0.4),
panel_inset: f64(0.02, 0.0, 0.2),
grain_scale: f64(2.0, 4.0, 28.0),
grain_warp: f64(0.1, 0.0, 1.0),
color_wood_light: color3(0.07),
color_wood_dark: color3(0.07),
normal_strength: f32(0.5, 0.5, 8.0),
});
impl_genotype!(StainedGlassConfig {
seed: seed,
cell_count: usize(3, 30),
lead_width: f64(0.01, 0.01, 0.15),
saturation: f32(0.1, 0.3, 1.0),
glass_roughness: f64(0.02, 0.0, 0.2),
grime_level: f64(0.05, 0.0, 0.6),
normal_strength: f32(0.3, 0.0, 4.0),
});
impl_genotype!(IronGrilleConfig {
seed: seed,
bars_x: usize(1, 12),
bars_y: usize(1, 12),
bar_width: f64(0.02, 0.01, 0.25),
round_bars: bool,
rust_level: f64(0.1, 0.0, 1.0),
color_iron: color3(0.07),
color_rust: color3(0.07),
normal_strength: f32(0.5, 0.5, 6.0),
});
impl_genotype!(EncausticConfig {
seed: seed,
scale: f64_round(1.0, 1.0, 12.0),
pattern: enum_cycle([
EncausticPattern::Checkerboard,
EncausticPattern::Octagon,
EncausticPattern::Diamond
]),
grout_width: f64(0.02, 0.01, 0.2),
glaze_roughness: f64(0.02, 0.0, 0.15),
color_a: color3(0.07),
color_b: color3(0.07),
color_grout: color3(0.07),
normal_strength: f32(0.5, 0.5, 6.0),
});
impl_genotype!(SoftDiscConfig {
seed: seed,
variant_rows: usize(1, 16),
variant_cols: usize(1, 16),
color_core: color3(0.07),
color_halo: color3(0.07),
core_radius: f64(0.05, 0.0, 0.9),
falloff: f64(0.5, 0.3, 8.0),
ellipticity: f64(0.08, 0.0, 0.6),
scale_jitter: f64(0.05, 0.0, 0.5),
normal_strength: f32(0.3, 0.0, 4.0),
});
impl_genotype!(SparkConfig {
seed: seed,
variant_rows: usize(1, 16),
variant_cols: usize(1, 16),
points: usize(2, 12),
color_core: color3(0.07),
color_tip: color3(0.07),
core_radius: f64(0.03, 0.02, 0.5),
arm_sharpness: f64(0.8, 0.5, 10.0),
falloff: f64(0.4, 0.5, 6.0),
length_jitter: f64(0.1, 0.0, 0.8),
normal_strength: f32(0.3, 0.0, 4.0),
});
impl_genotype!(SnowflakeConfig {
seed: seed,
variant_rows: usize(1, 16),
variant_cols: usize(1, 16),
arms: usize(3, 8),
color: color3(0.05),
core_radius: f64(0.03, 0.0, 0.4),
arm_width: f64(0.01, 0.01, 0.12),
branch_pairs: usize(0, 5),
branch_angle: f64(0.1, 0.3, 1.4),
branch_scale: f64(0.1, 0.1, 1.0),
softness: f64(0.005, 0.005, 0.08),
normal_strength: f32(0.3, 0.0, 4.0),
});
impl_genotype!(PuffConfig {
seed: seed,
variant_rows: usize(1, 16),
variant_cols: usize(1, 16),
color_base: color3(0.07),
color_shadow: color3(0.07),
noise_scale: f64(0.7, 1.0, 8.0),
octaves: usize(1, 8),
warp: f64(0.15, 0.0, 1.5),
density: f64(0.1, 0.0, 1.0),
edge_falloff: f64(0.5, 0.5, 6.0),
contrast: f64(0.3, 0.5, 4.0),
normal_strength: f32(0.3, 0.0, 4.0),
});
impl_genotype!(RingConfig {
seed: seed,
variant_rows: usize(1, 16),
variant_cols: usize(1, 16),
color: color3(0.07),
radius: f64(0.08, 0.1, 0.9),
thickness: f64(0.04, 0.01, 0.5),
falloff: f64(0.5, 0.5, 6.0),
waviness: f64(0.04, 0.0, 0.3),
wave_count: usize(2, 16),
radius_jitter: f64(0.05, 0.0, 0.4),
normal_strength: f32(0.3, 0.0, 4.0),
});
impl_genotype!(PetalConfig {
seed: seed,
variant_rows: usize(1, 16),
variant_cols: usize(1, 16),
color_base: color3(0.07),
color_edge: color3(0.07),
color_throat: color3(0.07),
length: f64(0.06, 0.4, 1.0),
width: f64(0.08, 0.15, 0.95),
peak: f64(0.07, 0.3, 0.9),
tip_notch: f64(0.03, 0.0, 0.25),
curl: f64(0.1, 0.0, 1.0),
asymmetry: f64(0.05, 0.0, 0.4),
normal_strength: f32(0.3, 0.0, 4.0),
});
impl_genotype!(ShardConfig {
seed: seed,
variant_rows: usize(1, 16),
variant_cols: usize(1, 16),
color_base: color3(0.07),
color_edge: color3(0.07),
sides: usize(3, 9),
irregularity: f64(0.1, 0.0, 0.9),
edge_band: f64(0.05, 0.02, 0.5),
grain: f64(0.1, 0.0, 1.0),
normal_strength: f32(0.4, 0.0, 6.0),
});
impl_genotype!(LogEndConfig {
seed: seed,
ring_count: f64_round(2.0, 4.0, 30.0),
ring_warp: f64(0.1, 0.0, 1.0),
ring_contrast: f64(0.3, 0.5, 4.0),
crack_count: f64_round(1.0, 0.0, 12.0),
bark_width: f64(0.02, 0.02, 0.2),
color_early: color3(0.06),
color_late: color3(0.06),
color_bark: color3(0.06),
normal_strength: f32(0.3, 0.5, 6.0),
});
impl_genotype!(ChainLinkConfig {
seed: seed,
cell_count: f64_round(1.0, 4.0, 16.0),
wire_radius: f64(0.015, 0.02, 0.2),
weave_depth: f64(0.1, 0.0, 1.0),
rust_level: f64(0.1, 0.0, 1.0),
color_wire: color3(0.06),
color_rust: color3(0.06),
normal_strength: f32(0.3, 0.5, 6.0),
});
impl_genotype!(LavaConfig {
seed: seed,
plate_scale: f64(0.7, 3.0, 12.0),
crack_width: f64(0.03, 0.02, 0.3),
glow_falloff: f64(0.3, 0.5, 4.0),
color_crust: color3(0.04),
color_glow: color3(0.07),
emissive_intensity: f32(0.3, 0.0, 4.0),
normal_strength: f32(0.3, 0.5, 6.0),
});
impl_genotype!(IceConfig {
seed: seed,
scale: f64(0.6, 1.0, 8.0),
crack_density: f64(0.7, 1.0, 8.0),
vein_sharpness: f64(1.0, 2.0, 12.0),
frost_level: f64(0.1, 0.0, 1.0),
color_ice: color3(0.05),
color_crack: color3(0.05),
normal_strength: f32(0.25, 0.0, 4.0),
});
impl_genotype!(SnowConfig {
seed: seed,
drift_scale: f64(0.5, 1.0, 6.0),
drift_octaves: usize(2, 6),
sparkle_density: f64(0.04, 0.0, 0.5),
crust_roughness: f64(0.06, 0.5, 1.0),
color_snow: color3(0.05),
color_shadow: color3(0.05),
normal_strength: f32(0.3, 0.5, 5.0),
});
impl_genotype!(SandConfig {
seed: seed,
ripple_count: f64_round(2.0, 4.0, 24.0),
ripple_warp: f64(0.15, 0.0, 1.5),
grain_density: f64(0.05, 0.0, 0.5),
grain_scale: f64(4.0, 8.0, 48.0),
color_crest: color3(0.07),
color_trough: color3(0.07),
normal_strength: f32(0.3, 0.5, 6.0),
});
impl_genotype!(FabricConfig {
seed: seed,
thread_count: f64_round(4.0, 8.0, 64.0),
thread_width: f64(0.08, 0.3, 0.98),
weave_contrast: f64(0.12, 0.0, 1.0),
fuzz: f64(0.1, 0.0, 1.0),
color_warp: color3(0.07),
color_weft: color3(0.07),
normal_strength: f32(0.4, 0.5, 6.0),
});
impl_genotype!(FlowerConfig {
seed: seed,
variant_rows: usize(1, 16),
variant_cols: usize(1, 16),
petal: genotype,
petal_count: usize(4, 12),
center_radius: f64(0.03, 0.05, 0.3),
center_color: color3(0.07),
dot_density: f64(0.1, 0.0, 1.0),
normal_strength: f32(0.2, 0.0, 4.0),
});
impl_genotype!(FlameConfig {
seed: seed,
variant_rows: usize(1, 16),
variant_cols: usize(1, 16),
elongation: f64(0.2, 1.0, 3.0),
turbulence: f64(0.15, 0.0, 1.5),
lean_jitter: f64(0.05, 0.0, 0.5),
falloff: f64(0.3, 0.5, 4.0),
color_core: color3(0.07),
color_mid: color3(0.07),
color_tip: color3(0.07),
normal_strength: f32(0.2, 0.0, 4.0),
});
impl_genotype!(LeafSpriteConfig {
seed: seed,
variant_rows: usize(1, 16),
variant_cols: usize(1, 16),
leaf: genotype,
shape_jitter: f64(0.1, 0.0, 1.0),
tint_jitter: f32(0.08, 0.0, 1.0),
});
macro_rules! impl_texture_config_genotype {
($(($variant:ident, $module:ident, $config_ty:ty, $generator_ty:ty, $kind:ident)),* $(,)?) => {
impl Genotype for TextureConfig {
fn mutate<R: Rng>(&mut self, rng: &mut R, rate: f32) {
match self {
TextureConfig::None => {}
$(TextureConfig::$variant(c) => c.mutate(rng, rate)),*,
}
}
fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
match (self, other) {
$((TextureConfig::$variant(a), TextureConfig::$variant(b)) =>
TextureConfig::$variant(a.crossover(b, rng)),)*
(a, b) => {
if rng.random::<bool>() {
a.clone()
} else {
b.clone()
}
}
}
}
}
};
}
crate::registry::for_each_generator!(impl_texture_config_genotype);
#[cfg(test)]
mod tests {
use rand::SeedableRng;
use super::*;
fn seeded_rng() -> rand::rngs::StdRng {
rand::rngs::StdRng::seed_from_u64(42)
}
#[test]
fn bark_mutate_is_deterministic() {
let base = BarkConfig::default();
let mut a = base.clone();
let mut b = base.clone();
a.mutate(&mut seeded_rng(), 1.0);
b.mutate(&mut seeded_rng(), 1.0);
assert_eq!(a.seed, b.seed);
assert_eq!(a.octaves, b.octaves);
}
#[test]
fn bark_mutate_rate_zero_is_identity() {
let base = BarkConfig::default();
let mut c = base.clone();
c.mutate(&mut seeded_rng(), 0.0);
assert_eq!(c.seed, base.seed);
assert_eq!(c.octaves, base.octaves);
assert!((c.scale - base.scale).abs() < f64::EPSILON);
}
#[test]
fn bark_crossover_fields_from_parents() {
let a = BarkConfig::default();
let b = BarkConfig {
seed: 99,
octaves: 3,
scale: 8.0,
..BarkConfig::default()
};
let child = a.crossover(&b, &mut seeded_rng());
assert!(child.seed == a.seed || child.seed == b.seed);
assert!(child.octaves == a.octaves || child.octaves == b.octaves);
assert!(child.scale == a.scale || child.scale == b.scale);
}
#[test]
fn rock_mutate_rate_zero_is_identity() {
let base = RockConfig::default();
let mut c = base.clone();
c.mutate(&mut seeded_rng(), 0.0);
assert_eq!(c.seed, base.seed);
assert!((c.attenuation - base.attenuation).abs() < f64::EPSILON);
}
#[test]
fn ground_mutate_clamps_micro_weight() {
let mut c = GroundConfig {
micro_weight: 0.99,
..GroundConfig::default()
};
let mut rng = seeded_rng();
for _ in 0..50 {
c.mutate(&mut rng, 1.0);
assert!((0.0..=1.0).contains(&c.micro_weight));
}
}
#[test]
fn leaf_crossover_valid() {
let a = LeafConfig::default();
let b = LeafConfig {
seed: 77,
vein_angle: 4.0,
..LeafConfig::default()
};
let child = a.crossover(&b, &mut seeded_rng());
assert!(child.seed == a.seed || child.seed == b.seed);
assert!(child.vein_angle == a.vein_angle || child.vein_angle == b.vein_angle);
}
#[test]
fn twig_mutate_delegates_to_leaf() {
let base = TwigConfig::default();
let mut c = base.clone();
c.mutate(&mut seeded_rng(), 1.0);
assert!((1..=8).contains(&c.leaf_pairs));
assert!(c.stem_half_width >= 0.005 && c.stem_half_width <= 0.05);
}
#[test]
fn twig_crossover_valid() {
let a = TwigConfig {
sympodial: false,
leaf_pairs: 2,
..TwigConfig::default()
};
let b = TwigConfig {
sympodial: true,
leaf_pairs: 6,
..TwigConfig::default()
};
let child = a.crossover(&b, &mut seeded_rng());
assert!(child.leaf_pairs == a.leaf_pairs || child.leaf_pairs == b.leaf_pairs);
assert!(child.sympodial == a.sympodial || child.sympodial == b.sympodial);
}
}