use glam::Vec3;
use crate::gltf::{Collider, Physics};
use crate::mesh::Mesh;
use crate::palette::Palette;
use crate::recipe::BodyPlan;
use crate::sdf::{mesh_field, sd_ellipsoid, sd_round_cone, smin};
use super::rig::{MonsterRig, PrimKind, PrimTint, PrimitiveDesc};
pub(crate) fn eval_prim(d: &PrimitiveDesc, world: &[Vec3], p: Vec3) -> f32 {
let a = world[d.joint_a];
let b = world[d.joint_b];
match d.kind {
PrimKind::RoundCone => sd_round_cone(p, a, b, d.r1, d.r2),
PrimKind::Ellipsoid => {
let c = (a + b) * 0.5;
let r = match d.radii {
Some(rv) => rv,
None => Vec3::splat(d.r1) + (b - a).abs() * 0.5 + Vec3::splat(d.r2),
};
sd_ellipsoid(p, c, r)
}
}
}
pub(crate) fn organic_field(rig: &MonsterRig) -> impl Fn(Vec3) -> f32 + use<> {
let world = rig.world();
let prims = rig.prims.clone();
let max_rank = prims.iter().map(|d| d.fold_rank).max().unwrap_or(0);
let cross_k: Vec<f32> = (0..=max_rank as usize)
.map(|r| {
if r == 0 {
return 0.0;
}
let min_k = prims
.iter()
.filter(|d| d.fold_rank as usize == r)
.map(|d| d.k)
.fold(f32::INFINITY, f32::min);
if min_k.is_finite() { min_k * 0.55 } else { 0.0 }
})
.collect();
move |p: Vec3| -> f32 {
let mut band = vec![f32::INFINITY; max_rank as usize + 1];
for d in &prims {
let e = eval_prim(d, &world, p);
let r = d.fold_rank as usize;
band[r] = smin(band[r], e, d.k);
}
let mut acc = band[0];
for r in 1..=max_rank as usize {
if band[r].is_finite() {
acc = smin(acc, band[r], cross_k[r]);
}
}
acc
}
}
fn region_of(rig: &MonsterRig, world: &[Vec3], p: Vec3) -> (u8, PrimTint) {
let mut best = (f32::INFINITY, 0u8, PrimTint::Body);
for d in &rig.prims {
let dist = eval_prim(d, world, p);
if dist < best.0 {
best = (dist, d.fold_rank, d.tint);
}
}
(best.1, best.2)
}
pub(crate) fn build_body(
rig: &MonsterRig,
size: f32,
detail: f32,
seed: u64,
emissive: f32,
pal: &Palette,
) -> Mesh {
let world = rig.world();
let field = organic_field(rig);
let (lo, hi) = rig.bounds;
let s = size.clamp(0.2, 4.0);
let detail = detail.clamp(0.5, 2.0);
let cell = (0.028 * s / detail).clamp(0.012, 0.12);
let base = pal.terrain[2];
let dark = base * 0.6; let head_c = base * 0.88; let accent = pal.accent;
let bone = crate::palette::lerp(crate::palette::srgb(206, 196, 172), dark, 0.25);
let mid_y = (lo.y + hi.y) * 0.5;
let emissive_frac = emissive.clamp(0.0, 1.0);
let hash01 = |q: Vec3| -> f32 {
let key = ((q.x * 53.0) as i32).wrapping_mul(73856093)
^ ((q.y * 53.0) as i32).wrapping_mul(19349663)
^ ((q.z * 53.0) as i32).wrapping_mul(83492791)
^ (seed as i32).wrapping_mul(2654435761u32 as i32);
(key.rem_euclid(1000) as f32) / 1000.0
};
let color = |q: Vec3| -> Vec3 {
let (rank, tint) = region_of(rig, &world, q);
match tint {
PrimTint::Eye => return accent, PrimTint::Horn => return crate::palette::vary(bone, 0.05, hash01(q)),
PrimTint::Body => {}
}
let c = match rank {
0 => {
if q.y < mid_y {
dark
} else {
base
}
}
1 => head_c, 2 => dark, _ => base, };
let c = crate::palette::vary(c, 0.07, hash01(q));
if emissive_frac > 0.0 && hash01(q + Vec3::splat(11.0)) < emissive_frac {
return accent;
}
c
};
mesh_field(lo, hi, cell, &field, &color)
}
pub(crate) fn fit_collider(rig: &MonsterRig, size: f32, plan: BodyPlan) -> Physics {
let s = size.clamp(0.2, 4.0);
let (lo, hi) = rig.bounds;
let ext = hi - lo;
let half = ext * 0.5;
let collider = match plan {
BodyPlan::Arachnid | BodyPlan::Aberration => Collider::TriMesh,
BodyPlan::Ooze | BodyPlan::Insectoid => Collider::Box {
half_extents: half.max(Vec3::splat(0.05)),
},
_ => {
let radius = (ext.y.min(ext.x) * 0.42).max(0.05);
let height = ext.z.max(ext.y).max(radius * 2.0);
Collider::Capsule { radius, height }
}
};
Physics {
collider,
mass: 55.0 * s * s * s,
friction: 0.6,
restitution: 0.15,
}
}
#[cfg(test)]
mod tests {
use super::super::rig::build_rig;
use super::*;
use crate::recipe::MonsterParams;
#[test]
fn quadruped_body_meshes_and_is_watertight_ish() {
let p = MonsterParams::default();
let pal = crate::palette::by_name("verdant");
let rig = build_rig(&p);
let mesh = build_body(&rig, p.size, p.detail, p.seed, p.emissive, &pal);
assert!(mesh.positions.len() > 500, "non-trivial mesh");
assert!(mesh.indices.len().is_multiple_of(3), "triangulated");
let (lo, hi) = rig.bounds;
for v in &mesh.positions {
assert!(v.x >= lo.x - 0.5 && v.x <= hi.x + 0.5);
}
}
}