From a1c8a4a2f90434e6a7563433dd794e29e75c4c35 Mon Sep 17 00:00:00 2001 From: Imbris Date: Wed, 28 Aug 2019 22:48:06 -0400 Subject: [PATCH] adjust how figure head coloring is done --- .../voxel/figure/hair/danari/female.vox | Bin 2364 -> 45525 bytes common/src/comp/body/humanoid.rs | 157 ++++++++++++++---- common/src/figure/mod.rs | 58 ++++--- voxygen/src/menu/char_selection/ui.rs | 30 +--- voxygen/src/scene/figure/load.rs | 138 +++++++-------- 5 files changed, 243 insertions(+), 140 deletions(-) diff --git a/assets/voxygen/voxel/figure/hair/danari/female.vox b/assets/voxygen/voxel/figure/hair/danari/female.vox index 3ac396d9a268f2a218cfdb9b9119b91a50969aba..954aec0e3fd404d8ca89cc7942c4e86e3f18e853 100644 GIT binary patch literal 45525 zcmd6vdz@5d8OPr_=e*}OyBo?)Kw$-xo2@f@1qEFMghlQk0xILMJG(pXWwzO6xwKh| znn);f4xyETXhdeB5Rzs^V5F9rp_yewW@h)StnB*qd*4|uyI}nLyvw`ieeU1)a?TFt zv#@yXlHx5wh}n~C=HMa@>=t5S&9Z3)NaY^PYtP+N6jOmJ~TtMskKU z0!NC_u_A#B*^@@jwT#fS%s|>+jc*&FFU?TGM_Q54l99l&48$6tX_<&O!-#DLHte<) z$vHBDTn2I>2W&2Ud|Smw0tvkZy=9r9Db1XX{C30*Ov?%kY2_lem9r6VqrL+U2MjK9 zc(A#a6-wcRK71sYC2SUKmSus(3L>@ygAv%MCvAVGgZvI+TpcSBC&2(4bQW~B?bn!6 z<_zd<34Sm*&`aS4GUDbO)h(HG;bTXfoM%hah227)h=Uw5G;BMxz;6o&+!k_3%wK}p z4n5n3KVmF9XF)b0+la9tJJQa%+OG5t>MPj^q(oj^ZCoAO0Sj_Kwj3{K*=}e@yqxKx zJ8o!rUSPOx5b@l=E3^X}evab>j_v0B0y}i!=Q?iA^Ssb=p)151cy1^iFO;?$Sg>2L zo3NX(8=jks_?Qc7*{J0gVm(~sLoKhs30?TP@bi2x@KDo_I)U%IIf*Jap|hcLpmUJRje-#|52W$3mB3)G=_4$dw zEU?0YLiiQH&o{$p0epQK3XFwk=0wyABZY|Zt(@=>2YaN@&KX5EY%+)?J#vVHMRQOJ46gWWy zXGBJDPpEj)4=odC1>H7r9!#vY>4V)0qfy)!ut%^42G$^gHE>{)W)$}x*3FED(!`#i zOT!N=1KfrmI_Q=GE`j^dz&=M1XJFqA%tlH`YAh_a#!tG zYBioUdFg_|YS7)3O$8@flPxJF#8iQky_E74uXt}Sg?NfrX>Tt@bc$D5Z!h)J;}oy* z-d>9O6t9ZjUiRs{Dtmi5r}L^(UJIsQFjaIw8M(Iznz&z(ScLj(T@ss%NbKoH>qnhMJ7fLn zl}SU0W}Z)%;=g9;ZphVwwoeoE?@HYH<}c%@nlmdO_Pd1ns|DP}k+NXzRQrT~}E`E!CB@cxmz0j7 zX%pi#_sR*h>DF^;*8{`pwdcpukr&6&eWsPj?)Z-+vDwS?%>`+grxVE)5C)+#HwMr3FPo+dQv$CO4JrQFiah;~U zEmfPBE7TJwkJ=34T`pu#s9xvsUPJY=kvF0GYdvqm zv)B`!&7QC?dqVXti1#_{&z^7qd%}V23D03qIEX!=dT-17ULL}pP`#Gty@uzpCmhP2 za2R{SV)lgRvnL$Ro^S+v!jbF=FJMo2A$!76>NIGsJ=4EBUI>e`C!1E%99Tg!9-F&Sy`!fIZfF;gx9hs zypBEL_3R0kvnNcjC%l0@VUj)J3igDx>e?$gsa&Tu3=BOmObHz*%NlL zC;SL|!kgF=ew01o$Ji6z%%1QT_Jr%$6W+?6@HX~@x3eewID5i7*b}a2Pk1ML!VT;R zKf#`GBYVQT*c0B(o^TU;!W?_TfIVTzo^UgJ!cVd%+`^vl9`=OyvM2l$d%{n%C;SY1 z!u!}0-p`(JD|^Ci>{u;e+f6A7W4VS@wiG*b{z^J>gFFgr8?m_%M6IFR&+k zggxOG*%Lm>o^Tg?!pGPXKF*$SH+#Z8>gf_ z6F$wJ@T=?zzs8>M>+A{lvnTupd%^?k2@kR-e1<*YH`x;&Vo&%j_Jq%}Cwz`Q;q&YX zzs;WTJM0NxU{Cm6_Jl98C;T3J!k5?+exE(zVfKVCvnPCoJ>d`76TZrx@Q3UPUt>@B zBld(pW>5G!d%~ZvCp^NQ@TcqvkFqEH8GFJv*c1MoJ>i?|34g(!@GbU)zhqDNHhaQf zu_ydBd&1waC;TmY!r!qc{5^ZZKd>kKBYVPk*c1MVJ>j3(6aIxg;k)b!|H_{5Z|n*G z&Yti+_JsdnPxw#vg#Ths_;2=v|6xz~U-pE=k}x~>ikTxorka5+(GwkO+ESJpq*^<* zq9s$8ia-%l?r98Ev(bCHr(5e)jzrqT_gG1l7Mh|EN-HXgySoZYBwHJrQ!2P3R$LL& zS(4dQQWcGr#HzHSF5TXAyl53Hs;SI9i>_=ex#&GpBs*F(RW`XMQI}^g zKdxx<;a^@+9FLW&DQJC5I@QxrDNZ|4WoXDxYkY;Snkh@v>1iF4bX+`^U!Ztguki#0 ziq>Pg3OrGtX-FKKaz4*#SNVN6N>^=8wYKX)TQ+l}hSA$ypXpS|$}2ooDcP3FcC5_m zlMpK@teJ0FdXQS(mQnt3U9>aZpo6t3 zS(|Ftk~TFYThs!SmXxaLEWuDU<*1!iX1yINnlqUe9nq3ro!`IwKDE~-oAU?PK~N&u z-j=HE(D~J-COR6ksrJT9vtGxzsxb*_)q1SEj%~Pe5_di(WM(R z*?ei8Xp~OV>z-2f#EOO!Nz^oA#rRUSFX;~HQCm+mRo9T8sIGiGNatCVuIp&b7uKtE zeRme+rGt;Je?vBX694fmdKcTUL{$u)ZQ6t4PT*;G?! z<#?fdvQ$S0lAW|{Dk!dJ-@fX^IGNOA9jxGVYAvY=pO98GBx-v|IxeO;^$x3;crm{I E2a(Q&zyJUM delta 1356 zcmW-hKW-K=5XJ5B{ImDluZU8mLZYF`Awq+K1V|K!6CheD$~^-O1vj7rMNm;w({KWA zfKt#cKd++EjNg0n?48ff??3Y6;^Nhd*AG6HQl8!1J|~q&{lmreCe5mdiG&pit&*%e z>r|1hO{|&>7a2y4#?UAlg>UE@8iA|c#Ni!2-~&G4J6xg}@CI(+3a((yHkyiymBa`J zwz^_eF`9=W5GE!aF40(Z%*b#Cr*I11j$Wfz=w&nSw*eP$dGyW?G5X-Z6wEd@Z_^3F zAsiwgoMtP5$#@zZH_^C{+kP0(2<8%)%w8AgP>$YbFI<49xpX+AHg&Y@HMP@=b+F#@ z5KLk=yCm5R<6|8XE4UM5uc!h894PK@foX>u^ zfJ?Z9>tmx4(Cajy8A2g^-Uk|G0priPgg$j>0!^UF1rszKje!}Mf+<)7b1(-BumH=c zqc&=#ZYMMwfCCr@2*3pFXMV2}Le@G(_<`^`vrI(UF%i zGunZMx^!aURjqSPPbc6j`^echNW?Uo4KCmyGm#lOcsi^JJ@Nv!ux7-B6XV2TF&4}! zb(x#;A%zQD_?WUxj1!(hDyJVSuWR-<>lQ~T+8z7%lrN3=Xd+}PjByD_HXq+1AIJc diff --git a/common/src/comp/body/humanoid.rs b/common/src/comp/body/humanoid.rs index e5ac5323fd..64cc4b8257 100644 --- a/common/src/comp/body/humanoid.rs +++ b/common/src/comp/body/humanoid.rs @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { diff --git a/common/src/figure/mod.rs b/common/src/figure/mod.rs index f46a24b68c..7999fe1d52 100644 --- a/common/src/figure/mod.rs +++ b/common/src/figure/mod.rs @@ -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) -> 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) -> 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) -> Rgb) -> 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) -> 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) -> 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) -> 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 diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index a784de0d8f..7d39582123 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -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; diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 2711b834ca..e7db3c6c43 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -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::(full_specifier.as_str()).as_ref()) } -pub fn graceful_load_mat_segment(mesh_name: &str) -> MatSegment { +fn graceful_load_vox(mesh_name: &str) -> Arc { let full_specifier: String = ["voxygen.voxel.", mesh_name].concat(); - let dot_vox = match assets::load::(full_specifier.as_str()) { + match assets::load::(full_specifier.as_str()) { Ok(dot_vox) => dot_vox, Err(_) => { error!("Could not load vox file for figure: {}", full_specifier); assets::load_expect::("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) -> Mesh { Meshable::::generate_mesh(&load_segment(mesh_name), position).0 } +fn color_segment( + mat_segment: MatSegment, + skin: Rgb, + hair_color: Rgb, + 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 { @@ -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) -> 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::::generate_mesh( - &colored, + &head, Vec3::from(spec.offset) + origin_offset.map(|e| e as f32 * -1.0), ) .0