mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
commit
1f859111c4
BIN
assets/voxygen/voxel/armor/chest/chest_blue.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/armor/chest/chest_blue.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/armor/chest/chest_brown.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/armor/chest/chest_brown.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/armor/chest/chest_dark.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/armor/chest/chest_dark.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/armor/chest/chest_green.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/armor/chest/chest_green.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/armor/chest/chest_orange.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/armor/chest/chest_orange.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/armor/chest/grayscale.vox
(Stored with Git LFS)
Normal file
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
BIN
assets/voxygen/voxel/armor/pants/grayscale.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/armor/pants/pants_blue.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/armor/pants/pants_blue.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/armor/pants/pants_brown.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/armor/pants/pants_brown.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/armor/pants/pants_dark.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/armor/pants/pants_dark.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/armor/pants/pants_green.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/armor/pants/pants_green.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/armor/pants/pants_orange.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/armor/pants/pants_orange.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/accessory/danari/horns.vox
(Stored with Git LFS)
Normal file
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
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
BIN
assets/voxygen/voxel/figure/beard/dwarf/1.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/beard/human/1.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/beard/human/1.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/beard/orc/1.vox
(Stored with Git LFS)
Normal file
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
BIN
assets/voxygen/voxel/figure/body/chest.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/body/chest_female.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/body/chest_female.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/body/chest_male.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/body/chest_male.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/body/empty.vox
(Stored with Git LFS)
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
BIN
assets/voxygen/voxel/figure/hair/elf/male.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/human/female_1.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/hair/human/female_1.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/human/female_2.vox
(Stored with Git LFS)
Normal file
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
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
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
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
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
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
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
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
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
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
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
BIN
assets/voxygen/voxel/figure/head/elf/male.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/head/head_danari_female.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/head/head_danari_female.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/head/head_danari_male.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/head/head_danari_male.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/head/head_dwarf_male.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/head/head_dwarf_male.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/head/head_elf_female.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/head/head_elf_female.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/head/head_elf_male.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/head/head_elf_male.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/head/head_human_female.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/head/head_human_female.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/head/head_human_male.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/head/head_human_male.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/head/head_orc_female.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/head/head_orc_female.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/head/head_orc_male.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/head/head_orc_male.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/head/human/female.vox
(Stored with Git LFS)
Normal file
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
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
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
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
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
BIN
assets/voxygen/voxel/figure/head/undead/male.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
195
assets/voxygen/voxel/humanoid_head_manifest.ron
Normal file
195
assets/voxygen/voxel/humanoid_head_manifest.ron
Normal 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
BIN
assets/voxygen/voxel/not_found.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
34
common/src/figure/mat_cell.rs
Normal file
34
common/src/figure/mat_cell.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use crate::vol::Vox;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Material {
|
||||
Skin,
|
||||
Hair,
|
||||
EyeDark,
|
||||
EyeLight,
|
||||
EyeWhite,
|
||||
//HairLight,
|
||||
//HairDark,
|
||||
//Clothing,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MatCell {
|
||||
None,
|
||||
Mat(Material),
|
||||
Normal(Rgb<u8>),
|
||||
}
|
||||
|
||||
impl Vox for MatCell {
|
||||
fn empty() -> Self {
|
||||
MatCell::None
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
MatCell::None => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,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(
|
||||
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(), ())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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,13 +975,7 @@ 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,
|
||||
)
|
||||
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)
|
||||
@ -1057,8 +983,39 @@ impl CharSelectionUi {
|
||||
.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)
|
||||
{}
|
||||
.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
177
voxygen/src/scene/figure/cache.rs
Normal file
177
voxygen/src/scene/figure/cache.rs
Normal 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);
|
||||
}
|
||||
}
|
549
voxygen/src/scene/figure/load.rs
Normal file
549
voxygen/src/scene/figure/load.rs
Normal 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)
|
||||
}
|
476
voxygen/src/scene/figure/mod.rs
Normal file
476
voxygen/src/scene/figure/mod.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user