Merge branch 'imbris/char-creation' into 'master'

Segment combination for character creation

Closes #94

See merge request veloren/veloren!470
This commit is contained in:
Imbris 2019-09-04 03:25:09 +00:00
commit 1f859111c4
85 changed files with 2173 additions and 1347 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/armor/chest/grayscale.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/armor/pants/grayscale.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/figure/accessory/danari/horns.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/accessory/orc/earring.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/beard/dwarf/1.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/figure/beard/orc/1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/body/chest.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/figure/body/empty.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/voxel/figure/empty.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/danari/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/danari/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/dwarf/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/dwarf/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/elf/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/elf/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/human/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/human/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/orc/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/orc/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/undead/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/eyes/undead/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/danari/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/danari/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/dwarf/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/elf/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/elf/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/human/female_2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/human/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/orc/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/orc/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/undead/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/undead/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/danari/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/danari/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/dwarf/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/dwarf/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/elf/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/elf/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/human/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/human/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/orc/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/orc/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/undead/female.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/figure/head/undead/male.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,195 @@
({
(Human, Male): (
offset: (-7.0, -5.0, -2.25),
head: ("figure.head.human.male", (0, 1, 0)),
eyes: ("figure.eyes.human.male", (3, 8, 2)),
hair: {
Temp1: Some(("figure.hair.human.male", (1, 0, 1))),
},
beard: {
None: None,
Some: Some(("figure.beard.human.1", (4, 5, -2))),
},
accessory: {
Nothing: None,
Something: None,
},
),
(Human, Female): (
offset: (-7.0, -7.5, -3.25),
head: ("figure.head.human.female", (0, 5, 2)),
eyes: ("figure.eyes.human.female", (2, 12, 4)),
hair: {
Temp1: Some(("figure.hair.human.female_1", (1, 2, -5))),
Temp2: Some(("figure.hair.human.female_2", (1, 0, 0))),
},
beard: {
None: None,
Some: None,
},
accessory: {
Nothing: None,
Something: None,
},
),
(Orc, Male): (
offset: (-8.0, -5.0, -2.25),
head: ("figure.head.orc.male", (0, 1, 1)),
eyes: ("figure.eyes.orc.male", (0, 2, 4)),
hair: {
Temp1: Some(("figure.hair.orc.male", (4, 0, 1))),
},
beard: {
None: None,
Some: Some(("figure.beard.orc.1", (7, 9, 1))),
},
accessory: {
Nothing: None,
Something: None,
},
),
(Orc, Female): (
offset: (-8.0, -8.0, -2),
head: ("figure.head.orc.female", (0, 0, -2)),
eyes: ("figure.eyes.orc.female", (3, 9, -1)),
hair: {
Temp1: Some(("figure.hair.orc.female", (5, 1, -2))),
},
beard: {
None: None,
Some: None,
},
accessory: {
Nothing: None,
Something: None,
},
),
(Elf, Male): (
offset: (-8.0, -5.0, -2.25),
head: ("figure.head.elf.male", (0, 2, 0)),
eyes: ("figure.eyes.elf.male", (4, 9, 2)),
hair: {
Temp1: Some(("figure.hair.elf.male", (2, 1, 1))),
},
beard: {
None: None,
Some: None,
},
accessory: {
Nothing: None,
Something: None,
},
),
(Elf, Female): (
offset: (-8.0, -5.5, -2.0),
head: ("figure.head.elf.female", (0, 3, 1)),
eyes: ("figure.eyes.elf.female", (3, 10, 2)),
hair: {
Temp1: Some(("figure.hair.elf.female", (2, 1, -1))),
},
beard: {
None: None,
Some: None,
},
accessory: {
Nothing: None,
Something: None,
},
),
(Dwarf, Male): (
offset: (-6.0, -5.0, -2),
head: ("figure.head.dwarf.male", (0, 0, -1)),
eyes: ("figure.eyes.dwarf.male", (2, 7, 1)),
hair: {
},
beard: {
None: None,
Some: Some(("figure.beard.dwarf.1", (1, 3, -10))),
},
accessory: {
Nothing: None,
Something: None,
},
),
(Dwarf, Female): (
offset: (-6.0, -6.0, -2),
head: ("figure.head.dwarf.female", (0, 3, 0)),
eyes: ("figure.eyes.dwarf.female", (1, 10, 2)),
hair: {
Temp1: Some(("figure.hair.dwarf.female", (1 , 0, -7))),
},
beard: {
None: None,
Some: None,
},
accessory: {
Nothing: None,
Something: None,
},
),
(Undead, Male): (
offset: (-5.5, -5.0, -2.25),
head: ("figure.head.undead.male", (1, 1, -1)),
eyes: ("figure.eyes.undead.male", (3, 7, 3)),
hair: {
Temp1: Some(("figure.hair.undead.male", (0, 0, 0))),
},
beard: {
None: None,
Some: None,
},
accessory: {
Nothing: None,
Something: None,
},
),
(Undead, Female): (
offset: (-6.0, -5.0, -2.5),
head: ("figure.head.undead.female", (1, 1, -1)),
eyes: ("figure.eyes.undead.female", (3, 7, 2)),
hair: {
Temp1: Some(("figure.hair.undead.female", (1, 0, -1))),
},
beard: {
None: None,
Some: None,
},
accessory: {
Nothing: None,
Something: None,
},
),
(Danari, Male): (
offset: (-9.0, -5.0, -2.75),
head: ("figure.head.danari.male", (0, 1, -1)),
eyes: ("figure.eyes.danari.male", (5, 8, 1)),
hair: {
Temp1: Some(("figure.hair.danari.male", (3, 0, -1))),
},
beard: {
None: None,
Some: None,
},
accessory: {
Nothing: None,
Something: None,
},
),
(Danari, Female): (
offset: (-9.0, -7.5, -2.25),
head: ("figure.head.danari.female", (0, 7, 0)),
eyes: ("figure.eyes.danari.female", (4, 14, 2)),
hair: {
Temp1: Some(("figure.hair.danari.female", (3, 1, -1))),
},
beard: {
None: None,
Some: None,
},
accessory: {
Nothing: None,
Something: None,
},
),
// More here
})

BIN
assets/voxygen/voxel/not_found.vox (Stored with Git LFS) Normal file

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,13 +11,21 @@ pub struct Body {
pub hand: Hand,
pub foot: Foot,
pub shoulder: Shoulder,
pub hair_style: HairStyle,
pub beard: Beard,
pub eyebrows: Eyebrows,
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(),
@ -24,6 +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_style: *(&ALL_HAIR_STYLES).choose(&mut rng).unwrap(),
beard: *(&ALL_BEARDS).choose(&mut rng).unwrap(),
eyebrows: *(&ALL_EYEBROWS).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,
}
}
}
@ -46,6 +62,145 @@ pub const ALL_RACES: [Race; 6] = [
Race::Undead,
];
// Hair Colors
pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 4] = [
(198, 169, 113),
(146, 32, 32),
(199, 131, 58),
(107, 32, 60),
];
pub const DWARF_HAIR_COLORS: [(u8, u8, u8); 3] = [(126, 26, 26), (54, 46, 38), (99, 75, 49)];
pub const ELF_HAIR_COLORS: [(u8, u8, u8); 3] = [(66, 83, 113), (13, 76, 41), (189, 185, 126)];
pub const HUMAN_HAIR_COLORS: [(u8, u8, u8); 3] = [(107, 76, 51), (161, 63, 18), (64, 32, 18)];
pub const ORC_HAIR_COLORS: [(u8, u8, u8); 3] = [(66, 66, 59), (54, 30, 26), (125, 111, 51)];
pub const UNDEAD_HAIR_COLORS: [(u8, u8, u8); 3] = [(0, 131, 122), (66, 66, 59), (111, 54, 117)];
// Skin colors
pub const DANARI_SKIN_COLORS: [(u8, u8, u8); 4] = [
(104, 168, 196),
(30, 149, 201),
(57, 120, 148),
(40, 85, 105),
];
pub const DWARF_SKIN_COLORS: [(u8, u8, u8); 3] = [(215, 175, 123), (191, 125, 94), (212, 128, 89)];
pub const ELF_SKIN_COLORS: [(u8, u8, u8); 3] = [(176, 161, 181), (132, 139, 161), (138, 119, 201)];
pub const HUMAN_SKIN_COLORS: [(u8, u8, u8); 3] = [(255, 200, 159), (186, 140, 104), (87, 57, 34)];
pub const ORC_SKIN_COLORS: [(u8, u8, u8); 3] = [(77, 150, 51), (82, 117, 36), (71, 94, 42)];
pub const UNDEAD_SKIN_COLORS: [(u8, u8, u8); 3] =
[(255, 255, 255), (178, 178, 178), (145, 135, 121)];
// Eye colors
pub const DANARI_EYE_COLORS: [EyeColor; 6] = [
EyeColor::Black,
EyeColor::Blue,
EyeColor::Green,
EyeColor::Brown,
EyeColor::Red,
EyeColor::Orange,
];
pub const DWARF_EYE_COLORS: [EyeColor; 6] = [
EyeColor::Black,
EyeColor::Blue,
EyeColor::Green,
EyeColor::Brown,
EyeColor::Red,
EyeColor::Orange,
];
pub const ELF_EYE_COLORS: [EyeColor; 6] = [
EyeColor::Black,
EyeColor::Blue,
EyeColor::Green,
EyeColor::Brown,
EyeColor::Red,
EyeColor::Orange,
];
pub const HUMAN_EYE_COLORS: [EyeColor; 6] = [
EyeColor::Black,
EyeColor::Blue,
EyeColor::Green,
EyeColor::Brown,
EyeColor::Red,
EyeColor::Orange,
];
pub const ORC_EYE_COLORS: [EyeColor; 6] = [
EyeColor::Black,
EyeColor::Blue,
EyeColor::Green,
EyeColor::Brown,
EyeColor::Red,
EyeColor::Orange,
];
pub const UNDEAD_EYE_COLORS: [EyeColor; 6] = [
EyeColor::Black,
EyeColor::Blue,
EyeColor::Green,
EyeColor::Brown,
EyeColor::Red,
EyeColor::Orange,
];
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 {
Race::Danari => &DANARI_EYE_COLORS,
Race::Dwarf => &DWARF_EYE_COLORS,
Race::Elf => &ELF_EYE_COLORS,
Race::Human => &HUMAN_EYE_COLORS,
Race::Orc => &ORC_EYE_COLORS,
Race::Undead => &UNDEAD_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::Blue)
}
pub fn num_eye_colors(self) -> usize {
self.eye_colors().len()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BodyType {
Female,
@ -112,3 +267,65 @@ pub enum Shoulder {
Brown1,
}
pub const ALL_SHOULDERS: [Shoulder; 2] = [Shoulder::None, Shoulder::Brown1];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum HairStyle {
Temp1,
Temp2,
}
pub const ALL_HAIR_STYLES: [HairStyle; 2] = [HairStyle::Temp1, HairStyle::Temp2];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Eyebrows {
Yup,
}
pub const ALL_EYEBROWS: [Eyebrows; 1] = [Eyebrows::Yup];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EyeColor {
Black,
Blue,
Green,
Brown,
Red,
Orange,
}
impl EyeColor {
pub fn light_rgb(self) -> Rgb<u8> {
match self {
EyeColor::Black => Rgb::new(71, 59, 49),
EyeColor::Blue => Rgb::new(75, 158, 191),
EyeColor::Green => Rgb::new(110, 167, 113),
EyeColor::Brown => Rgb::new(73, 42, 36),
EyeColor::Red => Rgb::new(182, 0, 0),
EyeColor::Orange => Rgb::new(161, 69, 0),
}
}
pub fn dark_rgb(self) -> Rgb<u8> {
match self {
EyeColor::Black => Rgb::new(32, 32, 32),
EyeColor::Blue => Rgb::new(62, 130, 159),
EyeColor::Green => Rgb::new(81, 124, 84),
EyeColor::Brown => Rgb::new(54, 30, 26),
EyeColor::Red => Rgb::new(148, 0, 0),
EyeColor::Orange => Rgb::new(148, 64, 0),
}
}
pub fn white_rgb(self) -> Rgb<u8> {
Rgb::new(255, 255, 255)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Accessory {
Nothing,
Something,
}
pub const ALL_ACCESSORIES: [Accessory; 2] = [Accessory::Nothing, Accessory::Something];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Beard {
None,
Some,
}
pub const ALL_BEARDS: [Beard; 2] = [Beard::None, Beard::Some];

View File

@ -1,6 +1,4 @@
use specs::{Component, FlaggedStorage, NullStorage};
use specs_idvs::IDVStorage;
use vek::*;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct CanBuild;

View File

@ -2,7 +2,7 @@ use crate::vol::Vox;
use vek::*;
/// A type representing a single voxel in a figure.
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Cell {
Filled([u8; 3]),
Empty,

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,8 +1,11 @@
pub mod cell;
pub mod mat_cell;
pub use mat_cell::Material;
use self::cell::Cell;
use self::mat_cell::MatCell;
use crate::{
vol::{Vox, WriteVol},
vol::{ReadVol, SizedVol, Vox, WriteVol},
volumes::dyna::Dyna,
};
use dot_vox::DotVoxData;
@ -30,11 +33,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();
}
}
@ -44,3 +48,134 @@ impl From<&DotVoxData> for Segment {
}
}
}
impl Segment {
/// Transform cells
pub fn map(mut self, transform: impl Fn(Cell) -> Option<Cell>) -> Self {
for pos in self.iter_positions() {
if let Some(new) = transform(*self.get(pos).unwrap()) {
self.set(pos, new).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))))
}
}
// TODO: move
/// A `Dyna` builder that combines Dynas
pub struct DynaUnionizer<V: Vox>(Vec<(Dyna<V, ()>, Vec3<i32>)>);
impl<V: Vox + Copy> DynaUnionizer<V> {
pub fn new() -> Self {
DynaUnionizer(Vec::new())
}
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<(Dyna<V, ()>, Vec3<i32>)>) -> Self {
match maybe {
Some((dyna, offset)) => self.add(dyna, offset),
None => self,
}
}
pub fn unify(self) -> (Dyna<V, ()>, Vec3<i32>) {
if self.0.is_empty() {
return (Dyna::filled(Vec3::zero(), V::empty(), ()), Vec3::zero());
}
// 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 (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 = Dyna::filled(new_size, V::empty(), ());
// Copy segments into combined
let origin = min_point.map(|e| e * -1);
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();
}
}
}
(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

@ -1,6 +1,6 @@
pub const GIT_HASH: &str = include_str!(concat!(env!("OUT_DIR"), "/githash"));
use vek::{Rgb, Rgba, Vec3};
use vek::{Mat3, Rgb, Rgba, Vec3};
#[inline(always)]
pub fn srgb_to_linear(col: Rgb<f32>) -> Rgb<f32> {
@ -91,6 +91,32 @@ pub fn hsv_to_rgb(hsv: Vec3<f32>) -> Rgb<f32> {
Rgb::new(r + m, g + m, b + m)
}
/// Convert linear rgb to CIExyY
#[inline(always)]
pub fn rgb_to_xyy(rgb: Rgb<f32>) -> Vec3<f32> {
// XYZ
let xyz = Mat3::new(
0.4124, 0.3576, 0.1805, 0.2126, 0.7152, 0.0722, 0.0193, 0.1192, 0.9504,
) * Vec3::from(rgb);
let sum = xyz.sum();
Vec3::new(xyz.x / sum, xyz.y / sum, xyz.y)
}
/// Convert to CIExyY to linear rgb
#[inline(always)]
pub fn xyy_to_rgb(xyy: Vec3<f32>) -> Rgb<f32> {
let xyz = Vec3::new(
xyy.z / xyy.y * xyy.x,
xyy.z,
xyy.z / xyy.y * (1.0 - xyy.x - xyy.y),
);
Rgb::from(
Mat3::new(
3.2406, -1.5372, -0.4986, -0.9689, 1.8758, 0.0415, 0.0557, -0.2040, 1.0570,
) * xyz,
)
}
#[inline(always)]
pub fn saturate_srgb(col: Rgb<f32>, value: f32) -> Rgb<f32> {
@ -98,3 +124,13 @@ pub fn saturate_srgb(col: Rgb<f32>, value: f32) -> Rgb<f32> {
hsv.y *= 1.0 + value;
linear_to_srgb(hsv_to_rgb(hsv).map(|e| e.min(1.0).max(0.0)))
}
/// Preserves the luma of one color while changing its chromaticty to match the other
#[inline(always)]
pub fn chromify_srgb(luma: Rgb<f32>, chroma: Rgb<f32>) -> Rgb<f32> {
let l = rgb_to_xyy(srgb_to_linear(luma)).z;
let mut xyy = rgb_to_xyy(srgb_to_linear(chroma));
xyy.z = l;
linear_to_srgb(xyy_to_rgb(xyy).map(|e| e.min(1.0).max(0.0)))
}

View File

@ -36,7 +36,7 @@ use crate::{
window::Window,
};
use heaptrack::track_mem;
use log::{self, debug, error, info, warn};
use log::{self, debug, error, info};
use simplelog::{CombinedLogger, Config, TermLogger, TerminalMode, WriteLogger};
use std::{fs::File, mem, panic, str::FromStr};
@ -102,19 +102,12 @@ lazy_static! {
}
fn main() {
// Set up the global state.
// Load the settings
let settings = Settings::load();
let audio = if settings.audio.audio_on {
AudioFrontend::new()
} else {
AudioFrontend::no_audio()
};
let mut global_state = GlobalState {
audio,
window: Window::new(&settings).expect("Failed to create window!"),
settings,
};
// Save settings to add new fields or create the file if it is not already there
if let Err(err) = settings.save_to_file() {
panic!("Failed to save settings: {:?}", err);
}
// Initialize logging.
let term_log_level = std::env::var_os("VOXYGEN_LOG")
@ -126,24 +119,13 @@ fn main() {
WriteLogger::new(
log::LevelFilter::Info,
Config::default(),
File::create(&global_state.settings.log.file).unwrap(),
File::create(&settings.log.file).unwrap(),
),
])
.unwrap();
// Initialize discord. (lazy_static initalise lazily...)
#[cfg(feature = "discord")]
{
match DISCORD_INSTANCE.lock() {
Ok(_disc) => {
//great
}
Err(e) => log::error!("Couldn't init discord: {}", e),
}
}
// Set up panic handler to relay swish panic messages to the user
let settings_clone = global_state.settings.clone();
let settings_clone = settings.clone();
let default_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
let panic_info_payload = panic_info.payload();
@ -204,6 +186,30 @@ fn main() {
default_hook(panic_info);
}));
// Set up the global state.
let audio = if settings.audio.audio_on {
AudioFrontend::new()
} else {
AudioFrontend::no_audio()
};
let mut global_state = GlobalState {
audio,
window: Window::new(&settings).expect("Failed to create window!"),
settings,
};
// Initialize discord. (lazy_static initalise lazily...)
#[cfg(feature = "discord")]
{
match DISCORD_INSTANCE.lock() {
Ok(_disc) => {
//great
}
Err(e) => log::error!("Couldn't init discord: {}", e),
}
}
match global_state.audio.model.get_genre() {
Genre::Bgm => {
global_state.settings.audio.audio_device =
@ -290,8 +296,6 @@ fn main() {
}
}
// Save settings to add new fields or create the file if it is not already there
if let Err(err) = global_state.settings.save_to_file() {
warn!("Failed to save settings: {:?}", err);
}
// Save any unsaved changes to settings
global_state.settings.save_to_file_warn();
}

View File

@ -45,8 +45,10 @@ impl PlayState for CharSelectionState {
Event::Ui(event) => {
self.char_selection_ui.handle_event(event);
}
// Ignore all other events.
_ => {}
// Pass all other events to the scene
event => {
self.scene.handle_input_event(event);
} // TODO: Do something if the event wasn't handled?
}
}

View File

@ -10,8 +10,9 @@ use crate::{
},
scene::{
camera::{Camera, CameraMode},
figure::{FigureModelCache, FigureState},
figure::{load_mesh, FigureModelCache, FigureState},
},
window::Event,
};
use client::Client;
use common::{
@ -69,7 +70,7 @@ impl Scene {
figure_state: FigureState::new(renderer, CharacterSkeleton::new()),
backdrop_model: renderer
.create_model(&FigureModelCache::load_mesh(
.create_model(&load_mesh(
"fixture.selection_bg",
Vec3::new(-55.0, -49.5, -2.0),
))
@ -82,6 +83,21 @@ impl Scene {
&self.globals
}
/// Handle an incoming user input event (e.g.: cursor moved, key pressed, window closed).
///
/// If the event is handled, return true.
pub fn handle_input_event(&mut self, event: Event) -> bool {
match event {
// When the window is resized, change the camera's aspect ratio
Event::Resize(dims) => {
self.camera.set_aspect_ratio(dims.x as f32 / dims.y as f32);
true
}
// All other events are unhandled
_ => false,
}
}
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client, body: humanoid::Body) {
self.camera.set_focus_pos(Vec3::unit_z() * 2.0);
self.camera.update(client.state().get_time());

View File

@ -13,7 +13,7 @@ use conrod_core::{
color,
color::TRANSPARENT,
widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Scrollbar, Text, TextBox},
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget,
};
widget_ids! {
@ -86,10 +86,13 @@ widget_ids! {
eyebrows_slider,
eyebrows_text,
beard_slider,
beard_slider_2,
beard_text,
accessories_slider,
accessories_text,
chest_slider,
chest_text,
pants_slider,
pants_text,
// Buttons
enter_world_button,
@ -132,8 +135,6 @@ widget_ids! {
undead,
elf,
danari,
// Body Features
chest_slider,
}
}
@ -838,203 +839,134 @@ impl CharSelectionUi {
.set(self.ids.axe_grey, ui_widgets);*/
// Sliders
let (metamorph, slider_indicator, slider_range) = (
self.fonts.metamorph,
self.imgs.slider_indicator,
self.imgs.slider_range,
);
let char_slider = move |prev_id,
text,
text_id,
max,
selected_val,
slider_id,
ui_widgets: &mut UiCell| {
Text::new(text)
.down_from(prev_id, 22.0)
.align_middle_x_of(prev_id)
.font_size(18)
.font_id(metamorph)
.color(TEXT_COLOR)
.set(text_id, ui_widgets);
ImageSlider::discrete(selected_val, 0, max, slider_indicator, slider_range)
.w_h(208.0, 22.0)
.down_from(text_id, 8.0)
.align_middle_x()
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(slider_id, ui_widgets)
};
// Hair Style
Text::new("Hair Style")
.mid_bottom_with_margin_on(self.ids.creation_buttons_alignment_2, -40.0)
.font_size(18)
.font_id(self.fonts.metamorph)
.color(TEXT_COLOR)
.set(self.ids.hairstyle_text, ui_widgets);
let current_chest = self.character_body.chest;
if let Some(new_val) = ImageSlider::discrete(
humanoid::ALL_CHESTS
let current_hair_style = self.character_body.hair_style;
if let Some(new_val) = char_slider(
self.ids.creation_buttons_alignment_2,
"Hair Style",
self.ids.hairstyle_text,
humanoid::ALL_HAIR_STYLES.len() - 1,
humanoid::ALL_HAIR_STYLES
.iter()
.position(|&c| c == current_chest)
.position(|&c| c == current_hair_style)
.unwrap_or(0),
0,
humanoid::ALL_CHESTS.len() - 1,
self.imgs.slider_indicator,
self.imgs.slider_range,
)
.w_h(208.0, 22.0)
.mid_bottom_with_margin_on(self.ids.hairstyle_text, -30.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(self.ids.hairstyle_slider, ui_widgets)
{
self.character_body.chest = humanoid::ALL_CHESTS[new_val];
self.ids.hairstyle_slider,
ui_widgets,
) {
self.character_body.hair_style = humanoid::ALL_HAIR_STYLES[new_val];
}
// Hair Color
Text::new("Hair Color")
.mid_bottom_with_margin_on(self.ids.hairstyle_slider, -40.0)
.font_size(18)
.font_id(self.fonts.metamorph)
.color(TEXT_COLOR)
.set(self.ids.haircolor_text, ui_widgets);
let current_chest = self.character_body.chest;
if let Some(new_val) = ImageSlider::discrete(
humanoid::ALL_CHESTS
.iter()
.position(|&c| c == current_chest)
.unwrap_or(0),
0,
humanoid::ALL_CHESTS.len() - 1,
self.imgs.slider_indicator,
self.imgs.slider_range,
)
.w_h(208.0, 22.0)
.mid_bottom_with_margin_on(self.ids.haircolor_text, -30.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(self.ids.haircolor_slider, ui_widgets)
{
self.character_body.chest = humanoid::ALL_CHESTS[new_val];
if let Some(new_val) = char_slider(
self.ids.hairstyle_slider,
"Hair Color",
self.ids.haircolor_text,
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 = new_val as u8;
}
// Skin
Text::new("Skin")
.mid_bottom_with_margin_on(self.ids.haircolor_slider, -40.0)
.font_size(18)
.font_id(self.fonts.metamorph)
.color(TEXT_COLOR)
.set(self.ids.skin_text, ui_widgets);
let current_chest = self.character_body.chest;
if let Some(new_val) = ImageSlider::discrete(
humanoid::ALL_CHESTS
.iter()
.position(|&c| c == current_chest)
.unwrap_or(0),
0,
humanoid::ALL_CHESTS.len() - 1,
self.imgs.slider_indicator,
self.imgs.slider_range,
)
.w_h(208.0, 22.0)
.mid_bottom_with_margin_on(self.ids.skin_text, -30.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(self.ids.skin_slider, ui_widgets)
{
self.character_body.chest = humanoid::ALL_CHESTS[new_val];
if let Some(new_val) = char_slider(
self.ids.haircolor_slider,
"Skin",
self.ids.skin_text,
self.character_body.race.num_skin_colors() - 1,
self.character_body.skin as usize,
self.ids.skin_slider,
ui_widgets,
) {
self.character_body.skin = new_val as u8;
}
// EyeBrows
Text::new("Eyebrows")
.mid_bottom_with_margin_on(self.ids.skin_slider, -40.0)
.font_size(18)
.font_id(self.fonts.metamorph)
.color(TEXT_COLOR)
.set(self.ids.eyebrows_text, ui_widgets);
let current_chest = self.character_body.chest;
if let Some(new_val) = ImageSlider::discrete(
humanoid::ALL_CHESTS
// Eyebrows
let current_eyebrows = self.character_body.eyebrows;
if let Some(new_val) = char_slider(
self.ids.skin_slider,
"Eyebrows",
self.ids.eyebrows_text,
humanoid::ALL_EYEBROWS.len() - 1,
humanoid::ALL_EYEBROWS
.iter()
.position(|&c| c == current_chest)
.position(|&c| c == current_eyebrows)
.unwrap_or(0),
0,
humanoid::ALL_CHESTS.len() - 1,
self.imgs.slider_indicator,
self.imgs.slider_range,
)
.w_h(208.0, 22.0)
.mid_bottom_with_margin_on(self.ids.eyebrows_text, -30.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(self.ids.eyebrows_slider, ui_widgets)
{
self.character_body.chest = humanoid::ALL_CHESTS[new_val];
self.ids.eyebrows_slider,
ui_widgets,
) {
self.character_body.eyebrows = humanoid::ALL_EYEBROWS[new_val];
}
// EyeColor
Text::new("Eye Color")
.mid_bottom_with_margin_on(self.ids.eyebrows_slider, -40.0)
.font_size(18)
.font_id(self.fonts.metamorph)
.color(TEXT_COLOR)
.set(self.ids.eyecolor_text, ui_widgets);
let current_chest = self.character_body.chest;
if let Some(new_val) = ImageSlider::discrete(
humanoid::ALL_CHESTS
.iter()
.position(|&c| c == current_chest)
.unwrap_or(0),
0,
humanoid::ALL_CHESTS.len() - 1,
self.imgs.slider_indicator,
self.imgs.slider_range,
)
.w_h(208.0, 22.0)
.mid_bottom_with_margin_on(self.ids.eyecolor_text, -30.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(self.ids.eyecolor_slider, ui_widgets)
{
self.character_body.chest = humanoid::ALL_CHESTS[new_val];
if let Some(new_val) = char_slider(
self.ids.eyebrows_slider,
"Eye Color",
self.ids.eyecolor_text,
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 = new_val as u8;
}
// Accessories
Text::new("Accessories")
.mid_bottom_with_margin_on(self.ids.eyecolor_slider, -40.0)
.font_size(18)
.font_id(self.fonts.metamorph)
.color(TEXT_COLOR)
.set(self.ids.accessories_text, ui_widgets);
let current_chest = self.character_body.chest;
if let Some(new_val) = ImageSlider::discrete(
humanoid::ALL_CHESTS
let current_accessory = self.character_body.accessory;
if let Some(new_val) = char_slider(
self.ids.eyecolor_slider,
"Accessories",
self.ids.accessories_text,
humanoid::ALL_ACCESSORIES.len() - 1,
humanoid::ALL_ACCESSORIES
.iter()
.position(|&c| c == current_chest)
.position(|&c| c == current_accessory)
.unwrap_or(0),
0,
humanoid::ALL_CHESTS.len() - 1,
self.imgs.slider_indicator,
self.imgs.slider_range,
)
.w_h(208.0, 22.0)
.mid_bottom_with_margin_on(self.ids.accessories_text, -30.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(self.ids.accessories_slider, ui_widgets)
{
self.character_body.chest = humanoid::ALL_CHESTS[new_val];
self.ids.accessories_slider,
ui_widgets,
) {
self.character_body.accessory = humanoid::ALL_ACCESSORIES[new_val];
}
// Beard
if let humanoid::BodyType::Male = self.character_body.body_type {
Text::new("Beard")
.mid_bottom_with_margin_on(self.ids.accessories_slider, -40.0)
.font_size(18)
.font_id(self.fonts.metamorph)
.color(TEXT_COLOR)
.set(self.ids.beard_text, ui_widgets);
if let Some(new_val) = ImageSlider::discrete(
humanoid::ALL_CHESTS
let current_beard = self.character_body.beard;
if let Some(new_val) = char_slider(
self.ids.accessories_slider,
"Beard",
self.ids.beard_text,
humanoid::ALL_BEARDS.len() - 1,
humanoid::ALL_BEARDS
.iter()
.position(|&c| c == current_chest)
.position(|&c| c == current_beard)
.unwrap_or(0),
0,
humanoid::ALL_CHESTS.len() - 1,
self.imgs.slider_indicator,
self.imgs.slider_range,
)
.w_h(208.0, 22.0)
.mid_bottom_with_margin_on(self.ids.beard_text, -30.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(self.ids.beard_slider, ui_widgets)
{
self.character_body.chest = humanoid::ALL_CHESTS[new_val];
self.ids.beard_slider,
ui_widgets,
) {
self.character_body.beard = humanoid::ALL_BEARDS[new_val];
}
} else {
Text::new("Beard")
@ -1043,22 +975,47 @@ impl CharSelectionUi {
.font_id(self.fonts.metamorph)
.color(TEXT_COLOR_2)
.set(self.ids.beard_text, ui_widgets);
if let Some(_val) = ImageSlider::continuous(
5.0,
0.0,
10.0,
self.imgs.nothing,
self.imgs.slider_range,
)
.w_h(208.0, 22.0)
.mid_bottom_with_margin_on(self.ids.beard_text, -30.0)
.track_breadth(12.0)
.slider_length(10.0)
.track_color(Color::Rgba(1.0, 1.0, 1.0, 0.2))
.slider_color(Color::Rgba(1.0, 1.0, 1.0, 0.2))
.pad_track((5.0, 5.0))
.set(self.ids.beard_slider_2, ui_widgets)
{}
ImageSlider::discrete(5, 0, 10, self.imgs.nothing, self.imgs.slider_range)
.w_h(208.0, 22.0)
.mid_bottom_with_margin_on(self.ids.beard_text, -30.0)
.track_breadth(12.0)
.slider_length(10.0)
.track_color(Color::Rgba(1.0, 1.0, 1.0, 0.2))
.slider_color(Color::Rgba(1.0, 1.0, 1.0, 0.2))
.pad_track((5.0, 5.0))
.set(self.ids.beard_slider, ui_widgets);
}
// Chest
let current_chest = self.character_body.chest;
if let Some(new_val) = char_slider(
self.ids.beard_slider,
"Chest",
self.ids.chest_text,
humanoid::ALL_CHESTS.len() - 1,
humanoid::ALL_CHESTS
.iter()
.position(|&c| c == current_chest)
.unwrap_or(0),
self.ids.chest_slider,
ui_widgets,
) {
self.character_body.chest = humanoid::ALL_CHESTS[new_val];
}
// Pants
let current_pants = self.character_body.pants;
if let Some(new_val) = char_slider(
self.ids.chest_slider,
"Pants",
self.ids.pants_text,
humanoid::ALL_PANTS.len() - 1,
humanoid::ALL_PANTS
.iter()
.position(|&c| c == current_pants)
.unwrap_or(0),
self.ids.pants_slider,
ui_widgets,
) {
self.character_body.pants = humanoid::ALL_PANTS[new_val];
}
} // Char Creation fin

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,177 @@
use super::load::*;
use crate::{
anim::SkeletonAttr,
render::{FigurePipeline, Mesh, Model, Renderer},
};
use common::{
assets::watch::ReloadIndicator,
comp::{Body, Equipment},
};
use hashbrown::HashMap;
#[derive(PartialEq, Eq, Hash, Clone)]
enum FigureKey {
Simple(Body),
Complex(Body, Option<Equipment>),
}
pub struct FigureModelCache {
models: HashMap<FigureKey, ((Model<FigurePipeline>, SkeletonAttr), u64)>,
manifest_indicator: ReloadIndicator,
}
impl FigureModelCache {
pub fn new() -> Self {
Self {
models: HashMap::new(),
manifest_indicator: ReloadIndicator::new(),
}
}
pub fn get_or_create_model(
&mut self,
renderer: &mut Renderer,
body: Body,
equipment: Option<&Equipment>,
tick: u64,
) -> &(Model<FigurePipeline>, SkeletonAttr) {
let key = if equipment.is_some() {
FigureKey::Complex(body, equipment.cloned())
} else {
FigureKey::Simple(body)
};
match self.models.get_mut(&key) {
Some((_model, last_used)) => {
*last_used = tick;
}
None => {
self.models.insert(
key.clone(),
(
{
let humanoid_head_spec =
HumHeadSpec::load_watched(&mut self.manifest_indicator);
let bone_meshes = match body {
Body::Humanoid(body) => [
Some(humanoid_head_spec.mesh_head(
body.race,
body.body_type,
body.hair_color,
body.hair_style,
body.beard,
body.eye_color,
body.skin,
body.eyebrows,
body.accessory,
)),
Some(mesh_chest(body.chest)),
Some(mesh_belt(body.belt)),
Some(mesh_pants(body.pants)),
Some(mesh_left_hand(body.hand)),
Some(mesh_right_hand(body.hand)),
Some(mesh_left_foot(body.foot)),
Some(mesh_right_foot(body.foot)),
Some(mesh_main(equipment.and_then(|e| e.main.as_ref()))),
Some(mesh_left_shoulder(body.shoulder)),
Some(mesh_right_shoulder(body.shoulder)),
Some(mesh_draw()),
None,
None,
None,
None,
],
Body::Quadruped(body) => [
Some(mesh_pig_head(body.head)),
Some(mesh_pig_chest(body.chest)),
Some(mesh_pig_leg_lf(body.leg_l)),
Some(mesh_pig_leg_rf(body.leg_r)),
Some(mesh_pig_leg_lb(body.leg_l)),
Some(mesh_pig_leg_rb(body.leg_r)),
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
],
Body::QuadrupedMedium(body) => [
Some(mesh_wolf_head_upper(body.head_upper)),
Some(mesh_wolf_jaw(body.jaw)),
Some(mesh_wolf_head_lower(body.head_lower)),
Some(mesh_wolf_tail(body.tail)),
Some(mesh_wolf_torso_back(body.torso_back)),
Some(mesh_wolf_torso_mid(body.torso_mid)),
Some(mesh_wolf_ears(body.ears)),
Some(mesh_wolf_foot_lf(body.foot_lf)),
Some(mesh_wolf_foot_rf(body.foot_rf)),
Some(mesh_wolf_foot_lb(body.foot_lb)),
Some(mesh_wolf_foot_rb(body.foot_rb)),
None,
None,
None,
None,
None,
],
Body::Object(object) => [
Some(mesh_object(object)),
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
],
};
let skeleton_attr = match body {
Body::Humanoid(body) => SkeletonAttr::from(&body),
_ => SkeletonAttr::default(),
};
let mut mesh = Mesh::new();
bone_meshes
.iter()
.enumerate()
.filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm)))
.for_each(|(i, bone_mesh)| {
mesh.push_mesh_map(bone_mesh, |vert| {
vert.with_bone_idx(i as u8)
})
});
(renderer.create_model(&mesh).unwrap(), skeleton_attr)
},
tick,
),
);
}
}
&self.models[&key].0
}
pub fn clean(&mut self, tick: u64) {
// Check for reloaded manifests
// TODO: maybe do this in a different function, maintain?
if self.manifest_indicator.reloaded() {
self.models.clear();
}
// TODO: Don't hard-code this.
self.models
.retain(|_, (_, last_used)| *last_used + 60 > tick);
}
}

View File

@ -0,0 +1,549 @@
use crate::{
mesh::Meshable,
render::{FigurePipeline, Mesh},
};
use common::{
assets::{self, watch::ReloadIndicator, Asset},
comp::{
humanoid::{
Accessory, Beard, Belt, BodyType, Chest, EyeColor, Eyebrows, Foot, HairStyle, Hand,
Pants, Race, Shoulder,
},
item::Tool,
object, quadruped, quadruped_medium, Item,
},
figure::{DynaUnionizer, MatSegment, Material, Segment},
};
use dot_vox::DotVoxData;
use hashbrown::HashMap;
use log::{error, warn};
use serde_derive::{Deserialize, Serialize};
use std::{fs::File, io::BufReader, sync::Arc};
use vek::*;
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())
}
fn graceful_load_vox(mesh_name: &str) -> Arc<DotVoxData> {
let full_specifier: String = ["voxygen.voxel.", mesh_name].concat();
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")
}
}
}
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(),
})
}
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
}
})
}
#[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
#[derive(Serialize, Deserialize)]
struct HumHeadSubSpec {
offset: [f32; 3], // Should be relative to initial origin
head: VoxSpec,
eyes: VoxSpec,
hair: HashMap<HairStyle, Option<VoxSpec>>,
beard: HashMap<Beard, Option<VoxSpec>>,
accessory: HashMap<Accessory, Option<VoxSpec>>,
}
#[derive(Serialize, Deserialize)]
pub struct HumHeadSpec(HashMap<(Race, BodyType), HumHeadSubSpec>);
impl Asset for HumHeadSpec {
const ENDINGS: &'static [&'static str] = &["ron"];
fn parse(buf_reader: BufReader<File>) -> Result<Self, assets::Error> {
Ok(ron::de::from_reader(buf_reader).expect("Error parsing humanoid head spec"))
}
}
impl HumHeadSpec {
pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc<Self> {
assets::load_watched::<Self>("voxygen.voxel.humanoid_head_manifest", indicator).unwrap()
}
pub fn mesh_head(
&self,
race: Race,
body_type: BodyType,
hair_color: u8,
hair_style: HairStyle,
beard: Beard,
eye_color: u8,
skin: u8,
_eyebrows: Eyebrows,
accessory: Accessory,
) -> Mesh<FigurePipeline> {
let spec = match self.0.get(&(race, body_type)) {
Some(spec) => spec,
None => {
error!(
"No head specification exists for the combination of {:?} and {:?}",
race, body_type
);
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);
// 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((
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);
None
}
};
let beard = match spec.beard.get(&beard) {
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);
None
}
};
let accessory = match spec.accessory.get(&accessory) {
Some(Some(spec)) => Some((graceful_load_segment(&spec.0), Vec3::from(spec.1))),
Some(None) => None,
None => {
warn!("No specification for this accessory: {:?}", accessory);
None
}
};
let (head, origin_offset) = DynaUnionizer::new()
.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();
Meshable::<FigurePipeline, FigurePipeline>::generate_mesh(
&head,
Vec3::from(spec.offset) + origin_offset.map(|e| e as f32 * -1.0),
)
.0
}
}
pub fn mesh_chest(chest: Chest) -> Mesh<FigurePipeline> {
let color = match chest {
Chest::Blue => (28, 66, 109),
Chest::Brown => (54, 30, 26),
Chest::Dark => (24, 19, 17),
Chest::Green => (49, 95, 59),
Chest::Orange => (148, 52, 33),
};
let bare_chest = graceful_load_segment("figure.body.chest");
let chest_armor = graceful_load_segment("armor.chest.grayscale");
let chest = DynaUnionizer::new()
.add(bare_chest, Vec3::new(0, 0, 0))
.add(
recolor_greys(chest_armor, Rgb::from(color)),
Vec3::new(0, 0, 0),
)
.unify()
.0;
Meshable::<FigurePipeline, FigurePipeline>::generate_mesh(&chest, Vec3::new(-6.0, -3.5, 0.0)).0
}
pub fn mesh_belt(belt: Belt) -> Mesh<FigurePipeline> {
load_mesh(
match belt {
//Belt::Default => "figure/body/belt_male",
Belt::Dark => "armor.belt.belt_dark",
},
Vec3::new(-5.0, -3.5, 0.0),
)
}
pub fn mesh_pants(pants: Pants) -> Mesh<FigurePipeline> {
let color = match pants {
Pants::Blue => (28, 66, 109),
Pants::Brown => (54, 30, 26),
Pants::Dark => (24, 19, 17),
Pants::Green => (49, 95, 59),
Pants::Orange => (148, 52, 33),
};
let pants_segment = recolor_greys(
graceful_load_segment("armor.pants.grayscale"),
Rgb::from(color),
);
Meshable::<FigurePipeline, FigurePipeline>::generate_mesh(
&pants_segment,
Vec3::new(-5.0, -3.5, 0.0),
)
.0
}
pub fn mesh_left_hand(hand: Hand) -> Mesh<FigurePipeline> {
load_mesh(
match hand {
Hand::Default => "figure.body.hand",
},
Vec3::new(-2.0, -2.5, -2.0),
)
}
pub fn mesh_right_hand(hand: Hand) -> Mesh<FigurePipeline> {
load_mesh(
match hand {
Hand::Default => "figure.body.hand",
},
Vec3::new(-2.0, -2.5, -2.0),
)
}
pub fn mesh_left_foot(foot: Foot) -> Mesh<FigurePipeline> {
load_mesh(
match foot {
Foot::Dark => "armor.foot.foot_dark",
},
Vec3::new(-2.5, -3.5, -9.0),
)
}
pub fn mesh_right_foot(foot: Foot) -> Mesh<FigurePipeline> {
load_mesh(
match foot {
Foot::Dark => "armor.foot.foot_dark",
},
Vec3::new(-2.5, -3.5, -9.0),
)
}
pub fn mesh_main(item: Option<&Item>) -> Mesh<FigurePipeline> {
if let Some(item) = item {
let (name, offset) = match item {
Item::Tool { kind, .. } => match kind {
Tool::Sword => ("weapon.sword.rusty_2h", Vec3::new(-1.5, -6.5, -4.0)),
Tool::Axe => ("weapon.axe.rusty_2h", Vec3::new(-1.5, -5.0, -4.0)),
Tool::Hammer => ("weapon.hammer.rusty_2h", Vec3::new(-2.5, -5.5, -4.0)),
Tool::Daggers => ("weapon.hammer.rusty_2h", Vec3::new(-2.5, -5.5, -4.0)),
Tool::SwordShield => ("weapon.axe.rusty_2h", Vec3::new(-2.5, -6.5, -2.0)),
Tool::Bow => ("weapon.hammer.rusty_2h", Vec3::new(-2.5, -5.5, -4.0)),
Tool::Staff => ("weapon.axe.rusty_2h", Vec3::new(-2.5, -6.5, -2.0)),
},
Item::Debug(_) => ("weapon.debug_wand", Vec3::new(-1.5, -9.5, -4.0)),
_ => return Mesh::new(),
};
load_mesh(name, offset)
} else {
Mesh::new()
}
}
pub fn mesh_left_shoulder(shoulder: Shoulder) -> Mesh<FigurePipeline> {
load_mesh(
match shoulder {
Shoulder::None => return Mesh::new(),
Shoulder::Brown1 => "armor.shoulder.shoulder_l_brown",
},
Vec3::new(-2.5, -3.5, -1.5),
)
}
pub fn mesh_right_shoulder(shoulder: Shoulder) -> Mesh<FigurePipeline> {
load_mesh(
match shoulder {
Shoulder::None => return Mesh::new(),
Shoulder::Brown1 => "armor.shoulder.shoulder_r_brown",
},
Vec3::new(-2.5, -3.5, -1.5),
)
}
// TODO: Inventory
pub fn mesh_draw() -> Mesh<FigurePipeline> {
load_mesh("object.glider", Vec3::new(-26.0, -26.0, -5.0))
}
//pub fn mesh_right_equip(hand: Hand) -> Mesh<FigurePipeline> {
// load_mesh(
// match hand {
// Hand::Default => "figure/body/hand",
// },
// Vec3::new(-2.0, -2.5, -5.0),
// )
//}
/////////
pub fn mesh_pig_head(head: quadruped::Head) -> Mesh<FigurePipeline> {
load_mesh(
match head {
quadruped::Head::Default => "npc.pig_purple.pig_head",
},
Vec3::new(-6.0, 4.5, 3.0),
)
}
pub fn mesh_pig_chest(chest: quadruped::Chest) -> Mesh<FigurePipeline> {
load_mesh(
match chest {
quadruped::Chest::Default => "npc.pig_purple.pig_chest",
},
Vec3::new(-5.0, 4.5, 0.0),
)
}
pub fn mesh_pig_leg_lf(leg_l: quadruped::LegL) -> Mesh<FigurePipeline> {
load_mesh(
match leg_l {
quadruped::LegL::Default => "npc.pig_purple.pig_leg_l",
},
Vec3::new(0.0, -1.0, -1.5),
)
}
pub fn mesh_pig_leg_rf(leg_r: quadruped::LegR) -> Mesh<FigurePipeline> {
load_mesh(
match leg_r {
quadruped::LegR::Default => "npc.pig_purple.pig_leg_r",
},
Vec3::new(0.0, -1.0, -1.5),
)
}
pub fn mesh_pig_leg_lb(leg_l: quadruped::LegL) -> Mesh<FigurePipeline> {
load_mesh(
match leg_l {
quadruped::LegL::Default => "npc.pig_purple.pig_leg_l",
},
Vec3::new(0.0, -1.0, -1.5),
)
}
pub fn mesh_pig_leg_rb(leg_r: quadruped::LegR) -> Mesh<FigurePipeline> {
load_mesh(
match leg_r {
quadruped::LegR::Default => "npc.pig_purple.pig_leg_r",
},
Vec3::new(0.0, -1.0, -1.5),
)
}
//////
pub fn mesh_wolf_head_upper(upper_head: quadruped_medium::HeadUpper) -> Mesh<FigurePipeline> {
load_mesh(
match upper_head {
quadruped_medium::HeadUpper::Default => "npc.wolf.wolf_head_upper",
},
Vec3::new(-7.0, -6.0, -5.5),
)
}
pub fn mesh_wolf_jaw(jaw: quadruped_medium::Jaw) -> Mesh<FigurePipeline> {
load_mesh(
match jaw {
quadruped_medium::Jaw::Default => "npc.wolf.wolf_jaw",
},
Vec3::new(-3.0, -3.0, -2.5),
)
}
pub fn mesh_wolf_head_lower(head_lower: quadruped_medium::HeadLower) -> Mesh<FigurePipeline> {
load_mesh(
match head_lower {
quadruped_medium::HeadLower::Default => "npc.wolf.wolf_head_lower",
},
Vec3::new(-7.0, -6.0, -5.5),
)
}
pub fn mesh_wolf_tail(tail: quadruped_medium::Tail) -> Mesh<FigurePipeline> {
load_mesh(
match tail {
quadruped_medium::Tail::Default => "npc.wolf.wolf_tail",
},
Vec3::new(-2.0, -12.0, -5.0),
)
}
pub fn mesh_wolf_torso_back(torso_back: quadruped_medium::TorsoBack) -> Mesh<FigurePipeline> {
load_mesh(
match torso_back {
quadruped_medium::TorsoBack::Default => "npc.wolf.wolf_torso_back",
},
Vec3::new(-7.0, -6.0, -6.0),
)
}
pub fn mesh_wolf_torso_mid(torso_mid: quadruped_medium::TorsoMid) -> Mesh<FigurePipeline> {
load_mesh(
match torso_mid {
quadruped_medium::TorsoMid::Default => "npc.wolf.wolf_torso_mid",
},
Vec3::new(-8.0, -5.5, -6.0),
)
}
pub fn mesh_wolf_ears(ears: quadruped_medium::Ears) -> Mesh<FigurePipeline> {
load_mesh(
match ears {
quadruped_medium::Ears::Default => "npc.wolf.wolf_ears",
},
Vec3::new(-4.0, -1.0, -1.0),
)
}
pub fn mesh_wolf_foot_lf(foot_lf: quadruped_medium::FootLF) -> Mesh<FigurePipeline> {
load_mesh(
match foot_lf {
quadruped_medium::FootLF::Default => "npc.wolf.wolf_foot_lf",
},
Vec3::new(-2.5, -4.0, -2.5),
)
}
pub fn mesh_wolf_foot_rf(foot_rf: quadruped_medium::FootRF) -> Mesh<FigurePipeline> {
load_mesh(
match foot_rf {
quadruped_medium::FootRF::Default => "npc.wolf.wolf_foot_rf",
},
Vec3::new(-2.5, -4.0, -2.5),
)
}
pub fn mesh_wolf_foot_lb(foot_lb: quadruped_medium::FootLB) -> Mesh<FigurePipeline> {
load_mesh(
match foot_lb {
quadruped_medium::FootLB::Default => "npc.wolf.wolf_foot_lb",
},
Vec3::new(-2.5, -4.0, -2.5),
)
}
pub fn mesh_wolf_foot_rb(foot_rb: quadruped_medium::FootRB) -> Mesh<FigurePipeline> {
load_mesh(
match foot_rb {
quadruped_medium::FootRB::Default => "npc.wolf.wolf_foot_rb",
},
Vec3::new(-2.5, -4.0, -2.5),
)
}
pub fn mesh_object(obj: object::Body) -> Mesh<FigurePipeline> {
use object::Body;
let (name, offset) = match obj {
Body::Bomb => ("object.bomb", Vec3::new(-5.5, -5.5, 0.0)),
Body::Scarecrow => ("object.scarecrow", Vec3::new(-9.5, -4.0, 0.0)),
Body::Cauldron => ("object.cauldron", Vec3::new(-10.0, -10.0, 0.0)),
Body::ChestVines => ("object.chest_vines", Vec3::new(-7.5, -6.0, 0.0)),
Body::Chest => ("object.chest", Vec3::new(-7.5, -6.0, 0.0)),
Body::ChestDark => ("object.chest_dark", Vec3::new(-7.5, -6.0, 0.0)),
Body::ChestDemon => ("object.chest_demon", Vec3::new(-7.5, -6.0, 0.0)),
Body::ChestGold => ("object.chest_gold", Vec3::new(-7.5, -6.0, 0.0)),
Body::ChestLight => ("object.chest_light", Vec3::new(-7.5, -6.0, 0.0)),
Body::ChestOpen => ("object.chest_open", Vec3::new(-7.5, -6.0, 0.0)),
Body::ChestSkull => ("object.chest_skull", Vec3::new(-7.5, -6.0, 0.0)),
Body::Pumpkin => ("object.pumpkin", Vec3::new(-5.5, -4.0, 0.0)),
Body::Pumpkin2 => ("object.pumpkin_2", Vec3::new(-5.0, -4.0, 0.0)),
Body::Pumpkin3 => ("object.pumpkin_3", Vec3::new(-5.0, -4.0, 0.0)),
Body::Pumpkin4 => ("object.pumpkin_4", Vec3::new(-5.0, -4.0, 0.0)),
Body::Pumpkin5 => ("object.pumpkin_5", Vec3::new(-4.0, -5.0, 0.0)),
Body::Campfire => ("object.campfire", Vec3::new(-9.0, -10.0, 0.0)),
Body::LanternGround => ("object.lantern_ground", Vec3::new(-3.5, -3.5, 0.0)),
Body::LanternGroundOpen => ("object.lantern_ground_open", Vec3::new(-3.5, -3.5, 0.0)),
Body::LanternStanding => ("object.lantern_standing", Vec3::new(-7.5, -3.5, 0.0)),
Body::LanternStanding2 => ("object.lantern_standing_2", Vec3::new(-11.5, -3.5, 0.0)),
Body::PotionRed => ("object.potion_red", Vec3::new(-2.0, -2.0, 0.0)),
Body::PotionBlue => ("object.potion_blue", Vec3::new(-2.0, -2.0, 0.0)),
Body::PotionGreen => ("object.potion_green", Vec3::new(-2.0, -2.0, 0.0)),
Body::Crate => ("object.crate", Vec3::new(-7.0, -7.0, 0.0)),
Body::Tent => ("object.tent", Vec3::new(-18.5, -19.5, 0.0)),
Body::WindowSpooky => ("object.window_spooky", Vec3::new(-15.0, -1.5, -1.0)),
Body::DoorSpooky => ("object.door_spooky", Vec3::new(-15.0, -4.5, 0.0)),
Body::Table => ("object.table", Vec3::new(-12.0, -8.0, 0.0)),
Body::Table2 => ("object.table_2", Vec3::new(-8.0, -8.0, 0.0)),
Body::Table3 => ("object.table_3", Vec3::new(-10.0, -10.0, 0.0)),
Body::Drawer => ("object.drawer", Vec3::new(-11.0, -7.5, 0.0)),
Body::BedBlue => ("object.bed_human_blue", Vec3::new(-11.0, -15.0, 0.0)),
Body::Anvil => ("object.anvil", Vec3::new(-3.0, -7.0, 0.0)),
Body::Gravestone => ("object.gravestone", Vec3::new(-5.0, -2.0, 0.0)),
Body::Gravestone2 => ("object.gravestone_2", Vec3::new(-8.5, -3.0, 0.0)),
Body::Chair => ("object.chair", Vec3::new(-5.0, -4.5, 0.0)),
Body::Chair2 => ("object.chair_2", Vec3::new(-5.0, -4.5, 0.0)),
Body::Chair3 => ("object.chair_3", Vec3::new(-5.0, -4.5, 0.0)),
Body::Bench => ("object.bench", Vec3::new(-8.8, -5.0, 0.0)),
Body::Carpet => ("object.carpet", Vec3::new(-14.0, -14.0, -0.5)),
Body::Bedroll => ("object.bedroll", Vec3::new(-11.0, -19.5, -0.5)),
Body::CarpetHumanRound => ("object.carpet_human_round", Vec3::new(-14.0, -14.0, -0.5)),
Body::CarpetHumanSquare => ("object.carpet_human_square", Vec3::new(-13.5, -14.0, -0.5)),
Body::CarpetHumanSquare2 => (
"object.carpet_human_square_2",
Vec3::new(-13.5, -14.0, -0.5),
),
Body::CarpetHumanSquircle => (
"object.carpet_human_squircle",
Vec3::new(-21.0, -21.0, -0.5),
),
Body::Pouch => ("object.pouch", Vec3::new(-5.5, -4.5, 0.0)),
};
load_mesh(name, offset)
}

View File

@ -0,0 +1,476 @@
mod cache;
mod load;
pub use cache::FigureModelCache;
pub use load::load_mesh; // TODO: Don't make this public.
use crate::{
anim::{
self, character::CharacterSkeleton, object::ObjectSkeleton, quadruped::QuadrupedSkeleton,
quadrupedmedium::QuadrupedMediumSkeleton, Animation, Skeleton,
},
render::{Consts, FigureBoneData, FigureLocals, Globals, Light, Renderer},
scene::camera::{Camera, CameraMode},
};
use client::Client;
use common::{
comp::{
ActionState::*, Body, CharacterState, Last, MovementState::*, Ori, Pos, Scale, Stats, Vel,
},
terrain::TerrainChunkSize,
vol::VolSize,
};
use hashbrown::HashMap;
use log::debug;
use specs::{Entity as EcsEntity, Join};
use std::time::Instant;
use vek::*;
const DAMAGE_FADE_COEFFICIENT: f64 = 5.0;
pub struct FigureMgr {
model_cache: FigureModelCache,
character_states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>,
quadruped_states: HashMap<EcsEntity, FigureState<QuadrupedSkeleton>>,
quadruped_medium_states: HashMap<EcsEntity, FigureState<QuadrupedMediumSkeleton>>,
object_states: HashMap<EcsEntity, FigureState<ObjectSkeleton>>,
}
impl FigureMgr {
pub fn new() -> Self {
Self {
model_cache: FigureModelCache::new(),
character_states: HashMap::new(),
quadruped_states: HashMap::new(),
quadruped_medium_states: HashMap::new(),
object_states: HashMap::new(),
}
}
pub fn clean(&mut self, tick: u64) {
self.model_cache.clean(tick);
}
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
let time = client.state().get_time();
let tick = client.get_tick();
let ecs = client.state().ecs();
let view_distance = client.view_distance().unwrap_or(1);
let dt = client.state().get_delta_time();
// Get player position.
let player_pos = ecs
.read_storage::<Pos>()
.get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0);
for (entity, pos, vel, ori, scale, body, character, last_character, stats) in (
&ecs.entities(),
&ecs.read_storage::<Pos>(),
&ecs.read_storage::<Vel>(),
&ecs.read_storage::<Ori>(),
ecs.read_storage::<Scale>().maybe(),
&ecs.read_storage::<Body>(),
ecs.read_storage::<CharacterState>().maybe(),
ecs.read_storage::<Last<CharacterState>>().maybe(),
ecs.read_storage::<Stats>().maybe(),
)
.join()
{
// Don't process figures outside the vd
let vd_frac = (pos.0 - player_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32)
.magnitude()
/ view_distance as f32;
// Keep from re-adding/removing entities on the border of the vd
if vd_frac > 1.2 {
match body {
Body::Humanoid(_) => {
self.character_states.remove(&entity);
}
Body::Quadruped(_) => {
self.quadruped_states.remove(&entity);
}
Body::QuadrupedMedium(_) => {
self.quadruped_medium_states.remove(&entity);
}
Body::Object(_) => {
self.object_states.remove(&entity);
}
}
continue;
} else if vd_frac > 1.0 {
continue;
}
// Change in health as color!
let col = stats
.and_then(|stats| stats.health.last_change)
.map(|(_, time, _)| {
Rgba::broadcast(1.0)
+ Rgba::new(0.0, -1.0, -1.0, 0.0)
.map(|c| (c / (1.0 + DAMAGE_FADE_COEFFICIENT * time)) as f32)
})
.unwrap_or(Rgba::broadcast(1.0));
let scale = scale.map(|s| s.0).unwrap_or(1.0);
let skeleton_attr = &self
.model_cache
.get_or_create_model(renderer, *body, stats.map(|s| &s.equipment), tick)
.1;
match body {
Body::Humanoid(_) => {
let state = self
.character_states
.entry(entity)
.or_insert_with(|| FigureState::new(renderer, CharacterSkeleton::new()));
let (character, last_character) = match (character, last_character) {
(Some(c), Some(l)) => (c, l),
_ => continue,
};
if !character.is_same_movement(&last_character.0) {
state.last_movement_change = Instant::now();
}
if !character.is_same_action(&last_character.0) {
state.last_action_change = Instant::now();
}
let time_since_movement_change =
state.last_movement_change.elapsed().as_secs_f64();
let time_since_action_change = state.last_action_change.elapsed().as_secs_f64();
let target_base = match &character.movement {
Stand => anim::character::StandAnimation::update_skeleton(
&CharacterSkeleton::new(),
time,
time_since_movement_change,
skeleton_attr,
),
Run => anim::character::RunAnimation::update_skeleton(
&CharacterSkeleton::new(),
(vel.0.magnitude(), ori.0.magnitude(), time),
time_since_movement_change,
skeleton_attr,
),
Jump => anim::character::JumpAnimation::update_skeleton(
&CharacterSkeleton::new(),
time,
time_since_movement_change,
skeleton_attr,
),
Roll { .. } => anim::character::RollAnimation::update_skeleton(
&CharacterSkeleton::new(),
time,
time_since_movement_change,
skeleton_attr,
),
Glide => anim::character::GlidingAnimation::update_skeleton(
&CharacterSkeleton::new(),
(vel.0.magnitude(), time),
time_since_movement_change,
skeleton_attr,
),
};
let target_bones = match (&character.movement, &character.action) {
(Stand, Wield { .. }) => anim::character::CidleAnimation::update_skeleton(
&target_base,
time,
time_since_action_change,
skeleton_attr,
),
(Stand, Block { .. }) => {
anim::character::BlockIdleAnimation::update_skeleton(
&target_base,
time,
time_since_action_change,
skeleton_attr,
)
}
(_, Attack { .. }) => anim::character::AttackAnimation::update_skeleton(
&target_base,
time,
time_since_action_change,
skeleton_attr,
),
(_, Wield { .. }) => anim::character::WieldAnimation::update_skeleton(
&target_base,
(vel.0.magnitude(), time),
time_since_action_change,
skeleton_attr,
),
(_, Block { .. }) => anim::character::BlockAnimation::update_skeleton(
&target_base,
time,
time_since_action_change,
skeleton_attr,
),
_ => target_base,
};
state.skeleton.interpolate(&target_bones, dt);
state.update(renderer, pos.0, ori.0, scale, col, dt);
}
Body::Quadruped(_) => {
let state = self
.quadruped_states
.entry(entity)
.or_insert_with(|| FigureState::new(renderer, QuadrupedSkeleton::new()));
let (character, last_character) = match (character, last_character) {
(Some(c), Some(l)) => (c, l),
_ => continue,
};
if !character.is_same_movement(&last_character.0) {
state.last_movement_change = Instant::now();
}
let time_since_movement_change =
state.last_movement_change.elapsed().as_secs_f64();
let target_base = match character.movement {
Stand => anim::quadruped::IdleAnimation::update_skeleton(
&QuadrupedSkeleton::new(),
time,
time_since_movement_change,
skeleton_attr,
),
Run => anim::quadruped::RunAnimation::update_skeleton(
&QuadrupedSkeleton::new(),
(vel.0.magnitude(), time),
time_since_movement_change,
skeleton_attr,
),
Jump => anim::quadruped::JumpAnimation::update_skeleton(
&QuadrupedSkeleton::new(),
(vel.0.magnitude(), time),
time_since_movement_change,
skeleton_attr,
),
// TODO!
_ => state.skeleton_mut().clone(),
};
state.skeleton.interpolate(&target_base, dt);
state.update(renderer, pos.0, ori.0, scale, col, dt);
}
Body::QuadrupedMedium(_) => {
let state = self
.quadruped_medium_states
.entry(entity)
.or_insert_with(|| {
FigureState::new(renderer, QuadrupedMediumSkeleton::new())
});
let (character, last_character) = match (character, last_character) {
(Some(c), Some(l)) => (c, l),
_ => continue,
};
if !character.is_same_movement(&last_character.0) {
state.last_movement_change = Instant::now();
}
let time_since_movement_change =
state.last_movement_change.elapsed().as_secs_f64();
let target_base = match character.movement {
Stand => anim::quadrupedmedium::IdleAnimation::update_skeleton(
&QuadrupedMediumSkeleton::new(),
time,
time_since_movement_change,
skeleton_attr,
),
Run => anim::quadrupedmedium::RunAnimation::update_skeleton(
&QuadrupedMediumSkeleton::new(),
(vel.0.magnitude(), time),
time_since_movement_change,
skeleton_attr,
),
Jump => anim::quadrupedmedium::JumpAnimation::update_skeleton(
&QuadrupedMediumSkeleton::new(),
(vel.0.magnitude(), time),
time_since_movement_change,
skeleton_attr,
),
// TODO!
_ => state.skeleton_mut().clone(),
};
state.skeleton.interpolate(&target_base, dt);
state.update(renderer, pos.0, ori.0, scale, col, dt);
}
Body::Object(_) => {
let state = self
.object_states
.entry(entity)
.or_insert_with(|| FigureState::new(renderer, ObjectSkeleton::new()));
state.skeleton = state.skeleton_mut().clone();
state.update(renderer, pos.0, ori.0, scale, col, dt);
}
}
}
// Clear states that have dead entities.
self.character_states
.retain(|entity, _| ecs.entities().is_alive(*entity));
self.quadruped_states
.retain(|entity, _| ecs.entities().is_alive(*entity));
self.quadruped_medium_states
.retain(|entity, _| ecs.entities().is_alive(*entity));
self.object_states
.retain(|entity, _| ecs.entities().is_alive(*entity));
}
pub fn render(
&mut self,
renderer: &mut Renderer,
client: &mut Client,
globals: &Consts<Globals>,
lights: &Consts<Light>,
camera: &Camera,
) {
let tick = client.get_tick();
let ecs = client.state().ecs();
let frustum = camera.frustum(client);
for (entity, _, _, _, body, stats, _) in (
&ecs.entities(),
&ecs.read_storage::<Pos>(),
&ecs.read_storage::<Vel>(),
&ecs.read_storage::<Ori>(),
&ecs.read_storage::<Body>(),
ecs.read_storage::<Stats>().maybe(),
ecs.read_storage::<Scale>().maybe(),
)
.join()
// Don't render figures outside of frustum (camera viewport, max draw distance is farplane)
.filter(|(_, pos, _, _, _, _, scale)| {
frustum.sphere_intersecting(
&pos.0.x,
&pos.0.y,
&pos.0.z,
&(scale.unwrap_or(&Scale(1.0)).0 * 2.0),
)
})
// Don't render dead entities
.filter(|(_, _, _, _, _, stats, _)| stats.map_or(true, |s| !s.is_dead))
{
if let Some((locals, bone_consts)) = match body {
Body::Humanoid(_) => self
.character_states
.get(&entity)
.map(|state| (state.locals(), state.bone_consts())),
Body::Quadruped(_) => self
.quadruped_states
.get(&entity)
.map(|state| (state.locals(), state.bone_consts())),
Body::QuadrupedMedium(_) => self
.quadruped_medium_states
.get(&entity)
.map(|state| (state.locals(), state.bone_consts())),
Body::Object(_) => self
.object_states
.get(&entity)
.map(|state| (state.locals(), state.bone_consts())),
} {
let model = &self
.model_cache
.get_or_create_model(renderer, *body, stats.map(|s| &s.equipment), tick)
.0;
// Don't render the player's body while in first person mode
if camera.get_mode() == CameraMode::FirstPerson
&& client
.state()
.read_storage::<Body>()
.get(client.entity())
.is_some()
&& entity == client.entity()
{
continue;
}
renderer.render_figure(model, globals, locals, bone_consts, lights);
} else {
debug!("Body has no saved figure");
}
}
}
}
pub struct FigureState<S: Skeleton> {
bone_consts: Consts<FigureBoneData>,
locals: Consts<FigureLocals>,
last_movement_change: Instant,
last_action_change: Instant,
skeleton: S,
pos: Vec3<f32>,
ori: Vec3<f32>,
}
impl<S: Skeleton> FigureState<S> {
pub fn new(renderer: &mut Renderer, skeleton: S) -> Self {
Self {
bone_consts: renderer
.create_consts(&skeleton.compute_matrices())
.unwrap(),
locals: renderer.create_consts(&[FigureLocals::default()]).unwrap(),
last_movement_change: Instant::now(),
last_action_change: Instant::now(),
skeleton,
pos: Vec3::zero(),
ori: Vec3::zero(),
}
}
pub fn update(
&mut self,
renderer: &mut Renderer,
pos: Vec3<f32>,
ori: Vec3<f32>,
scale: f32,
col: Rgba<f32>,
dt: f32,
) {
// Update interpolation values
if self.pos.distance_squared(pos) < 64.0 * 64.0 {
self.pos = Lerp::lerp(self.pos, pos, 15.0 * dt);
self.ori = Slerp::slerp(self.ori, ori, 7.5 * dt);
} else {
self.pos = pos;
self.ori = ori;
}
let mat = Mat4::<f32>::identity()
* Mat4::translation_3d(self.pos)
* Mat4::rotation_z(-ori.x.atan2(ori.y))
* Mat4::scaling_3d(Vec3::from(0.8 * scale));
let locals = FigureLocals::new(mat, col);
renderer.update_consts(&mut self.locals, &[locals]).unwrap();
renderer
.update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices())
.unwrap();
}
pub fn locals(&self) -> &Consts<FigureLocals> {
&self.locals
}
pub fn bone_consts(&self) -> &Consts<FigureBoneData> {
&self.bone_consts
}
pub fn skeleton_mut(&mut self) -> &mut S {
&mut self.skeleton
}
}