mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add basic index based coloring for figures
This commit is contained in:
34
common/src/figure/mat_cell.rs
Normal file
34
common/src/figure/mat_cell.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
pub mod cell;
|
pub mod cell;
|
||||||
|
pub mod mat_cell;
|
||||||
|
pub use mat_cell::Material;
|
||||||
|
|
||||||
use self::cell::Cell;
|
use self::cell::Cell;
|
||||||
|
use self::mat_cell::MatCell;
|
||||||
use crate::{
|
use crate::{
|
||||||
util::chromify_srgb,
|
util::chromify_srgb,
|
||||||
vol::{ReadVol, SizedVol, Vox, WriteVol},
|
vol::{ReadVol, SizedVol, Vox, WriteVol},
|
||||||
@ -31,11 +34,12 @@ impl From<&DotVoxData> for Segment {
|
|||||||
|
|
||||||
for voxel in &model.voxels {
|
for voxel in &model.voxels {
|
||||||
if let Some(&color) = palette.get(voxel.i as usize) {
|
if let Some(&color) = palette.get(voxel.i as usize) {
|
||||||
// TODO: Maybe don't ignore this error?
|
segment
|
||||||
let _ = segment.set(
|
.set(
|
||||||
Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| i32::from(e)),
|
Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| i32::from(e)),
|
||||||
Cell::new(color),
|
Cell::new(color),
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +55,7 @@ impl Segment {
|
|||||||
pub fn replace(mut self, old: Cell, new: Cell) -> Self {
|
pub fn replace(mut self, old: Cell, new: Cell) -> Self {
|
||||||
for pos in self.iter_positions() {
|
for pos in self.iter_positions() {
|
||||||
if old == *self.get(pos).unwrap() {
|
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 {
|
pub fn chromify(mut self, chroma: Rgb<u8>) -> Self {
|
||||||
let chroma = chroma.map(|e| e as f32 * 255.0);
|
let chroma = chroma.map(|e| e as f32 * 255.0);
|
||||||
for pos in self.iter_positions() {
|
for pos in self.iter_positions() {
|
||||||
match self.get(pos).unwrap() {
|
let cell = match self.get(pos).unwrap() {
|
||||||
Cell::Filled(rgb) => self
|
Cell::Filled(rgb) => Cell::Filled(
|
||||||
.set(
|
chromify_srgb(Rgb::from_slice(rgb).map(|e| e as f32 / 255.0), chroma)
|
||||||
pos,
|
.map(|e| (e * 255.0) as u8)
|
||||||
Cell::Filled(
|
.into_array(),
|
||||||
chromify_srgb(Rgb::from_slice(rgb).map(|e| e as f32 / 255.0), chroma)
|
),
|
||||||
.map(|e| (e * 255.0) as u8)
|
Cell::Empty => continue,
|
||||||
.into_array(),
|
};
|
||||||
),
|
self.set(pos, cell).unwrap();
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
Cell::Empty => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `Segment` builder that combines segments
|
// TODO: move
|
||||||
pub struct SegmentUnionizer(Vec<(Segment, Vec3<i32>)>);
|
/// 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 {
|
pub fn new() -> Self {
|
||||||
SegmentUnionizer(Vec::new())
|
DynaUnionizer(Vec::new())
|
||||||
}
|
}
|
||||||
pub fn add(mut self, segment: Segment, offset: Vec3<i32>) -> Self {
|
pub fn add(mut self, dyna: Dyna<V, ()>, offset: Vec3<i32>) -> Self {
|
||||||
self.0.push((segment, offset));
|
self.0.push((dyna, offset));
|
||||||
self
|
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 {
|
match maybe {
|
||||||
Some((segment, offset)) => self.add(segment, offset),
|
Some((dyna, offset)) => self.add(dyna, offset),
|
||||||
None => self,
|
None => self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn unify(self) -> (Segment, Vec3<i32>) {
|
pub fn unify(self) -> (Dyna<V, ()>, Vec3<i32>) {
|
||||||
if self.0.is_empty() {
|
if self.0.is_empty() {
|
||||||
return (
|
return (Dyna::filled(Vec3::zero(), V::empty(), ()), Vec3::zero());
|
||||||
Segment::filled(Vec3::new(0, 0, 0), Cell::empty(), ()),
|
|
||||||
Vec3::new(0, 0, 0),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine size of the new segment
|
// Determine size of the new Dyna
|
||||||
let mut min_point = self.0[0].1;
|
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);
|
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) {
|
for (dyna, offset) in self.0.iter().skip(1) {
|
||||||
let size = segment.get_size().map(|e| e as i32);
|
let size = dyna.get_size().map(|e| e as i32);
|
||||||
min_point = min_point.map2(*offset, std::cmp::min);
|
min_point = min_point.map2(*offset, std::cmp::min);
|
||||||
max_point = max_point.map2(offset + size, std::cmp::max);
|
max_point = max_point.map2(offset + size, std::cmp::max);
|
||||||
}
|
}
|
||||||
let new_size = (max_point - min_point).map(|e| e as u32);
|
let new_size = (max_point - min_point).map(|e| e as u32);
|
||||||
// Allocate new segment
|
// 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
|
// Copy segments into combined
|
||||||
let origin = min_point.map(|e| e * -1);
|
let origin = min_point.map(|e| e * -1);
|
||||||
for (segment, offset) in self.0 {
|
for (dyna, offset) in self.0 {
|
||||||
for pos in segment.iter_positions() {
|
for pos in dyna.iter_positions() {
|
||||||
if let Cell::Filled(col) = *segment.get(pos).unwrap() {
|
let vox = dyna.get(pos).unwrap();
|
||||||
combined
|
if !vox.is_empty() {
|
||||||
.set(origin + offset + pos, Cell::Filled(col))
|
combined.set(origin + offset + pos, *vox).unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,3 +129,68 @@ impl SegmentUnionizer {
|
|||||||
(combined, origin)
|
(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(), ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,7 +12,7 @@ use common::{
|
|||||||
item::Tool,
|
item::Tool,
|
||||||
object, quadruped, quadruped_medium, Item,
|
object, quadruped, quadruped_medium, Item,
|
||||||
},
|
},
|
||||||
figure::{Segment, SegmentUnionizer},
|
figure::{DynaUnionizer, MatSegment, Material, Segment},
|
||||||
};
|
};
|
||||||
use dot_vox::DotVoxData;
|
use dot_vox::DotVoxData;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
@ -25,7 +25,7 @@ pub fn load_segment(mesh_name: &str) -> Segment {
|
|||||||
let full_specifier: String = ["voxygen.voxel.", mesh_name].concat();
|
let full_specifier: String = ["voxygen.voxel.", mesh_name].concat();
|
||||||
Segment::from(assets::load_expect::<DotVoxData>(full_specifier.as_str()).as_ref())
|
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 full_specifier: String = ["voxygen.voxel.", mesh_name].concat();
|
||||||
let dot_vox = match assets::load::<DotVoxData>(full_specifier.as_str()) {
|
let dot_vox = match assets::load::<DotVoxData>(full_specifier.as_str()) {
|
||||||
Ok(dot_vox) => dot_vox,
|
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")
|
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> {
|
pub fn load_mesh(mesh_name: &str, position: Vec3<f32>) -> Mesh<FigurePipeline> {
|
||||||
Meshable::<FigurePipeline, FigurePipeline>::generate_mesh(&load_segment(mesh_name), position).0
|
Meshable::<FigurePipeline, FigurePipeline>::generate_mesh(&load_segment(mesh_name), position).0
|
||||||
@ -75,7 +75,7 @@ impl HumHeadSpec {
|
|||||||
beard: Beard,
|
beard: Beard,
|
||||||
eye_color: EyeColor,
|
eye_color: EyeColor,
|
||||||
skin: Skin,
|
skin: Skin,
|
||||||
eyebrows: Eyebrows,
|
_eyebrows: Eyebrows,
|
||||||
accessory: Accessory,
|
accessory: Accessory,
|
||||||
) -> Mesh<FigurePipeline> {
|
) -> Mesh<FigurePipeline> {
|
||||||
let spec = match self.0.get(&(race, body_type)) {
|
let spec = match self.0.get(&(race, body_type)) {
|
||||||
@ -85,15 +85,14 @@ impl HumHeadSpec {
|
|||||||
"No head specification exists for the combination of {:?} and {:?}",
|
"No head specification exists for the combination of {:?} and {:?}",
|
||||||
race, body_type
|
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
|
// Load segment pieces
|
||||||
let bare_head = graceful_load_segment(&spec.head.0);
|
let bare_head = graceful_load_mat_segment(&spec.head.0);
|
||||||
let eyes = graceful_load_segment(&spec.eyes.0);
|
let eyes = graceful_load_mat_segment(&spec.eyes.0);
|
||||||
let hair = match spec.hair.get(&hair_style) {
|
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,
|
Some(None) => None,
|
||||||
None => {
|
None => {
|
||||||
warn!("No specification for this hair style: {:?}", hair_style);
|
warn!("No specification for this hair style: {:?}", hair_style);
|
||||||
@ -101,7 +100,7 @@ impl HumHeadSpec {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let beard = match spec.beard.get(&beard) {
|
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,
|
Some(None) => None,
|
||||||
None => {
|
None => {
|
||||||
warn!("No specification for this beard: {:?}", beard);
|
warn!("No specification for this beard: {:?}", beard);
|
||||||
@ -109,7 +108,7 @@ impl HumHeadSpec {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let accessory = match spec.accessory.get(&accessory) {
|
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,
|
Some(None) => None,
|
||||||
None => {
|
None => {
|
||||||
warn!("No specification for this accessory: {:?}", accessory);
|
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(bare_head, spec.head.1.into())
|
||||||
.add(eyes, spec.eyes.1.into())
|
.add(eyes, spec.eyes.1.into())
|
||||||
.maybe_add(hair)
|
.maybe_add(hair)
|
||||||
@ -125,8 +124,58 @@ impl HumHeadSpec {
|
|||||||
.maybe_add(accessory)
|
.maybe_add(accessory)
|
||||||
.unify();
|
.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(
|
Meshable::<FigurePipeline, FigurePipeline>::generate_mesh(
|
||||||
&head,
|
&colored,
|
||||||
Vec3::from(spec.offset) + origin_offset.map(|e| e as f32 * -1.0),
|
Vec3::from(spec.offset) + origin_offset.map(|e| e as f32 * -1.0),
|
||||||
)
|
)
|
||||||
.0
|
.0
|
||||||
@ -199,7 +248,7 @@ pub fn mesh_chest(chest: Chest) -> Mesh<FigurePipeline> {
|
|||||||
|
|
||||||
let bare_chest = load_segment("figure.body.chest");
|
let bare_chest = load_segment("figure.body.chest");
|
||||||
let chest_armor = load_segment("armor.chest.generic");
|
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(bare_chest, Vec3::new(0, 0, 0))
|
||||||
.add(chest_armor.chromify(Rgb::from(color)), Vec3::new(0, 0, 0))
|
.add(chest_armor.chromify(Rgb::from(color)), Vec3::new(0, 0, 0))
|
||||||
.unify()
|
.unify()
|
||||||
|
Reference in New Issue
Block a user