Add basic index based coloring for figures

This commit is contained in:
Imbris 2019-08-25 17:31:08 -04:00
parent a826eb76ec
commit 9d086949d1
3 changed files with 201 additions and 56 deletions

View File

@ -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<u8>),
}
impl Vox for MatCell {
fn empty() -> Self {
MatCell::None
}
fn is_empty(&self) -> bool {
match self {
MatCell::None => true,
_ => false,
}
}
}

View File

@ -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<u8>) -> 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<i32>)>);
// TODO: move
/// A `Dyna` builder that combines Dynas
pub struct DynaUnionizer<V: Vox>(Vec<(Dyna<V, ()>, Vec3<i32>)>);
impl SegmentUnionizer {
impl<V: Vox + Copy> DynaUnionizer<V> {
pub fn new() -> Self {
SegmentUnionizer(Vec::new())
DynaUnionizer(Vec::new())
}
pub fn add(mut self, segment: Segment, offset: Vec3<i32>) -> Self {
self.0.push((segment, offset));
pub fn add(mut self, dyna: Dyna<V, ()>, offset: Vec3<i32>) -> Self {
self.0.push((dyna, offset));
self
}
pub fn maybe_add(self, maybe: Option<(Segment, Vec3<i32>)>) -> Self {
pub fn maybe_add(self, maybe: Option<(Dyna<V, ()>, Vec3<i32>)>) -> 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<i32>) {
pub fn unify(self) -> (Dyna<V, ()>, Vec3<i32>) {
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<MatCell, ()>;
impl MatSegment {
pub fn to_segment(&self, map: impl Fn(Material) -> Rgb<u8>) -> 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::<Vec<_>>();
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(), ())
}
}
}

View File

@ -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::<DotVoxData>(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::<DotVoxData>(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::<DotVoxData>("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<f32>) -> Mesh<FigurePipeline> {
Meshable::<FigurePipeline, FigurePipeline>::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<FigurePipeline> {
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::<FigurePipeline, FigurePipeline>::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<FigurePipeline> {
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()