adjust how figure head coloring is done

This commit is contained in:
Imbris 2019-08-28 22:48:06 -04:00
parent 9654262938
commit c4999f5e9d
5 changed files with 245 additions and 142 deletions

Binary file not shown.

View File

@ -1,4 +1,5 @@
use rand::{seq::SliceRandom, thread_rng};
use rand::{seq::SliceRandom, thread_rng, Rng};
use vek::Rgb;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Body {
@ -10,20 +11,21 @@ pub struct Body {
pub hand: Hand,
pub foot: Foot,
pub shoulder: Shoulder,
pub hair_color: HairColor,
pub hair_style: HairStyle,
pub beard: Beard,
pub skin: Skin,
pub eyebrows: Eyebrows,
pub eye_color: EyeColor,
pub accessory: Accessory,
pub hair_color: u8,
pub skin: u8,
pub eye_color: u8,
}
impl Body {
pub fn random() -> Self {
let mut rng = thread_rng();
let race = *(&ALL_RACES).choose(&mut rng).unwrap();
Self {
race: *(&ALL_RACES).choose(&mut rng).unwrap(),
race,
body_type: *(&ALL_BODY_TYPES).choose(&mut rng).unwrap(),
chest: *(&ALL_CHESTS).choose(&mut rng).unwrap(),
belt: *(&ALL_BELTS).choose(&mut rng).unwrap(),
@ -31,13 +33,13 @@ impl Body {
hand: *(&ALL_HANDS).choose(&mut rng).unwrap(),
foot: *(&ALL_FEET).choose(&mut rng).unwrap(),
shoulder: *(&ALL_SHOULDERS).choose(&mut rng).unwrap(),
hair_color: *(&ALL_HAIR_COLORS).choose(&mut rng).unwrap(),
hair_style: *(&ALL_HAIR_STYLES).choose(&mut rng).unwrap(),
beard: *(&ALL_BEARDS).choose(&mut rng).unwrap(),
skin: *(&ALL_SKINS).choose(&mut rng).unwrap(),
eyebrows: *(&ALL_EYEBROWS).choose(&mut rng).unwrap(),
eye_color: *(&ALL_EYE_COLORS).choose(&mut rng).unwrap(),
accessory: *(&ALL_ACCESSORIES).choose(&mut rng).unwrap(),
hair_color: rng.gen_range(0, race.num_hair_colors()) as u8,
skin: rng.gen_range(0, race.num_skin_colors()) as u8,
eye_color: rng.gen_range(0, race.num_eye_colors()) as u8,
}
}
}
@ -59,6 +61,92 @@ pub const ALL_RACES: [Race; 6] = [
Race::Orc,
Race::Undead,
];
// Hair Colors
pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 4] = [
(198, 169, 113),
(200, 100, 100),
(100, 100, 200),
(100, 200, 100),
];
pub const DWARF_HAIR_COLORS: [(u8, u8, u8); 3] =
[(200, 100, 100), (100, 100, 200), (100, 200, 100)];
pub const ELF_HAIR_COLORS: [(u8, u8, u8); 3] = [(200, 100, 100), (100, 100, 200), (100, 200, 100)];
pub const HUMAN_HAIR_COLORS: [(u8, u8, u8); 3] =
[(200, 100, 100), (100, 100, 200), (100, 200, 100)];
pub const ORC_HAIR_COLORS: [(u8, u8, u8); 3] = [(200, 100, 100), (100, 100, 200), (100, 200, 100)];
pub const UNDEAD_HAIR_COLORS: [(u8, u8, u8); 3] =
[(200, 100, 100), (100, 100, 200), (100, 200, 100)];
// Skin colors
pub const DANARI_SKIN_COLORS: [(u8, u8, u8); 4] = [
(198, 169, 113),
(200, 100, 100),
(100, 100, 200),
(100, 200, 100),
];
pub const DWARF_SKIN_COLORS: [(u8, u8, u8); 3] =
[(200, 100, 100), (100, 100, 200), (100, 200, 100)];
pub const ELF_SKIN_COLORS: [(u8, u8, u8); 3] = [(200, 100, 100), (100, 100, 200), (100, 200, 100)];
pub const HUMAN_SKIN_COLORS: [(u8, u8, u8); 3] =
[(200, 100, 100), (100, 100, 200), (100, 200, 100)];
pub const ORC_SKIN_COLORS: [(u8, u8, u8); 3] = [(200, 100, 100), (100, 100, 200), (100, 200, 100)];
pub const UNDEAD_SKIN_COLORS: [(u8, u8, u8); 3] =
[(200, 100, 100), (100, 100, 200), (100, 200, 100)];
impl Race {
fn hair_colors(self) -> &'static [(u8, u8, u8)] {
match self {
Race::Danari => &DANARI_HAIR_COLORS,
Race::Dwarf => &DWARF_HAIR_COLORS,
Race::Elf => &ELF_HAIR_COLORS,
Race::Human => &HUMAN_HAIR_COLORS,
Race::Orc => &ORC_HAIR_COLORS,
Race::Undead => &UNDEAD_HAIR_COLORS,
}
}
fn skin_colors(self) -> &'static [(u8, u8, u8)] {
match self {
Race::Danari => &DANARI_SKIN_COLORS,
Race::Dwarf => &DWARF_SKIN_COLORS,
Race::Elf => &ELF_SKIN_COLORS,
Race::Human => &HUMAN_SKIN_COLORS,
Race::Orc => &ORC_SKIN_COLORS,
Race::Undead => &UNDEAD_SKIN_COLORS,
}
}
fn eye_colors(self) -> &'static [EyeColor] {
match self {
_ => &ALL_EYE_COLORS,
}
}
pub fn hair_color(self, val: u8) -> Rgb<u8> {
self.hair_colors()
.get(val as usize)
.copied()
.unwrap_or((0, 0, 0))
.into()
}
pub fn num_hair_colors(self) -> usize {
self.hair_colors().len()
}
pub fn skin_color(self, val: u8) -> Rgb<u8> {
self.skin_colors()
.get(val as usize)
.copied()
.unwrap_or((0, 0, 0))
.into()
}
pub fn num_skin_colors(self) -> usize {
self.skin_colors().len()
}
pub fn eye_color(self, val: u8) -> EyeColor {
self.eye_colors()
.get(val as usize)
.copied()
.unwrap_or(EyeColor::Black)
}
pub fn num_eye_colors(self) -> usize {
self.eye_colors().len()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BodyType {
@ -135,31 +223,6 @@ pub enum HairStyle {
}
pub const ALL_HAIR_STYLES: [HairStyle; 3] = [HairStyle::None, HairStyle::Temp1, HairStyle::Temp2];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum HairColor {
Red,
Green,
Blue,
Brown,
Black,
}
pub const ALL_HAIR_COLORS: [HairColor; 5] = [
HairColor::Red,
HairColor::Green,
HairColor::Blue,
HairColor::Brown,
HairColor::Black,
];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Skin {
Light,
Medium,
Dark,
Rainbow,
}
pub const ALL_SKINS: [Skin; 4] = [Skin::Light, Skin::Medium, Skin::Dark, Skin::Rainbow];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Eyebrows {
Yup,
@ -183,6 +246,34 @@ pub const ALL_EYE_COLORS: [EyeColor; 6] = [
EyeColor::Red,
EyeColor::White,
];
impl EyeColor {
pub fn light_rgb(self) -> Rgb<u8> {
match self {
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),
}
}
pub fn dark_rgb(self) -> Rgb<u8> {
match self {
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),
}
}
pub fn white_rgb(self) -> Rgb<u8> {
match self {
EyeColor::White => Rgb::new(0, 0, 0),
_ => Rgb::new(255, 255, 255),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Accessory {

View File

@ -51,34 +51,52 @@ impl From<&DotVoxData> for Segment {
}
impl Segment {
/// Replaces one cell with another
pub fn replace(mut self, old: Cell, new: Cell) -> Self {
/// Transform cells
pub fn map(mut self, transform: impl Fn(Cell) -> Option<Cell>) -> Self {
for pos in self.iter_positions() {
if old == *self.get(pos).unwrap() {
if let Some(new) = transform(*self.get(pos).unwrap()) {
self.set(pos, new).unwrap();
}
}
self
}
/// Preserve the luminance of all the colors but set the chomaticity to match the provided color
// TODO add more advanced recoloring and/or indexed based coloring
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() {
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
/// Transform cell colors
pub fn map_rgb(self, transform: impl Fn(Rgb<u8>) -> Rgb<u8>) -> Self {
self.map(|cell| cell.get_color().map(|rgb| Cell::new(transform(rgb))))
}
/// Replaces one cell with another
// TODO unused -> remove?
pub fn replace(self, old: Cell, new: Cell) -> Self {
self.map(|cell| if cell == old { Some(new) } else { None })
}
/// Preserve the luminance of all the colors but set the chomaticity to match the provided color
pub fn chromify(self, chroma: Rgb<u8>) -> Self {
let chroma = chroma.map(|e| e as f32 / 255.0);
self.map_rgb(|rgb| {
chromify_srgb(rgb.map(|e| e as f32 / 255.0), chroma).map(|e| (e * 255.0) as u8)
})
}
// Sets the chromaticity based on the provided color
// Multiplies luma with luma of the provided color (might not be what we want)
/*pub fn colorify(mut self, color: Rgb<u8>) -> Self {
self.map_rgb(|rgb| {
let l = rgb_to_xyy(srgb_to_linear(rgb.map(|e| e as f32 / 255.0))).z;
let mut xyy = rgb_to_xyy(srgb_to_linear(color.map(|e| e as f32 / 255.0)));
xyy.z = l;
linear_to_srgb(xyy_to_rgb(xyy).map(|e| e.min(1.0).max(0.0))).map(|e| (e * 255.0) as u8)
})
}
// Multiplies the supplied color with all the current colors in linear space
pub fn tint(mut self, color: Rgb<u8>) -> Self {
self.map_rgb(|rgb| {
let c1 = srgb_to_linear(rgb.map(|e| e as f32 / 255.0));
let c2 = srgb_to_linear(color.map(|e| e as f32 / 255.0));
linear_to_srgb(c1*c2).map(|e| (e.min(1.0).max(0.0) * 255.0) as u8)
})
}*/
}
// TODO: move

View File

@ -883,36 +883,28 @@ impl CharSelectionUi {
self.character_body.hair_style = humanoid::ALL_HAIR_STYLES[new_val];
}
// Hair Color
let current_hair_color = self.character_body.hair_color;
if let Some(new_val) = char_slider(
self.ids.hairstyle_slider,
"Hair Color",
self.ids.haircolor_text,
humanoid::ALL_HAIR_COLORS.len() - 1,
humanoid::ALL_HAIR_COLORS
.iter()
.position(|&c| c == current_hair_color)
.unwrap_or(0),
self.character_body.race.num_hair_colors() - 1,
self.character_body.hair_color as usize,
self.ids.haircolor_slider,
ui_widgets,
) {
self.character_body.hair_color = humanoid::ALL_HAIR_COLORS[new_val];
self.character_body.hair_color = new_val as u8;
}
// Skin
let current_skin = self.character_body.skin;
if let Some(new_val) = char_slider(
self.ids.haircolor_slider,
"Skin",
self.ids.skin_text,
humanoid::ALL_SKINS.len() - 1,
humanoid::ALL_SKINS
.iter()
.position(|&c| c == current_skin)
.unwrap_or(0),
self.character_body.race.num_skin_colors() - 1,
self.character_body.skin as usize,
self.ids.skin_slider,
ui_widgets,
) {
self.character_body.skin = humanoid::ALL_SKINS[new_val];
self.character_body.skin = new_val as u8;
}
// Eyebrows
let current_eyebrows = self.character_body.eyebrows;
@ -931,20 +923,16 @@ impl CharSelectionUi {
self.character_body.eyebrows = humanoid::ALL_EYEBROWS[new_val];
}
// EyeColor
let current_eye_color = self.character_body.eye_color;
if let Some(new_val) = char_slider(
self.ids.eyebrows_slider,
"Eye Color",
self.ids.eyecolor_text,
humanoid::ALL_EYE_COLORS.len() - 1,
humanoid::ALL_EYE_COLORS
.iter()
.position(|&c| c == current_eye_color)
.unwrap_or(0),
self.character_body.race.num_eye_colors() - 1,
self.character_body.eye_color as usize,
self.ids.eyecolor_slider,
ui_widgets,
) {
self.character_body.eye_color = humanoid::ALL_EYE_COLORS[new_val];
self.character_body.eye_color = new_val as u8;
}
// Accessories
let current_accessory = self.character_body.accessory;

View File

@ -6,8 +6,8 @@ use common::{
assets::{self, watch::ReloadIndicator, Asset},
comp::{
humanoid::{
Accessory, Beard, Belt, BodyType, Chest, EyeColor, Eyebrows, Foot, HairColor,
HairStyle, Hand, Pants, Race, Shoulder, Skin,
Accessory, Beard, Belt, BodyType, Chest, EyeColor, Eyebrows, Foot, HairStyle, Hand,
Pants, Race, Shoulder,
},
item::Tool,
object, quadruped, quadruped_medium, Item,
@ -21,25 +21,48 @@ use serde_derive::{Deserialize, Serialize};
use std::{fs::File, io::BufReader, sync::Arc};
use vek::*;
pub fn load_segment(mesh_name: &str) -> Segment {
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_mat_segment(mesh_name: &str) -> MatSegment {
fn graceful_load_vox(mesh_name: &str) -> Arc<DotVoxData> {
let full_specifier: String = ["voxygen.voxel.", mesh_name].concat();
let dot_vox = match assets::load::<DotVoxData>(full_specifier.as_str()) {
match assets::load::<DotVoxData>(full_specifier.as_str()) {
Ok(dot_vox) => dot_vox,
Err(_) => {
error!("Could not load vox file for figure: {}", full_specifier);
assets::load_expect::<DotVoxData>("voxygen.voxel.not_found")
}
};
MatSegment::from(dot_vox.as_ref())
}
}
fn graceful_load_segment(mesh_name: &str) -> Segment {
Segment::from(graceful_load_vox(mesh_name).as_ref())
}
fn graceful_load_mat_segment(mesh_name: &str) -> MatSegment {
MatSegment::from(graceful_load_vox(mesh_name).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
}
fn color_segment(
mat_segment: MatSegment,
skin: Rgb<u8>,
hair_color: Rgb<u8>,
eye_color: EyeColor,
) -> Segment {
// TODO move some of the colors to common
mat_segment.to_segment(|mat| match mat {
Material::Skin => skin,
Material::Hair => hair_color,
// TODO add back multiple colors
Material::EyeLight => eye_color.light_rgb(),
Material::EyeDark => eye_color.dark_rgb(),
Material::EyeWhite => eye_color.white_rgb(),
})
}
#[derive(Serialize, Deserialize)]
struct VoxSpec(String, [i32; 3]); // All offsets should be relative to an initial origin that doesn't change when combining segments
// All reliant on humanoid::Race and humanoid::BodyType
@ -70,11 +93,11 @@ impl HumHeadSpec {
&self,
race: Race,
body_type: BodyType,
hair_color: HairColor,
hair_color: u8,
hair_style: HairStyle,
beard: Beard,
eye_color: EyeColor,
skin: Skin,
eye_color: u8,
skin: u8,
_eyebrows: Eyebrows,
accessory: Accessory,
) -> Mesh<FigurePipeline> {
@ -88,11 +111,35 @@ impl HumHeadSpec {
return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5));
}
};
let hair_rgb = race.hair_color(hair_color);
let skin_rgb = race.skin_color(skin);
let eye_color = race.eye_color(eye_color);
fn recolor_greys(segment: Segment, color: Rgb<u8>) -> Segment {
use common::util::{linear_to_srgb, srgb_to_linear};
segment.map_rgb(|rgb| {
const BASE_GREY: f32 = 178.0;
if rgb.r == rgb.g && rgb.g == rgb.b {
let c1 = srgb_to_linear(rgb.map(|e| e as f32 / BASE_GREY));
let c2 = srgb_to_linear(color.map(|e| e as f32 / 255.0));
linear_to_srgb(c1 * c2).map(|e| (e.min(1.0).max(0.0) * 255.0) as u8)
} else {
rgb
}
})
}
// Load segment pieces
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_mat_segment(&spec.0), Vec3::from(spec.1))),
Some(Some(spec)) => Some((
recolor_greys(graceful_load_segment(&spec.0), hair_rgb),
Vec3::from(spec.1),
)),
Some(None) => None,
None => {
warn!("No specification for this hair style: {:?}", hair_style);
@ -100,7 +147,10 @@ impl HumHeadSpec {
}
};
let beard = match spec.beard.get(&beard) {
Some(Some(spec)) => Some((graceful_load_mat_segment(&spec.0), Vec3::from(spec.1))),
Some(Some(spec)) => Some((
recolor_greys(graceful_load_segment(&spec.0), hair_rgb),
Vec3::from(spec.1),
)),
Some(None) => None,
None => {
warn!("No specification for this beard: {:?}", beard);
@ -108,7 +158,7 @@ impl HumHeadSpec {
}
};
let accessory = match spec.accessory.get(&accessory) {
Some(Some(spec)) => Some((graceful_load_mat_segment(&spec.0), Vec3::from(spec.1))),
Some(Some(spec)) => Some((graceful_load_segment(&spec.0), Vec3::from(spec.1))),
Some(None) => None,
None => {
warn!("No specification for this accessory: {:?}", accessory);
@ -117,65 +167,21 @@ impl HumHeadSpec {
};
let (head, origin_offset) = DynaUnionizer::new()
.add(bare_head, spec.head.1.into())
.add(eyes, spec.eyes.1.into())
.add(
color_segment(bare_head, skin_rgb, hair_rgb, eye_color),
spec.head.1.into(),
)
.add(
color_segment(eyes, skin_rgb, hair_rgb, eye_color),
spec.eyes.1.into(),
)
.maybe_add(hair)
.maybe_add(beard)
.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(
&colored,
&head,
Vec3::from(spec.offset) + origin_offset.map(|e| e as f32 * -1.0),
)
.0