diff --git a/common/src/figure/mat_cell.rs b/common/src/figure/mat_cell.rs new file mode 100644 index 0000000000..905457f3b0 --- /dev/null +++ b/common/src/figure/mat_cell.rs @@ -0,0 +1,34 @@ +use crate::vol::Vox; +use vek::*; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Material { + Skin, + Hair, + EyeDark, + EyeLight, + EyeWhite, + //HairLight, + //HairDark, + //Clothing, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum MatCell { + None, + Mat(Material), + Normal(Rgb), +} + +impl Vox for MatCell { + fn empty() -> Self { + MatCell::None + } + + fn is_empty(&self) -> bool { + match self { + MatCell::None => true, + _ => false, + } + } +} diff --git a/common/src/figure/mod.rs b/common/src/figure/mod.rs index 5c3d9634f7..f46a24b68c 100644 --- a/common/src/figure/mod.rs +++ b/common/src/figure/mod.rs @@ -1,6 +1,9 @@ pub mod cell; +pub mod mat_cell; +pub use mat_cell::Material; use self::cell::Cell; +use self::mat_cell::MatCell; use crate::{ util::chromify_srgb, vol::{ReadVol, SizedVol, Vox, WriteVol}, @@ -31,11 +34,12 @@ impl From<&DotVoxData> for Segment { for voxel in &model.voxels { if let Some(&color) = palette.get(voxel.i as usize) { - // TODO: Maybe don't ignore this error? - let _ = segment.set( - Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| i32::from(e)), - Cell::new(color), - ); + segment + .set( + Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| i32::from(e)), + Cell::new(color), + ) + .unwrap(); } } @@ -51,7 +55,7 @@ impl Segment { pub fn replace(mut self, old: Cell, new: Cell) -> Self { for pos in self.iter_positions() { if old == *self.get(pos).unwrap() { - self.set(pos, new); + self.set(pos, new).unwrap(); } } @@ -62,69 +66,62 @@ impl Segment { pub fn chromify(mut self, chroma: Rgb) -> Self { let chroma = chroma.map(|e| e as f32 * 255.0); for pos in self.iter_positions() { - match self.get(pos).unwrap() { - Cell::Filled(rgb) => self - .set( - pos, - Cell::Filled( - chromify_srgb(Rgb::from_slice(rgb).map(|e| e as f32 / 255.0), chroma) - .map(|e| (e * 255.0) as u8) - .into_array(), - ), - ) - .unwrap(), - Cell::Empty => (), - } + let cell = match self.get(pos).unwrap() { + Cell::Filled(rgb) => Cell::Filled( + chromify_srgb(Rgb::from_slice(rgb).map(|e| e as f32 / 255.0), chroma) + .map(|e| (e * 255.0) as u8) + .into_array(), + ), + Cell::Empty => continue, + }; + self.set(pos, cell).unwrap(); } self } } -/// A `Segment` builder that combines segments -pub struct SegmentUnionizer(Vec<(Segment, Vec3)>); +// TODO: move +/// A `Dyna` builder that combines Dynas +pub struct DynaUnionizer(Vec<(Dyna, Vec3)>); -impl SegmentUnionizer { +impl DynaUnionizer { pub fn new() -> Self { - SegmentUnionizer(Vec::new()) + DynaUnionizer(Vec::new()) } - pub fn add(mut self, segment: Segment, offset: Vec3) -> Self { - self.0.push((segment, offset)); + pub fn add(mut self, dyna: Dyna, offset: Vec3) -> Self { + self.0.push((dyna, offset)); self } - pub fn maybe_add(self, maybe: Option<(Segment, Vec3)>) -> Self { + pub fn maybe_add(self, maybe: Option<(Dyna, Vec3)>) -> Self { match maybe { - Some((segment, offset)) => self.add(segment, offset), + Some((dyna, offset)) => self.add(dyna, offset), None => self, } } - pub fn unify(self) -> (Segment, Vec3) { + pub fn unify(self) -> (Dyna, Vec3) { if self.0.is_empty() { - return ( - Segment::filled(Vec3::new(0, 0, 0), Cell::empty(), ()), - Vec3::new(0, 0, 0), - ); + return (Dyna::filled(Vec3::zero(), V::empty(), ()), Vec3::zero()); } - // Determine size of the new segment + // Determine size of the new Dyna let mut min_point = self.0[0].1; let mut max_point = self.0[0].1 + self.0[0].0.get_size().map(|e| e as i32); - for (segment, offset) in self.0.iter().skip(1) { - let size = segment.get_size().map(|e| e as i32); + for (dyna, offset) in self.0.iter().skip(1) { + let size = dyna.get_size().map(|e| e as i32); min_point = min_point.map2(*offset, std::cmp::min); max_point = max_point.map2(offset + size, std::cmp::max); } let new_size = (max_point - min_point).map(|e| e as u32); // Allocate new segment - let mut combined = Segment::filled(new_size, Cell::empty(), ()); + let mut combined = Dyna::filled(new_size, V::empty(), ()); // Copy segments into combined let origin = min_point.map(|e| e * -1); - for (segment, offset) in self.0 { - for pos in segment.iter_positions() { - if let Cell::Filled(col) = *segment.get(pos).unwrap() { - combined - .set(origin + offset + pos, Cell::Filled(col)) - .unwrap(); + for (dyna, offset) in self.0 { + for pos in dyna.iter_positions() { + let vox = dyna.get(pos).unwrap(); + if !vox.is_empty() { + combined.set(origin + offset + pos, *vox).unwrap(); } } } @@ -132,3 +129,68 @@ impl SegmentUnionizer { (combined, origin) } } + +pub type MatSegment = Dyna; + +impl MatSegment { + pub fn to_segment(&self, map: impl Fn(Material) -> Rgb) -> Segment { + let mut vol = Dyna::filled(self.get_size(), Cell::empty(), ()); + for pos in self.iter_positions() { + let rgb = match self.get(pos).unwrap() { + MatCell::None => continue, + MatCell::Mat(mat) => map(*mat), + MatCell::Normal(rgb) => *rgb, + }; + vol.set(pos, Cell::new(rgb)).unwrap(); + } + vol + } +} + +impl From<&DotVoxData> for MatSegment { + fn from(dot_vox_data: &DotVoxData) -> Self { + if let Some(model) = dot_vox_data.models.get(0) { + let palette = dot_vox_data + .palette + .iter() + .map(|col| Rgba::from(col.to_ne_bytes()).into()) + .collect::>(); + + let mut vol = Dyna::filled( + Vec3::new(model.size.x, model.size.y, model.size.z), + MatCell::empty(), + (), + ); + + for voxel in &model.voxels { + let block = match voxel.i { + 0 => MatCell::Mat(Material::Skin), + 1 => MatCell::Mat(Material::Hair), + 2 => MatCell::Mat(Material::EyeDark), + 3 => MatCell::Mat(Material::EyeLight), + 7 => MatCell::Mat(Material::EyeWhite), + //1 => MatCell::Mat(Material::HairLight), + //1 => MatCell::Mat(Material::HairDark), + //6 => MatCell::Mat(Material::Clothing), + index => { + let color = palette + .get(index as usize) + .copied() + .unwrap_or_else(|| Rgb::broadcast(0)); + MatCell::Normal(color) + } + }; + + vol.set( + Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| i32::from(e)), + block, + ) + .unwrap(); + } + + vol + } else { + Dyna::filled(Vec3::zero(), MatCell::empty(), ()) + } + } +} diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index a83613e509..2711b834ca 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -12,7 +12,7 @@ use common::{ item::Tool, object, quadruped, quadruped_medium, Item, }, - figure::{Segment, SegmentUnionizer}, + figure::{DynaUnionizer, MatSegment, Material, Segment}, }; use dot_vox::DotVoxData; use hashbrown::HashMap; @@ -25,7 +25,7 @@ pub fn load_segment(mesh_name: &str) -> Segment { let full_specifier: String = ["voxygen.voxel.", mesh_name].concat(); Segment::from(assets::load_expect::(full_specifier.as_str()).as_ref()) } -pub fn graceful_load_segment(mesh_name: &str) -> Segment { +pub fn graceful_load_mat_segment(mesh_name: &str) -> MatSegment { let full_specifier: String = ["voxygen.voxel.", mesh_name].concat(); let dot_vox = match assets::load::(full_specifier.as_str()) { Ok(dot_vox) => dot_vox, @@ -34,7 +34,7 @@ pub fn graceful_load_segment(mesh_name: &str) -> Segment { assets::load_expect::("voxygen.voxel.not_found") } }; - Segment::from(dot_vox.as_ref()) + MatSegment::from(dot_vox.as_ref()) } pub fn load_mesh(mesh_name: &str, position: Vec3) -> Mesh { Meshable::::generate_mesh(&load_segment(mesh_name), position).0 @@ -75,7 +75,7 @@ impl HumHeadSpec { beard: Beard, eye_color: EyeColor, skin: Skin, - eyebrows: Eyebrows, + _eyebrows: Eyebrows, accessory: Accessory, ) -> Mesh { let spec = match self.0.get(&(race, body_type)) { @@ -85,15 +85,14 @@ impl HumHeadSpec { "No head specification exists for the combination of {:?} and {:?}", race, body_type ); - return load_mesh("not_found", Vec3::new(-5.0, -5.0, -5.0)); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); } }; - // TODO: color hair(via index or recoloring), color skin(via index) // Load segment pieces - let bare_head = graceful_load_segment(&spec.head.0); - let eyes = graceful_load_segment(&spec.eyes.0); + let bare_head = graceful_load_mat_segment(&spec.head.0); + let eyes = graceful_load_mat_segment(&spec.eyes.0); let hair = match spec.hair.get(&hair_style) { - Some(Some(spec)) => Some((graceful_load_segment(&spec.0), Vec3::from(spec.1))), + Some(Some(spec)) => Some((graceful_load_mat_segment(&spec.0), Vec3::from(spec.1))), Some(None) => None, None => { warn!("No specification for this hair style: {:?}", hair_style); @@ -101,7 +100,7 @@ impl HumHeadSpec { } }; let beard = match spec.beard.get(&beard) { - Some(Some(spec)) => Some((graceful_load_segment(&spec.0), Vec3::from(spec.1))), + Some(Some(spec)) => Some((graceful_load_mat_segment(&spec.0), Vec3::from(spec.1))), Some(None) => None, None => { warn!("No specification for this beard: {:?}", beard); @@ -109,7 +108,7 @@ impl HumHeadSpec { } }; let accessory = match spec.accessory.get(&accessory) { - Some(Some(spec)) => Some((graceful_load_segment(&spec.0), Vec3::from(spec.1))), + Some(Some(spec)) => Some((graceful_load_mat_segment(&spec.0), Vec3::from(spec.1))), Some(None) => None, None => { warn!("No specification for this accessory: {:?}", accessory); @@ -117,7 +116,7 @@ impl HumHeadSpec { } }; - let (head, origin_offset) = SegmentUnionizer::new() + let (head, origin_offset) = DynaUnionizer::new() .add(bare_head, spec.head.1.into()) .add(eyes, spec.eyes.1.into()) .maybe_add(hair) @@ -125,8 +124,58 @@ impl HumHeadSpec { .maybe_add(accessory) .unify(); + // TODO move this code to a fn + // TODO move some of the colors to rgb + let colored = head.to_segment(|mat| match mat { + Material::Skin => match skin { + // TODO include Race in match + Skin::Light => Rgb::new(243, 198, 165), + Skin::Medium => Rgb::new(203, 128, 97), + Skin::Dark => Rgb::new(151, 91, 67), + Skin::Rainbow => { + use rand::{seq::SliceRandom, thread_rng}; + *[ + Rgb::new(240, 4, 4), + Rgb::new(240, 140, 4), + Rgb::new(240, 235, 4), + Rgb::new(50, 240, 5), + Rgb::new(4, 4, 240), + Rgb::new(150, 0, 175), + ] + .choose(&mut thread_rng()) + .unwrap() + } + }, + Material::Hair => match hair_color { + HairColor::Red => Rgb::new(255, 20, 20), + HairColor::Green => Rgb::new(20, 255, 20), + HairColor::Blue => Rgb::new(20, 20, 255), + HairColor::Brown => Rgb::new(50, 50, 0), + HairColor::Black => Rgb::new(10, 10, 20), + }, + Material::EyeLight => match eye_color { + EyeColor::Black => Rgb::new(0, 0, 0), + EyeColor::Blue => Rgb::new(0, 0, 200), + EyeColor::Green => Rgb::new(0, 200, 0), + EyeColor::Brown => Rgb::new(150, 150, 0), + EyeColor::Red => Rgb::new(255, 0, 0), + EyeColor::White => Rgb::new(255, 255, 255), + }, + Material::EyeDark => match eye_color { + EyeColor::Black => Rgb::new(0, 0, 0), + EyeColor::Blue => Rgb::new(0, 0, 100), + EyeColor::Green => Rgb::new(0, 100, 0), + EyeColor::Brown => Rgb::new(50, 50, 0), + EyeColor::Red => Rgb::new(200, 0, 0), + EyeColor::White => Rgb::new(255, 255, 255), + }, + Material::EyeWhite => match eye_color { + EyeColor::White => Rgb::new(0, 0, 0), + _ => Rgb::new(255, 255, 255), + }, + }); Meshable::::generate_mesh( - &head, + &colored, Vec3::from(spec.offset) + origin_offset.map(|e| e as f32 * -1.0), ) .0 @@ -199,7 +248,7 @@ pub fn mesh_chest(chest: Chest) -> Mesh { let bare_chest = load_segment("figure.body.chest"); let chest_armor = load_segment("armor.chest.generic"); - let chest = SegmentUnionizer::new() + let chest = DynaUnionizer::new() .add(bare_chest, Vec3::new(0, 0, 0)) .add(chest_armor.chromify(Rgb::from(color)), Vec3::new(0, 0, 0)) .unify()