diff --git a/assets/voxygen/voxel/armor/chest/chest_blue.vox b/assets/voxygen/voxel/armor/chest/chest_blue.vox deleted file mode 100644 index c45304c837..0000000000 --- a/assets/voxygen/voxel/armor/chest/chest_blue.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:64e66820ac9b73dd1d58e5809fe1fc9842b2f032a29fe85bbd3fa593c8afb05b -size 45499 diff --git a/assets/voxygen/voxel/armor/chest/chest_brown.vox b/assets/voxygen/voxel/armor/chest/chest_brown.vox deleted file mode 100644 index 5137985725..0000000000 --- a/assets/voxygen/voxel/armor/chest/chest_brown.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c28ff938e48ef8f8bdb0a0dcac9293892e85e86771d9b3550288bc0b66bcfd4 -size 45499 diff --git a/assets/voxygen/voxel/armor/chest/chest_dark.vox b/assets/voxygen/voxel/armor/chest/chest_dark.vox deleted file mode 100644 index 9af67e7cbc..0000000000 --- a/assets/voxygen/voxel/armor/chest/chest_dark.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:200cdedef555fa03b7761e0a3a4209de76851f007b6b137bd77925a49051e560 -size 2392 diff --git a/assets/voxygen/voxel/armor/chest/chest_green.vox b/assets/voxygen/voxel/armor/chest/chest_green.vox deleted file mode 100644 index fe2078bdf1..0000000000 --- a/assets/voxygen/voxel/armor/chest/chest_green.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ad9deed210d2f57409b83b09580a423938646fbff2c2e1aafbf864e1728139b -size 45499 diff --git a/assets/voxygen/voxel/armor/chest/chest_orange.vox b/assets/voxygen/voxel/armor/chest/chest_orange.vox deleted file mode 100644 index 40b184541e..0000000000 --- a/assets/voxygen/voxel/armor/chest/chest_orange.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3870daf5c631554debb8a417353f32fb09c7d5263a5b8cd54d607de21c620998 -size 45499 diff --git a/assets/voxygen/voxel/armor/chest/grayscale.vox b/assets/voxygen/voxel/armor/chest/grayscale.vox new file mode 100644 index 0000000000..9af701536c --- /dev/null +++ b/assets/voxygen/voxel/armor/chest/grayscale.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ce1a9b5a9152e55a73d548545d547474b1e4ced5910f59319b5b0916c1bbbae +size 45401 diff --git a/assets/voxygen/voxel/armor/pants/grayscale.vox b/assets/voxygen/voxel/armor/pants/grayscale.vox new file mode 100644 index 0000000000..22f30f956b --- /dev/null +++ b/assets/voxygen/voxel/armor/pants/grayscale.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd19a9beefb860af00b80d42948ea9cee84e948c65cfae5e94f9e28b4e906e77 +size 44945 diff --git a/assets/voxygen/voxel/armor/pants/pants_blue.vox b/assets/voxygen/voxel/armor/pants/pants_blue.vox deleted file mode 100644 index 4bd40bdd67..0000000000 --- a/assets/voxygen/voxel/armor/pants/pants_blue.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b83e93629182d325ff8d06e62f692d63bfbd6c1336a07295388ebcfe50273067 -size 44891 diff --git a/assets/voxygen/voxel/armor/pants/pants_brown.vox b/assets/voxygen/voxel/armor/pants/pants_brown.vox deleted file mode 100644 index 0831094d07..0000000000 --- a/assets/voxygen/voxel/armor/pants/pants_brown.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c967eece29977f89333f99ed4a9932dbc324a85ec319fad9d92c830c3ea8e6c5 -size 1784 diff --git a/assets/voxygen/voxel/armor/pants/pants_dark.vox b/assets/voxygen/voxel/armor/pants/pants_dark.vox deleted file mode 100644 index 0913ab44b5..0000000000 --- a/assets/voxygen/voxel/armor/pants/pants_dark.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d73f412dc1f75af7cb0fcd7a46d02c464d1bcde6fc6143827c3bfec4276f61ad -size 1784 diff --git a/assets/voxygen/voxel/armor/pants/pants_green.vox b/assets/voxygen/voxel/armor/pants/pants_green.vox deleted file mode 100644 index 98007c69d7..0000000000 --- a/assets/voxygen/voxel/armor/pants/pants_green.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ec1f0e426173eac10d8cfb29dffbcd30e2f5e68bac267b22bc1c10bd01c6239f -size 44891 diff --git a/assets/voxygen/voxel/armor/pants/pants_orange.vox b/assets/voxygen/voxel/armor/pants/pants_orange.vox deleted file mode 100644 index cf35d11fb6..0000000000 --- a/assets/voxygen/voxel/armor/pants/pants_orange.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9dff060088cb697f08623cbf8dab69c5b7234aee4f43f226fc161f04aee76032 -size 44891 diff --git a/assets/voxygen/voxel/figure/accessory/danari/horns.vox b/assets/voxygen/voxel/figure/accessory/danari/horns.vox new file mode 100644 index 0000000000..8b74f40935 --- /dev/null +++ b/assets/voxygen/voxel/figure/accessory/danari/horns.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e27bd3211ad3c0fca466a16297e37cd35b7118f8da2299e4b01ac36a5ad5f888 +size 1136 diff --git a/assets/voxygen/voxel/figure/accessory/orc/earring.vox b/assets/voxygen/voxel/figure/accessory/orc/earring.vox new file mode 100644 index 0000000000..d623b20961 --- /dev/null +++ b/assets/voxygen/voxel/figure/accessory/orc/earring.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01c7f2a0866e526a7a2528c11d82c0fbb33eb5c904f37d361eb1f07b1f3d486e +size 1100 diff --git a/assets/voxygen/voxel/figure/beard/dwarf/1.vox b/assets/voxygen/voxel/figure/beard/dwarf/1.vox new file mode 100644 index 0000000000..e7d84f724b --- /dev/null +++ b/assets/voxygen/voxel/figure/beard/dwarf/1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eaf09e52a043dc1106eeb6d6f882d0e252cf7198ff826a8ad7908d719af3ac57 +size 1824 diff --git a/assets/voxygen/voxel/figure/beard/human/1.vox b/assets/voxygen/voxel/figure/beard/human/1.vox index 8489a322ce..e607c7e7c3 100644 --- a/assets/voxygen/voxel/figure/beard/human/1.vox +++ b/assets/voxygen/voxel/figure/beard/human/1.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e9515f72b4595d7867b47c0f58cbc624eace2f043fc6643a3b30e033ca783dc -size 44944 +oid sha256:d493cbef02c10163092d61ea2c7a4246178e6955b99cae1e3500d5f66d846005 +size 1240 diff --git a/assets/voxygen/voxel/figure/beard/orc/1.vox b/assets/voxygen/voxel/figure/beard/orc/1.vox new file mode 100644 index 0000000000..2f8e9b3303 --- /dev/null +++ b/assets/voxygen/voxel/figure/beard/orc/1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78d06c8b04feefdb1c179c30c42118972cbf1dfaae4d14f2d329ac9f67bd1ae9 +size 1104 diff --git a/assets/voxygen/voxel/figure/body/chest.vox b/assets/voxygen/voxel/figure/body/chest.vox new file mode 100644 index 0000000000..706825e0b8 --- /dev/null +++ b/assets/voxygen/voxel/figure/body/chest.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69a302986cabe98ebc8a5e888231b44a7207b181a327289fa89278ff7d6ff44a +size 45131 diff --git a/assets/voxygen/voxel/figure/body/chest_female.vox b/assets/voxygen/voxel/figure/body/chest_female.vox index 490178fecb..bbfbe470cb 100644 --- a/assets/voxygen/voxel/figure/body/chest_female.vox +++ b/assets/voxygen/voxel/figure/body/chest_female.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7af2a4d3051af77893da03a1f6495c49647b5b0dbfe73f09c81c8d50dd7c7b3 -size 2208 +oid sha256:07d909f26e5e0eee95ccc6b2ddef03442fbcf70866daeeb91dedab6a69dd77f7 +size 45315 diff --git a/assets/voxygen/voxel/figure/body/chest_male.vox b/assets/voxygen/voxel/figure/body/chest_male.vox index f31a9f85a1..2826a626a9 100644 --- a/assets/voxygen/voxel/figure/body/chest_male.vox +++ b/assets/voxygen/voxel/figure/body/chest_male.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7970e86cee65bcc690f0341f595a1f860005db31f7d443c4ba380f67cf60128c +oid sha256:a6ac0a625ef1f542e3614920e931697fe6f41503c80fcb2e6531d478161d8b61 size 45323 diff --git a/assets/voxygen/voxel/figure/body/empty.vox b/assets/voxygen/voxel/figure/body/empty.vox deleted file mode 100644 index f7a98b8e9f..0000000000 --- a/assets/voxygen/voxel/figure/body/empty.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c520c6867ddad09d2839a9b74ecff16e5cdc1bd636c1766eaf1551de66a9b447 -size 44204 diff --git a/assets/voxygen/voxel/figure/empty.vox b/assets/voxygen/voxel/figure/empty.vox deleted file mode 100644 index f7a98b8e9f..0000000000 --- a/assets/voxygen/voxel/figure/empty.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c520c6867ddad09d2839a9b74ecff16e5cdc1bd636c1766eaf1551de66a9b447 -size 44204 diff --git a/assets/voxygen/voxel/figure/eyes/danari/female.vox b/assets/voxygen/voxel/figure/eyes/danari/female.vox new file mode 100644 index 0000000000..1e07d63b51 --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/danari/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af6830d432bf365bd571736a88495a5e714f2bb238307ad30d5b7ff1da3f3110 +size 44259 diff --git a/assets/voxygen/voxel/figure/eyes/danari/male.vox b/assets/voxygen/voxel/figure/eyes/danari/male.vox new file mode 100644 index 0000000000..5b7bc579c6 --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/danari/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7af1feee92b5ea92f33d1c668dab8bbbddab27c7153c6b582429d0f5b60457ec +size 1144 diff --git a/assets/voxygen/voxel/figure/eyes/dwarf/female.vox b/assets/voxygen/voxel/figure/eyes/dwarf/female.vox new file mode 100644 index 0000000000..4919ac544b --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/dwarf/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9b009ae959dba33eeb1b2c651b403bd8346a16db5102372f66c0c7b4b073689 +size 1152 diff --git a/assets/voxygen/voxel/figure/eyes/dwarf/male.vox b/assets/voxygen/voxel/figure/eyes/dwarf/male.vox new file mode 100644 index 0000000000..586d7ffdf4 --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/dwarf/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae419363a251ac31055b18c51d92175c57238d936018bdac2682da5e1921f0ff +size 55604 diff --git a/assets/voxygen/voxel/figure/eyes/elf/female.vox b/assets/voxygen/voxel/figure/eyes/elf/female.vox new file mode 100644 index 0000000000..f038c8d9b0 --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/elf/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57b96d488b5a100576370eb5145e1f5d2d7a6e3f0410b887fbfcec364b5ef2b7 +size 1160 diff --git a/assets/voxygen/voxel/figure/eyes/elf/male.vox b/assets/voxygen/voxel/figure/eyes/elf/male.vox new file mode 100644 index 0000000000..bde693101e --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/elf/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44e63c4be5bffaca26e8f305ca9d2af56de702c47cbde1ea3ea9308f217974c6 +size 1144 diff --git a/assets/voxygen/voxel/figure/eyes/human/female.vox b/assets/voxygen/voxel/figure/eyes/human/female.vox new file mode 100644 index 0000000000..92d9c14246 --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/human/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21c5a4f82b33258a8d3af1c4b518e0a37cc088bfb1bbe2e4652b4a07aa323c74 +size 44259 diff --git a/assets/voxygen/voxel/figure/eyes/human/female_1.vox b/assets/voxygen/voxel/figure/eyes/human/female_1 (unused).vox similarity index 100% rename from assets/voxygen/voxel/figure/eyes/human/female_1.vox rename to assets/voxygen/voxel/figure/eyes/human/female_1 (unused).vox diff --git a/assets/voxygen/voxel/figure/eyes/human/male.vox b/assets/voxygen/voxel/figure/eyes/human/male.vox new file mode 100644 index 0000000000..89cee13eda --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/human/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ebe9e856dd6563c959d41c7e2935b39da740a29c112599398ff6b697df6526d1 +size 44251 diff --git a/assets/voxygen/voxel/figure/eyes/human/male_1.vox b/assets/voxygen/voxel/figure/eyes/human/male_1 (unused).vox similarity index 100% rename from assets/voxygen/voxel/figure/eyes/human/male_1.vox rename to assets/voxygen/voxel/figure/eyes/human/male_1 (unused).vox diff --git a/assets/voxygen/voxel/figure/eyes/orc/female.vox b/assets/voxygen/voxel/figure/eyes/orc/female.vox new file mode 100644 index 0000000000..77aca2051b --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/orc/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e72d0abdda3a789f7aa4687a716ffb4784c8f97be2b127b6a7660acfd9192ed +size 1220 diff --git a/assets/voxygen/voxel/figure/eyes/orc/male.vox b/assets/voxygen/voxel/figure/eyes/orc/male.vox new file mode 100644 index 0000000000..4ff34def55 --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/orc/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf1564ab03f10c7bbe114ce34a30e927c9efae46ef0936d9f5ee9657f05ee287 +size 1272 diff --git a/assets/voxygen/voxel/figure/eyes/undead/female.vox b/assets/voxygen/voxel/figure/eyes/undead/female.vox new file mode 100644 index 0000000000..3d408abf51 --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/undead/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e00566a99bd9c82d44fb84bef34dbad4376ee64f18f86d9363ca1780ca3b2e3a +size 44273 diff --git a/assets/voxygen/voxel/figure/eyes/undead/male.vox b/assets/voxygen/voxel/figure/eyes/undead/male.vox new file mode 100644 index 0000000000..f4989910ca --- /dev/null +++ b/assets/voxygen/voxel/figure/eyes/undead/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce8503327544bcd3183e592e9516cccb8bb58cee87d76b375a9e3ffd76c85996 +size 44273 diff --git a/assets/voxygen/voxel/figure/hair/danari/female.vox b/assets/voxygen/voxel/figure/hair/danari/female.vox new file mode 100644 index 0000000000..9e0363f1b0 --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/danari/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7de1f0cf0f94dc577b22f41a284f37c31d8674221e82a67698a8c3f46a6ab417 +size 45525 diff --git a/assets/voxygen/voxel/figure/hair/danari/male.vox b/assets/voxygen/voxel/figure/hair/danari/male.vox new file mode 100644 index 0000000000..ea131353f4 --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/danari/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e090a18b4ddccb601c7f96d82756c8a1007eb6fda418d2d2ea052055b48c6b2b +size 1824 diff --git a/assets/voxygen/voxel/figure/hair/dwarf/female.vox b/assets/voxygen/voxel/figure/hair/dwarf/female.vox new file mode 100644 index 0000000000..8be6e982bc --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/dwarf/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf986adcf92255b6c6f4d2537dda786adbf7809acb11c3e6539284b90155c9a5 +size 2552 diff --git a/assets/voxygen/voxel/figure/hair/elf/female.vox b/assets/voxygen/voxel/figure/hair/elf/female.vox new file mode 100644 index 0000000000..ebe526c0f9 --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/elf/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad56fe39bededbb3ccd9ac48bdadc81f36dc70d22c7a24b7a5866200ba09e1a6 +size 2676 diff --git a/assets/voxygen/voxel/figure/hair/elf/male.vox b/assets/voxygen/voxel/figure/hair/elf/male.vox new file mode 100644 index 0000000000..25e9ec10fc --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/elf/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d60740e718f15423d040aaf0e2e077b2fc95a9f357e1c2d2f543569e0a8a997d +size 1880 diff --git a/assets/voxygen/voxel/figure/hair/human/female_1.vox b/assets/voxygen/voxel/figure/hair/human/female_1.vox index 2240c14415..57d548338d 100644 --- a/assets/voxygen/voxel/figure/hair/human/female_1.vox +++ b/assets/voxygen/voxel/figure/hair/human/female_1.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4738441bc4625b5083a0f055ffdb742ec4f5f62edc60bdd9670e3fda865a8bc9 +oid sha256:7d4603227faf064963a68c2be68e7b4cfe12507f294d5ed4b649d3017c646d80 size 2736 diff --git a/assets/voxygen/voxel/figure/hair/human/female_2.vox b/assets/voxygen/voxel/figure/hair/human/female_2.vox new file mode 100644 index 0000000000..a7c824a55f --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/human/female_2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:236a447ac08d57eba796e443a7541e1463bca17b21ce8bf8f8a8ddf044d55e81 +size 2924 diff --git a/assets/voxygen/voxel/figure/hair/human/male.vox b/assets/voxygen/voxel/figure/hair/human/male.vox new file mode 100644 index 0000000000..ab4ac0570e --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/human/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a8727d18e1e8edfec2bb2c84de86d4120226684a3b586906502664212ff9a2e +size 1824 diff --git a/assets/voxygen/voxel/figure/hair/orc/female.vox b/assets/voxygen/voxel/figure/hair/orc/female.vox new file mode 100644 index 0000000000..93e9ab8de6 --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/orc/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db3808a3e095615c2951b7a6e4b3f0263069a3471ecacb92d4cbc49dbff707ce +size 44995 diff --git a/assets/voxygen/voxel/figure/hair/orc/male.vox b/assets/voxygen/voxel/figure/hair/orc/male.vox new file mode 100644 index 0000000000..0ea2a92b50 --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/orc/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfd059319a87145475802f1ad80d08fc524adafe67d4db673e136fb68a87cec7 +size 44691 diff --git a/assets/voxygen/voxel/figure/hair/undead/female.vox b/assets/voxygen/voxel/figure/hair/undead/female.vox new file mode 100644 index 0000000000..675c1bc148 --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/undead/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a0721044563cabaa77e80acacc7d6e51b1fadcdf058c7fe10ebdc1348839f7c +size 2012 diff --git a/assets/voxygen/voxel/figure/hair/undead/male.vox b/assets/voxygen/voxel/figure/hair/undead/male.vox new file mode 100644 index 0000000000..300ca183d3 --- /dev/null +++ b/assets/voxygen/voxel/figure/hair/undead/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cd0fcd4ca772ff15467fdf31e09f57ede91d91017af5ea2d1470182af24b211 +size 1960 diff --git a/assets/voxygen/voxel/figure/head/danari/female.vox b/assets/voxygen/voxel/figure/head/danari/female.vox new file mode 100644 index 0000000000..e2b73c0509 --- /dev/null +++ b/assets/voxygen/voxel/figure/head/danari/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b43e5ef0b4333ec9d4c3ac751681a3c0721c61843dea16782fe7da7c5ef62ab +size 3248 diff --git a/assets/voxygen/voxel/figure/head/danari/male.vox b/assets/voxygen/voxel/figure/head/danari/male.vox new file mode 100644 index 0000000000..e2b73c0509 --- /dev/null +++ b/assets/voxygen/voxel/figure/head/danari/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b43e5ef0b4333ec9d4c3ac751681a3c0721c61843dea16782fe7da7c5ef62ab +size 3248 diff --git a/assets/voxygen/voxel/figure/head/dwarf/female.vox b/assets/voxygen/voxel/figure/head/dwarf/female.vox new file mode 100644 index 0000000000..90ca089930 --- /dev/null +++ b/assets/voxygen/voxel/figure/head/dwarf/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5845768ea2eaf5ba2424f2390e1ea5b76198b2c1ce0ea3dcb17bf0c37c621eb +size 57448 diff --git a/assets/voxygen/voxel/figure/head/dwarf/male.vox b/assets/voxygen/voxel/figure/head/dwarf/male.vox new file mode 100644 index 0000000000..f1da900efb --- /dev/null +++ b/assets/voxygen/voxel/figure/head/dwarf/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4eaca446ee94c499fb17572649cd7e0caf169fee676328fb7209fe1b085afa0e +size 57436 diff --git a/assets/voxygen/voxel/figure/head/elf/female.vox b/assets/voxygen/voxel/figure/head/elf/female.vox new file mode 100644 index 0000000000..f92607addf --- /dev/null +++ b/assets/voxygen/voxel/figure/head/elf/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c305b7682051586962145e79196ca72163942f78cf06059561187d9b7072d31f +size 3264 diff --git a/assets/voxygen/voxel/figure/head/elf/male.vox b/assets/voxygen/voxel/figure/head/elf/male.vox new file mode 100644 index 0000000000..97a8d65c2c --- /dev/null +++ b/assets/voxygen/voxel/figure/head/elf/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3468d72377299eb3c35de5aecb85505b9f5a466d17f56375ae5fc23f872df02 +size 3320 diff --git a/assets/voxygen/voxel/figure/head/head_danari_female.vox b/assets/voxygen/voxel/figure/head/head_danari_female.vox deleted file mode 100644 index 4c115c41b9..0000000000 --- a/assets/voxygen/voxel/figure/head/head_danari_female.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b0bcd4f8780e432c201f8af431e189af84d5b49b58f10acbc9ef500f64a53007 -size 47791 diff --git a/assets/voxygen/voxel/figure/head/head_danari_male.vox b/assets/voxygen/voxel/figure/head/head_danari_male.vox deleted file mode 100644 index fcb3214afc..0000000000 --- a/assets/voxygen/voxel/figure/head/head_danari_male.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96398d31aa2b019b1efa58d615267eeab357e7a9bbab305e192ec9363a26479c -size 4104 diff --git a/assets/voxygen/voxel/figure/head/head_dwarf_male.vox b/assets/voxygen/voxel/figure/head/head_dwarf_male.vox deleted file mode 100644 index f1bb88b59a..0000000000 --- a/assets/voxygen/voxel/figure/head/head_dwarf_male.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1b01897892239ddca087b225e0e35bad47f3d0feb23122bba899c1ea9131fc3 -size 3824 diff --git a/assets/voxygen/voxel/figure/head/head_elf_female.vox b/assets/voxygen/voxel/figure/head/head_elf_female.vox deleted file mode 100644 index 5d84174c05..0000000000 --- a/assets/voxygen/voxel/figure/head/head_elf_female.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f6e6b6d7c39cd7234d5bcc4af6a83476fa7bae241fabc83c63aca7d6015ab514 -size 48007 diff --git a/assets/voxygen/voxel/figure/head/head_elf_male.vox b/assets/voxygen/voxel/figure/head/head_elf_male.vox deleted file mode 100644 index 646fa5524f..0000000000 --- a/assets/voxygen/voxel/figure/head/head_elf_male.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aaf6c8d40ff51bdcc3177b4aab02885d11ce97db1fb13c62db8db1b5a98bb84b -size 47252 diff --git a/assets/voxygen/voxel/figure/head/head_human_female.vox b/assets/voxygen/voxel/figure/head/head_human_female.vox deleted file mode 100644 index cda0394334..0000000000 --- a/assets/voxygen/voxel/figure/head/head_human_female.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:76212e53742c9a5dc9bbde1d5709e21b99eb17935a3babfcb1c392d673a4f7ff -size 48299 diff --git a/assets/voxygen/voxel/figure/head/head_human_male.vox b/assets/voxygen/voxel/figure/head/head_human_male.vox deleted file mode 100644 index 8b2d8c9dd9..0000000000 --- a/assets/voxygen/voxel/figure/head/head_human_male.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:feb06650ac93a63318391a930fa6d6e2a07bec7300d8b9d76731259613cc4e65 -size 47204 diff --git a/assets/voxygen/voxel/figure/head/head_orc_female.vox b/assets/voxygen/voxel/figure/head/head_orc_female.vox deleted file mode 100644 index ffd88e1bf4..0000000000 --- a/assets/voxygen/voxel/figure/head/head_orc_female.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb6052b4aff4df32ac57192332229ad0b740346e654c0d663d11fe4c6306dbb1 -size 47209 diff --git a/assets/voxygen/voxel/figure/head/head_orc_male.vox b/assets/voxygen/voxel/figure/head/head_orc_male.vox deleted file mode 100644 index 98c1daa965..0000000000 --- a/assets/voxygen/voxel/figure/head/head_orc_male.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:88da79a8e2c9086e116721b23e823e75aae72ed05dceaa125177332f3836b2ae -size 46680 diff --git a/assets/voxygen/voxel/figure/head/human/female.vox b/assets/voxygen/voxel/figure/head/human/female.vox new file mode 100644 index 0000000000..8e571ea8d2 --- /dev/null +++ b/assets/voxygen/voxel/figure/head/human/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4268b2c28f9ed5869ff8a9f93fdb9e249306a329dae1dfac7703cab399bea4b4 +size 3328 diff --git a/assets/voxygen/voxel/figure/head/human/male.vox b/assets/voxygen/voxel/figure/head/human/male.vox new file mode 100644 index 0000000000..8e571ea8d2 --- /dev/null +++ b/assets/voxygen/voxel/figure/head/human/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4268b2c28f9ed5869ff8a9f93fdb9e249306a329dae1dfac7703cab399bea4b4 +size 3328 diff --git a/assets/voxygen/voxel/figure/head/orc/female.vox b/assets/voxygen/voxel/figure/head/orc/female.vox new file mode 100644 index 0000000000..c17ab46e27 --- /dev/null +++ b/assets/voxygen/voxel/figure/head/orc/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d3b010ca6c8ae6f440bb84e6d8b361f632a1742006250e5341eaa0c4a58fa43 +size 3216 diff --git a/assets/voxygen/voxel/figure/head/orc/male.vox b/assets/voxygen/voxel/figure/head/orc/male.vox new file mode 100644 index 0000000000..eb86b59462 --- /dev/null +++ b/assets/voxygen/voxel/figure/head/orc/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:133438abbbf80ed9589ffd45b25a4b8e583812aa88a8fe0d5756c22d6c56cc78 +size 3016 diff --git a/assets/voxygen/voxel/figure/head/undead/female.vox b/assets/voxygen/voxel/figure/head/undead/female.vox new file mode 100644 index 0000000000..889a371ee2 --- /dev/null +++ b/assets/voxygen/voxel/figure/head/undead/female.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9755d1f7da459808861664a9a4f7fbe922cd061804088fa956bd34ffe6a170a1 +size 3300 diff --git a/assets/voxygen/voxel/figure/head/undead/male.vox b/assets/voxygen/voxel/figure/head/undead/male.vox new file mode 100644 index 0000000000..5ffbc68ffd --- /dev/null +++ b/assets/voxygen/voxel/figure/head/undead/male.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:938f108c3fdbba5b1742c634f59c3e1b1fffecad36e153bd6e2b9ec63cea77b5 +size 3016 diff --git a/assets/voxygen/voxel/humanoid_head_manifest.ron b/assets/voxygen/voxel/humanoid_head_manifest.ron new file mode 100644 index 0000000000..7be1de3fca --- /dev/null +++ b/assets/voxygen/voxel/humanoid_head_manifest.ron @@ -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 +}) \ No newline at end of file diff --git a/assets/voxygen/voxel/not_found.vox b/assets/voxygen/voxel/not_found.vox new file mode 100644 index 0000000000..c80a4bc22e --- /dev/null +++ b/assets/voxygen/voxel/not_found.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e7a3fff54d0f4ba4f32a2bcd8aec0248728c404ff76de43206b24a2e794b1ff +size 49528 diff --git a/common/src/comp/body/humanoid.rs b/common/src/comp/body/humanoid.rs index 5693166a4b..6efb9b393f 100644 --- a/common/src/comp/body/humanoid.rs +++ b/common/src/comp/body/humanoid.rs @@ -1,4 +1,5 @@ -use rand::{seq::SliceRandom, thread_rng}; +use rand::{seq::SliceRandom, thread_rng, Rng}; +use vek::Rgb; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Body { @@ -10,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 { + self.hair_colors() + .get(val as usize) + .copied() + .unwrap_or((0, 0, 0)) + .into() + } + pub fn num_hair_colors(self) -> usize { + self.hair_colors().len() + } + pub fn skin_color(self, val: u8) -> Rgb { + self.skin_colors() + .get(val as usize) + .copied() + .unwrap_or((0, 0, 0)) + .into() + } + pub fn num_skin_colors(self) -> usize { + self.skin_colors().len() + } + pub fn eye_color(self, val: u8) -> EyeColor { + self.eye_colors() + .get(val as usize) + .copied() + .unwrap_or(EyeColor::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 { + 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 { + 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 { + 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]; diff --git a/common/src/comp/inputs.rs b/common/src/comp/inputs.rs index d748b16652..4a1807ddc0 100644 --- a/common/src/comp/inputs.rs +++ b/common/src/comp/inputs.rs @@ -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; diff --git a/common/src/figure/cell.rs b/common/src/figure/cell.rs index 8d225660cb..130c92bf27 100644 --- a/common/src/figure/cell.rs +++ b/common/src/figure/cell.rs @@ -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, diff --git a/common/src/figure/mat_cell.rs b/common/src/figure/mat_cell.rs new file mode 100644 index 0000000000..905457f3b0 --- /dev/null +++ b/common/src/figure/mat_cell.rs @@ -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), +} + +impl Vox for MatCell { + fn empty() -> Self { + MatCell::None + } + + fn is_empty(&self) -> bool { + match self { + MatCell::None => true, + _ => false, + } + } +} diff --git a/common/src/figure/mod.rs b/common/src/figure/mod.rs index 76ab19e8d0..9d73be8c8f 100644 --- a/common/src/figure/mod.rs +++ b/common/src/figure/mod.rs @@ -1,8 +1,11 @@ pub mod cell; +pub mod mat_cell; +pub use mat_cell::Material; use self::cell::Cell; +use self::mat_cell::MatCell; use crate::{ - vol::{Vox, WriteVol}, + vol::{ReadVol, SizedVol, Vox, WriteVol}, volumes::dyna::Dyna, }; use dot_vox::DotVoxData; @@ -30,11 +33,12 @@ impl From<&DotVoxData> for Segment { for voxel in &model.voxels { if let Some(&color) = palette.get(voxel.i as usize) { - // TODO: Maybe don't ignore this error? - let _ = segment.set( - Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| i32::from(e)), - Cell::new(color), - ); + segment + .set( + Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| i32::from(e)), + Cell::new(color), + ) + .unwrap(); } } @@ -44,3 +48,134 @@ impl From<&DotVoxData> for Segment { } } } + +impl Segment { + /// Transform cells + pub fn map(mut self, transform: impl Fn(Cell) -> Option) -> 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) -> Rgb) -> Self { + self.map(|cell| cell.get_color().map(|rgb| Cell::new(transform(rgb)))) + } +} + +// TODO: move +/// A `Dyna` builder that combines Dynas +pub struct DynaUnionizer(Vec<(Dyna, Vec3)>); + +impl DynaUnionizer { + pub fn new() -> Self { + DynaUnionizer(Vec::new()) + } + pub fn add(mut self, dyna: Dyna, offset: Vec3) -> Self { + self.0.push((dyna, offset)); + self + } + pub fn maybe_add(self, maybe: Option<(Dyna, Vec3)>) -> Self { + match maybe { + Some((dyna, offset)) => self.add(dyna, offset), + None => self, + } + } + pub fn unify(self) -> (Dyna, Vec3) { + 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; + +impl MatSegment { + pub fn to_segment(&self, map: impl Fn(Material) -> Rgb) -> 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::>(); + + 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(), ()) + } + } +} diff --git a/common/src/util/mod.rs b/common/src/util/mod.rs index 76121b4fea..09fb4d6acc 100644 --- a/common/src/util/mod.rs +++ b/common/src/util/mod.rs @@ -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) -> Rgb { @@ -91,6 +91,32 @@ pub fn hsv_to_rgb(hsv: Vec3) -> Rgb { Rgb::new(r + m, g + m, b + m) } +/// Convert linear rgb to CIExyY +#[inline(always)] +pub fn rgb_to_xyy(rgb: Rgb) -> Vec3 { + // 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) -> Rgb { + 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, value: f32) -> Rgb { @@ -98,3 +124,13 @@ pub fn saturate_srgb(col: Rgb, value: f32) -> Rgb { 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, chroma: Rgb) -> Rgb { + 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))) +} diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index d345ad05ae..4afeda5723 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -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(); } diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 967798c17f..5ddfb528ec 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -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? } } diff --git a/voxygen/src/menu/char_selection/scene.rs b/voxygen/src/menu/char_selection/scene.rs index ea57582854..87bbcdb02d 100644 --- a/voxygen/src/menu/char_selection/scene.rs +++ b/voxygen/src/menu/char_selection/scene.rs @@ -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()); diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 3a7264f18a..b98027c35a 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -13,7 +13,7 @@ use conrod_core::{ color, color::TRANSPARENT, widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Scrollbar, Text, TextBox}, - widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, + widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget, }; widget_ids! { @@ -86,10 +86,13 @@ widget_ids! { eyebrows_slider, eyebrows_text, beard_slider, - beard_slider_2, beard_text, accessories_slider, accessories_text, + chest_slider, + chest_text, + pants_slider, + pants_text, // Buttons enter_world_button, @@ -132,8 +135,6 @@ widget_ids! { undead, elf, danari, - // Body Features - chest_slider, } } @@ -838,203 +839,134 @@ impl CharSelectionUi { .set(self.ids.axe_grey, ui_widgets);*/ // Sliders - + let (metamorph, slider_indicator, slider_range) = ( + self.fonts.metamorph, + self.imgs.slider_indicator, + self.imgs.slider_range, + ); + let char_slider = move |prev_id, + text, + text_id, + max, + selected_val, + slider_id, + ui_widgets: &mut UiCell| { + Text::new(text) + .down_from(prev_id, 22.0) + .align_middle_x_of(prev_id) + .font_size(18) + .font_id(metamorph) + .color(TEXT_COLOR) + .set(text_id, ui_widgets); + ImageSlider::discrete(selected_val, 0, max, slider_indicator, slider_range) + .w_h(208.0, 22.0) + .down_from(text_id, 8.0) + .align_middle_x() + .track_breadth(12.0) + .slider_length(10.0) + .pad_track((5.0, 5.0)) + .set(slider_id, ui_widgets) + }; // Hair Style - Text::new("Hair Style") - .mid_bottom_with_margin_on(self.ids.creation_buttons_alignment_2, -40.0) - .font_size(18) - .font_id(self.fonts.metamorph) - .color(TEXT_COLOR) - .set(self.ids.hairstyle_text, ui_widgets); - let current_chest = self.character_body.chest; - if let Some(new_val) = ImageSlider::discrete( - humanoid::ALL_CHESTS + let current_hair_style = self.character_body.hair_style; + if let Some(new_val) = char_slider( + self.ids.creation_buttons_alignment_2, + "Hair Style", + self.ids.hairstyle_text, + humanoid::ALL_HAIR_STYLES.len() - 1, + humanoid::ALL_HAIR_STYLES .iter() - .position(|&c| c == current_chest) + .position(|&c| c == current_hair_style) .unwrap_or(0), - 0, - humanoid::ALL_CHESTS.len() - 1, - self.imgs.slider_indicator, - self.imgs.slider_range, - ) - .w_h(208.0, 22.0) - .mid_bottom_with_margin_on(self.ids.hairstyle_text, -30.0) - .track_breadth(12.0) - .slider_length(10.0) - .pad_track((5.0, 5.0)) - .set(self.ids.hairstyle_slider, ui_widgets) - { - self.character_body.chest = humanoid::ALL_CHESTS[new_val]; + self.ids.hairstyle_slider, + ui_widgets, + ) { + self.character_body.hair_style = humanoid::ALL_HAIR_STYLES[new_val]; } - // Hair Color - - Text::new("Hair Color") - .mid_bottom_with_margin_on(self.ids.hairstyle_slider, -40.0) - .font_size(18) - .font_id(self.fonts.metamorph) - .color(TEXT_COLOR) - .set(self.ids.haircolor_text, ui_widgets); - let current_chest = self.character_body.chest; - if let Some(new_val) = ImageSlider::discrete( - humanoid::ALL_CHESTS - .iter() - .position(|&c| c == current_chest) - .unwrap_or(0), - 0, - humanoid::ALL_CHESTS.len() - 1, - self.imgs.slider_indicator, - self.imgs.slider_range, - ) - .w_h(208.0, 22.0) - .mid_bottom_with_margin_on(self.ids.haircolor_text, -30.0) - .track_breadth(12.0) - .slider_length(10.0) - .pad_track((5.0, 5.0)) - .set(self.ids.haircolor_slider, ui_widgets) - { - self.character_body.chest = humanoid::ALL_CHESTS[new_val]; + if let Some(new_val) = char_slider( + self.ids.hairstyle_slider, + "Hair Color", + self.ids.haircolor_text, + self.character_body.race.num_hair_colors() - 1, + self.character_body.hair_color as usize, + self.ids.haircolor_slider, + ui_widgets, + ) { + self.character_body.hair_color = new_val as u8; } - // Skin - - Text::new("Skin") - .mid_bottom_with_margin_on(self.ids.haircolor_slider, -40.0) - .font_size(18) - .font_id(self.fonts.metamorph) - .color(TEXT_COLOR) - .set(self.ids.skin_text, ui_widgets); - let current_chest = self.character_body.chest; - if let Some(new_val) = ImageSlider::discrete( - humanoid::ALL_CHESTS - .iter() - .position(|&c| c == current_chest) - .unwrap_or(0), - 0, - humanoid::ALL_CHESTS.len() - 1, - self.imgs.slider_indicator, - self.imgs.slider_range, - ) - .w_h(208.0, 22.0) - .mid_bottom_with_margin_on(self.ids.skin_text, -30.0) - .track_breadth(12.0) - .slider_length(10.0) - .pad_track((5.0, 5.0)) - .set(self.ids.skin_slider, ui_widgets) - { - self.character_body.chest = humanoid::ALL_CHESTS[new_val]; + if let Some(new_val) = char_slider( + self.ids.haircolor_slider, + "Skin", + self.ids.skin_text, + self.character_body.race.num_skin_colors() - 1, + self.character_body.skin as usize, + self.ids.skin_slider, + ui_widgets, + ) { + self.character_body.skin = new_val as u8; } - - // EyeBrows - Text::new("Eyebrows") - .mid_bottom_with_margin_on(self.ids.skin_slider, -40.0) - .font_size(18) - .font_id(self.fonts.metamorph) - .color(TEXT_COLOR) - .set(self.ids.eyebrows_text, ui_widgets); - let current_chest = self.character_body.chest; - if let Some(new_val) = ImageSlider::discrete( - humanoid::ALL_CHESTS + // Eyebrows + let current_eyebrows = self.character_body.eyebrows; + if let Some(new_val) = char_slider( + self.ids.skin_slider, + "Eyebrows", + self.ids.eyebrows_text, + humanoid::ALL_EYEBROWS.len() - 1, + humanoid::ALL_EYEBROWS .iter() - .position(|&c| c == current_chest) + .position(|&c| c == current_eyebrows) .unwrap_or(0), - 0, - humanoid::ALL_CHESTS.len() - 1, - self.imgs.slider_indicator, - self.imgs.slider_range, - ) - .w_h(208.0, 22.0) - .mid_bottom_with_margin_on(self.ids.eyebrows_text, -30.0) - .track_breadth(12.0) - .slider_length(10.0) - .pad_track((5.0, 5.0)) - .set(self.ids.eyebrows_slider, ui_widgets) - { - self.character_body.chest = humanoid::ALL_CHESTS[new_val]; + self.ids.eyebrows_slider, + ui_widgets, + ) { + self.character_body.eyebrows = humanoid::ALL_EYEBROWS[new_val]; } - // EyeColor - Text::new("Eye Color") - .mid_bottom_with_margin_on(self.ids.eyebrows_slider, -40.0) - .font_size(18) - .font_id(self.fonts.metamorph) - .color(TEXT_COLOR) - .set(self.ids.eyecolor_text, ui_widgets); - let current_chest = self.character_body.chest; - if let Some(new_val) = ImageSlider::discrete( - humanoid::ALL_CHESTS - .iter() - .position(|&c| c == current_chest) - .unwrap_or(0), - 0, - humanoid::ALL_CHESTS.len() - 1, - self.imgs.slider_indicator, - self.imgs.slider_range, - ) - .w_h(208.0, 22.0) - .mid_bottom_with_margin_on(self.ids.eyecolor_text, -30.0) - .track_breadth(12.0) - .slider_length(10.0) - .pad_track((5.0, 5.0)) - .set(self.ids.eyecolor_slider, ui_widgets) - { - self.character_body.chest = humanoid::ALL_CHESTS[new_val]; + if let Some(new_val) = char_slider( + self.ids.eyebrows_slider, + "Eye Color", + self.ids.eyecolor_text, + self.character_body.race.num_eye_colors() - 1, + self.character_body.eye_color as usize, + self.ids.eyecolor_slider, + ui_widgets, + ) { + self.character_body.eye_color = new_val as u8; } // Accessories - Text::new("Accessories") - .mid_bottom_with_margin_on(self.ids.eyecolor_slider, -40.0) - .font_size(18) - .font_id(self.fonts.metamorph) - .color(TEXT_COLOR) - .set(self.ids.accessories_text, ui_widgets); - let current_chest = self.character_body.chest; - if let Some(new_val) = ImageSlider::discrete( - humanoid::ALL_CHESTS + let current_accessory = self.character_body.accessory; + if let Some(new_val) = char_slider( + self.ids.eyecolor_slider, + "Accessories", + self.ids.accessories_text, + humanoid::ALL_ACCESSORIES.len() - 1, + humanoid::ALL_ACCESSORIES .iter() - .position(|&c| c == current_chest) + .position(|&c| c == current_accessory) .unwrap_or(0), - 0, - humanoid::ALL_CHESTS.len() - 1, - self.imgs.slider_indicator, - self.imgs.slider_range, - ) - .w_h(208.0, 22.0) - .mid_bottom_with_margin_on(self.ids.accessories_text, -30.0) - .track_breadth(12.0) - .slider_length(10.0) - .pad_track((5.0, 5.0)) - .set(self.ids.accessories_slider, ui_widgets) - { - self.character_body.chest = humanoid::ALL_CHESTS[new_val]; + self.ids.accessories_slider, + ui_widgets, + ) { + self.character_body.accessory = humanoid::ALL_ACCESSORIES[new_val]; } - // Beard if let humanoid::BodyType::Male = self.character_body.body_type { - Text::new("Beard") - .mid_bottom_with_margin_on(self.ids.accessories_slider, -40.0) - .font_size(18) - .font_id(self.fonts.metamorph) - .color(TEXT_COLOR) - .set(self.ids.beard_text, ui_widgets); - - if let Some(new_val) = ImageSlider::discrete( - humanoid::ALL_CHESTS + let current_beard = self.character_body.beard; + if let Some(new_val) = char_slider( + self.ids.accessories_slider, + "Beard", + self.ids.beard_text, + humanoid::ALL_BEARDS.len() - 1, + humanoid::ALL_BEARDS .iter() - .position(|&c| c == current_chest) + .position(|&c| c == current_beard) .unwrap_or(0), - 0, - humanoid::ALL_CHESTS.len() - 1, - self.imgs.slider_indicator, - self.imgs.slider_range, - ) - .w_h(208.0, 22.0) - .mid_bottom_with_margin_on(self.ids.beard_text, -30.0) - .track_breadth(12.0) - .slider_length(10.0) - .pad_track((5.0, 5.0)) - .set(self.ids.beard_slider, ui_widgets) - { - self.character_body.chest = humanoid::ALL_CHESTS[new_val]; + self.ids.beard_slider, + ui_widgets, + ) { + self.character_body.beard = humanoid::ALL_BEARDS[new_val]; } } else { Text::new("Beard") @@ -1043,22 +975,47 @@ impl CharSelectionUi { .font_id(self.fonts.metamorph) .color(TEXT_COLOR_2) .set(self.ids.beard_text, ui_widgets); - if let Some(_val) = ImageSlider::continuous( - 5.0, - 0.0, - 10.0, - self.imgs.nothing, - self.imgs.slider_range, - ) - .w_h(208.0, 22.0) - .mid_bottom_with_margin_on(self.ids.beard_text, -30.0) - .track_breadth(12.0) - .slider_length(10.0) - .track_color(Color::Rgba(1.0, 1.0, 1.0, 0.2)) - .slider_color(Color::Rgba(1.0, 1.0, 1.0, 0.2)) - .pad_track((5.0, 5.0)) - .set(self.ids.beard_slider_2, ui_widgets) - {} + ImageSlider::discrete(5, 0, 10, self.imgs.nothing, self.imgs.slider_range) + .w_h(208.0, 22.0) + .mid_bottom_with_margin_on(self.ids.beard_text, -30.0) + .track_breadth(12.0) + .slider_length(10.0) + .track_color(Color::Rgba(1.0, 1.0, 1.0, 0.2)) + .slider_color(Color::Rgba(1.0, 1.0, 1.0, 0.2)) + .pad_track((5.0, 5.0)) + .set(self.ids.beard_slider, ui_widgets); + } + // Chest + let current_chest = self.character_body.chest; + if let Some(new_val) = char_slider( + self.ids.beard_slider, + "Chest", + self.ids.chest_text, + humanoid::ALL_CHESTS.len() - 1, + humanoid::ALL_CHESTS + .iter() + .position(|&c| c == current_chest) + .unwrap_or(0), + self.ids.chest_slider, + ui_widgets, + ) { + self.character_body.chest = humanoid::ALL_CHESTS[new_val]; + } + // Pants + let current_pants = self.character_body.pants; + if let Some(new_val) = char_slider( + self.ids.chest_slider, + "Pants", + self.ids.pants_text, + humanoid::ALL_PANTS.len() - 1, + humanoid::ALL_PANTS + .iter() + .position(|&c| c == current_pants) + .unwrap_or(0), + self.ids.pants_slider, + ui_widgets, + ) { + self.character_body.pants = humanoid::ALL_PANTS[new_val]; } } // Char Creation fin diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs deleted file mode 100644 index 3a868bf71b..0000000000 --- a/voxygen/src/scene/figure.rs +++ /dev/null @@ -1,1036 +0,0 @@ -use crate::{ - anim::{ - self, character::CharacterSkeleton, object::ObjectSkeleton, quadruped::QuadrupedSkeleton, - quadrupedmedium::QuadrupedMediumSkeleton, Animation as _, Skeleton, SkeletonAttr, - }, - mesh::Meshable, - render::{ - Consts, FigureBoneData, FigureLocals, FigurePipeline, Globals, Light, Mesh, Model, Renderer, - }, - scene::camera::{Camera, CameraMode}, -}; -use client::Client; -use common::{ - assets, - comp::{ - humanoid, item::Tool, object, quadruped, quadruped_medium, ActionState::*, Body, - CharacterState, Equipment, Item, Last, MovementState::*, Ori, Pos, Scale, Stats, Vel, - }, - figure::Segment, - terrain::TerrainChunkSize, - vol::VolSize, -}; -use dot_vox::DotVoxData; -use hashbrown::HashMap; -use log::debug; -use specs::{Entity as EcsEntity, Join}; -use std::{f32, time::Instant}; -use vek::*; - -const DAMAGE_FADE_COEFFICIENT: f64 = 5.0; - -#[derive(PartialEq, Eq, Hash, Clone)] -enum FigureKey { - Simple(Body), - Complex(Body, Option), -} - -pub struct FigureModelCache { - models: HashMap, SkeletonAttr), u64)>, -} - -impl FigureModelCache { - pub fn new() -> Self { - Self { - models: HashMap::new(), - } - } - - pub fn get_or_create_model( - &mut self, - renderer: &mut Renderer, - body: Body, - equipment: Option<&Equipment>, - tick: u64, - ) -> &(Model, 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 bone_meshes = match body { - Body::Humanoid(body) => [ - Some(Self::load_head(body.race, body.body_type)), - Some(Self::load_chest(body.chest)), - Some(Self::load_belt(body.belt)), - Some(Self::load_pants(body.pants)), - Some(Self::load_left_hand(body.hand)), - Some(Self::load_right_hand(body.hand)), - Some(Self::load_left_foot(body.foot)), - Some(Self::load_right_foot(body.foot)), - Some(Self::load_main(equipment.and_then(|e| e.main.as_ref()))), - Some(Self::load_left_shoulder(body.shoulder)), - Some(Self::load_right_shoulder(body.shoulder)), - Some(Self::load_draw()), - None, - None, - None, - None, - ], - Body::Quadruped(body) => [ - Some(Self::load_pig_head(body.head)), - Some(Self::load_pig_chest(body.chest)), - Some(Self::load_pig_leg_lf(body.leg_l)), - Some(Self::load_pig_leg_rf(body.leg_r)), - Some(Self::load_pig_leg_lb(body.leg_l)), - Some(Self::load_pig_leg_rb(body.leg_r)), - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - ], - Body::QuadrupedMedium(body) => [ - Some(Self::load_wolf_head_upper(body.head_upper)), - Some(Self::load_wolf_jaw(body.jaw)), - Some(Self::load_wolf_head_lower(body.head_lower)), - Some(Self::load_wolf_tail(body.tail)), - Some(Self::load_wolf_torso_back(body.torso_back)), - Some(Self::load_wolf_torso_mid(body.torso_mid)), - Some(Self::load_wolf_ears(body.ears)), - Some(Self::load_wolf_foot_lf(body.foot_lf)), - Some(Self::load_wolf_foot_rf(body.foot_rf)), - Some(Self::load_wolf_foot_lb(body.foot_lb)), - Some(Self::load_wolf_foot_rb(body.foot_rb)), - None, - None, - None, - None, - None, - ], - Body::Object(object) => [ - Some(Self::load_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) { - // TODO: Don't hard-code this. - self.models - .retain(|_, (_, last_used)| *last_used + 60 > tick); - } - - // TODO: Don't make this public. - pub fn load_mesh(mesh_name: &str, position: Vec3) -> Mesh { - let full_specifier: String = ["voxygen.voxel.", mesh_name].concat(); - Meshable::::generate_mesh( - &Segment::from(assets::load_expect::(full_specifier.as_str()).as_ref()), - position, - ) - .0 - } - - fn load_head(race: humanoid::Race, body_type: humanoid::BodyType) -> Mesh { - use humanoid::{BodyType::*, Race::*}; - - let (name, offset) = match (race, body_type) { - // z-value should be 0.25 of the total z - (Human, Male) => ("figure.head.head_human_male", Vec3::new(-7.0, -5.0, -2.25)), - (Human, Female) => ( - "figure.head.head_human_female", - Vec3::new(-7.0, -7.5, -3.25), - ), - (Elf, Male) => ("figure.head.head_elf_male", Vec3::new(-8.0, -5.0, -2.25)), - (Elf, Female) => ("figure.head.head_elf_female", Vec3::new(-8.0, -5.5, -3.0)), - (Dwarf, Male) => ("figure.head.head_dwarf_male", Vec3::new(-6.0, -5.0, -12.5)), - (Dwarf, Female) => ( - "figure.head.head_dwarf_female", - Vec3::new(-6.0, -6.0, -9.25), - ), - (Orc, Male) => ("figure.head.head_orc_male", Vec3::new(-8.0, -5.0, -2.50)), - (Orc, Female) => ("figure.head.head_orc_female", Vec3::new(-8.0, -8.0, -3.5)), - (Undead, Male) => ("figure.head.head_undead_male", Vec3::new(-5.5, -5.0, -2.5)), - (Undead, Female) => ( - "figure.head.head_undead_female", - Vec3::new(-6.0, -5.0, -2.5), - ), - (Danari, Male) => ("figure.head.head_danari_male", Vec3::new(-9.0, -5.0, -2.75)), - (Danari, Female) => ( - "figure.head.head_danari_female", - Vec3::new(-9.0, -7.5, -3.0), - ), - }; - Self::load_mesh(name, offset) - } - // loads models with different offsets - // fn load_beard(beard: Beard) -> Mesh { - // let (name, offset) = match beard { - // Beard::None => ("figure/body/empty", Vec3::new(0.0, 0.0, 0.0)), - // Beard::Human1 => ("figure/empty", Vec3::new(0.0, 0.0, 0.0)), - // }; - // Self::load_mesh(name, offset) - // } - - fn load_chest(chest: humanoid::Chest) -> Mesh { - use humanoid::Chest::*; - - Self::load_mesh( - match chest { - Blue => "armor.chest.chest_blue", - Brown => "armor.chest.chest_brown", - Dark => "armor.chest.chest_dark", - Green => "armor.chest.chest_green", - Orange => "armor.chest.chest_orange", - }, - Vec3::new(-6.0, -3.5, 0.0), - ) - } - - fn load_belt(belt: humanoid::Belt) -> Mesh { - use humanoid::Belt::*; - - Self::load_mesh( - match belt { - //Belt::Default => "figure/body/belt_male", - Dark => "armor.belt.belt_dark", - }, - Vec3::new(-5.0, -3.5, 0.0), - ) - } - - fn load_pants(pants: humanoid::Pants) -> Mesh { - use humanoid::Pants::*; - - Self::load_mesh( - match pants { - Blue => "armor.pants.pants_blue", - Brown => "armor.pants.pants_brown", - Dark => "armor.pants.pants_dark", - Green => "armor.pants.pants_green", - Orange => "armor.pants.pants_orange", - }, - Vec3::new(-5.0, -3.5, 0.0), - ) - } - - fn load_left_hand(hand: humanoid::Hand) -> Mesh { - Self::load_mesh( - match hand { - humanoid::Hand::Default => "figure.body.hand", - }, - Vec3::new(-2.0, -2.5, -2.0), - ) - } - - fn load_right_hand(hand: humanoid::Hand) -> Mesh { - Self::load_mesh( - match hand { - humanoid::Hand::Default => "figure.body.hand", - }, - Vec3::new(-2.0, -2.5, -2.0), - ) - } - - fn load_left_foot(foot: humanoid::Foot) -> Mesh { - use humanoid::Foot::*; - - Self::load_mesh( - match foot { - Dark => "armor.foot.foot_dark", - }, - Vec3::new(-2.5, -3.5, -9.0), - ) - } - - fn load_right_foot(foot: humanoid::Foot) -> Mesh { - use humanoid::Foot::*; - - Self::load_mesh( - match foot { - Dark => "armor.foot.foot_dark", - }, - Vec3::new(-2.5, -3.5, -9.0), - ) - } - - fn load_main(item: Option<&Item>) -> Mesh { - 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)), - _ => ("figure.empty", Vec3::default()), - }; - Self::load_mesh(name, offset) - } else { - Self::load_mesh("figure.empty", Vec3::default()) - } - } - - fn load_left_shoulder(shoulder: humanoid::Shoulder) -> Mesh { - Self::load_mesh( - match shoulder { - humanoid::Shoulder::None => "figure.empty", - humanoid::Shoulder::Brown1 => "armor.shoulder.shoulder_l_brown", - }, - Vec3::new(-2.5, -3.5, -1.5), - ) - } - - fn load_right_shoulder(shoulder: humanoid::Shoulder) -> Mesh { - Self::load_mesh( - match shoulder { - humanoid::Shoulder::None => "figure.empty", - humanoid::Shoulder::Brown1 => "armor.shoulder.shoulder_r_brown", - }, - Vec3::new(-2.5, -3.5, -1.5), - ) - } - - // TODO: Inventory - fn load_draw() -> Mesh { - Self::load_mesh("object.glider", Vec3::new(-26.0, -26.0, -5.0)) - } - - //fn load_right_equip(hand: humanoid::Hand) -> Mesh { - // Self::load_mesh( - // match hand { - // humanoid::Hand::Default => "figure/body/hand", - // }, - // Vec3::new(-2.0, -2.5, -5.0), - // ) - //} - - ///////// - fn load_pig_head(head: quadruped::Head) -> Mesh { - Self::load_mesh( - match head { - quadruped::Head::Default => "npc.pig_purple.pig_head", - }, - Vec3::new(-6.0, 4.5, 3.0), - ) - } - - fn load_pig_chest(chest: quadruped::Chest) -> Mesh { - Self::load_mesh( - match chest { - quadruped::Chest::Default => "npc.pig_purple.pig_chest", - }, - Vec3::new(-5.0, 4.5, 0.0), - ) - } - - fn load_pig_leg_lf(leg_l: quadruped::LegL) -> Mesh { - Self::load_mesh( - match leg_l { - quadruped::LegL::Default => "npc.pig_purple.pig_leg_l", - }, - Vec3::new(0.0, -1.0, -1.5), - ) - } - - fn load_pig_leg_rf(leg_r: quadruped::LegR) -> Mesh { - Self::load_mesh( - match leg_r { - quadruped::LegR::Default => "npc.pig_purple.pig_leg_r", - }, - Vec3::new(0.0, -1.0, -1.5), - ) - } - - fn load_pig_leg_lb(leg_l: quadruped::LegL) -> Mesh { - Self::load_mesh( - match leg_l { - quadruped::LegL::Default => "npc.pig_purple.pig_leg_l", - }, - Vec3::new(0.0, -1.0, -1.5), - ) - } - - fn load_pig_leg_rb(leg_r: quadruped::LegR) -> Mesh { - Self::load_mesh( - match leg_r { - quadruped::LegR::Default => "npc.pig_purple.pig_leg_r", - }, - Vec3::new(0.0, -1.0, -1.5), - ) - } - ////// - fn load_wolf_head_upper(upper_head: quadruped_medium::HeadUpper) -> Mesh { - Self::load_mesh( - match upper_head { - quadruped_medium::HeadUpper::Default => "npc.wolf.wolf_head_upper", - }, - Vec3::new(-7.0, -6.0, -5.5), - ) - } - - fn load_wolf_jaw(jaw: quadruped_medium::Jaw) -> Mesh { - Self::load_mesh( - match jaw { - quadruped_medium::Jaw::Default => "npc.wolf.wolf_jaw", - }, - Vec3::new(-3.0, -3.0, -2.5), - ) - } - - fn load_wolf_head_lower(head_lower: quadruped_medium::HeadLower) -> Mesh { - Self::load_mesh( - match head_lower { - quadruped_medium::HeadLower::Default => "npc.wolf.wolf_head_lower", - }, - Vec3::new(-7.0, -6.0, -5.5), - ) - } - - fn load_wolf_tail(tail: quadruped_medium::Tail) -> Mesh { - Self::load_mesh( - match tail { - quadruped_medium::Tail::Default => "npc.wolf.wolf_tail", - }, - Vec3::new(-2.0, -12.0, -5.0), - ) - } - - fn load_wolf_torso_back(torso_back: quadruped_medium::TorsoBack) -> Mesh { - Self::load_mesh( - match torso_back { - quadruped_medium::TorsoBack::Default => "npc.wolf.wolf_torso_back", - }, - Vec3::new(-7.0, -6.0, -6.0), - ) - } - - fn load_wolf_torso_mid(torso_mid: quadruped_medium::TorsoMid) -> Mesh { - Self::load_mesh( - match torso_mid { - quadruped_medium::TorsoMid::Default => "npc.wolf.wolf_torso_mid", - }, - Vec3::new(-8.0, -5.5, -6.0), - ) - } - - fn load_wolf_ears(ears: quadruped_medium::Ears) -> Mesh { - Self::load_mesh( - match ears { - quadruped_medium::Ears::Default => "npc.wolf.wolf_ears", - }, - Vec3::new(-4.0, -1.0, -1.0), - ) - } - - fn load_wolf_foot_lf(foot_lf: quadruped_medium::FootLF) -> Mesh { - Self::load_mesh( - match foot_lf { - quadruped_medium::FootLF::Default => "npc.wolf.wolf_foot_lf", - }, - Vec3::new(-2.5, -4.0, -2.5), - ) - } - - fn load_wolf_foot_rf(foot_rf: quadruped_medium::FootRF) -> Mesh { - Self::load_mesh( - match foot_rf { - quadruped_medium::FootRF::Default => "npc.wolf.wolf_foot_rf", - }, - Vec3::new(-2.5, -4.0, -2.5), - ) - } - - fn load_wolf_foot_lb(foot_lb: quadruped_medium::FootLB) -> Mesh { - Self::load_mesh( - match foot_lb { - quadruped_medium::FootLB::Default => "npc.wolf.wolf_foot_lb", - }, - Vec3::new(-2.5, -4.0, -2.5), - ) - } - - fn load_wolf_foot_rb(foot_rb: quadruped_medium::FootRB) -> Mesh { - Self::load_mesh( - match foot_rb { - quadruped_medium::FootRB::Default => "npc.wolf.wolf_foot_rb", - }, - Vec3::new(-2.5, -4.0, -2.5), - ) - } - - fn load_object(obj: object::Body) -> Mesh { - let (name, offset) = match obj { - object::Body::Bomb => ("object.bomb", Vec3::new(-5.5, -5.5, 0.0)), - object::Body::Scarecrow => ("object.scarecrow", Vec3::new(-9.5, -4.0, 0.0)), - object::Body::Cauldron => ("object.cauldron", Vec3::new(-10.0, -10.0, 0.0)), - object::Body::ChestVines => ("object.chest_vines", Vec3::new(-7.5, -6.0, 0.0)), - object::Body::Chest => ("object.chest", Vec3::new(-7.5, -6.0, 0.0)), - object::Body::ChestDark => ("object.chest_dark", Vec3::new(-7.5, -6.0, 0.0)), - object::Body::ChestDemon => ("object.chest_demon", Vec3::new(-7.5, -6.0, 0.0)), - object::Body::ChestGold => ("object.chest_gold", Vec3::new(-7.5, -6.0, 0.0)), - object::Body::ChestLight => ("object.chest_light", Vec3::new(-7.5, -6.0, 0.0)), - object::Body::ChestOpen => ("object.chest_open", Vec3::new(-7.5, -6.0, 0.0)), - object::Body::ChestSkull => ("object.chest_skull", Vec3::new(-7.5, -6.0, 0.0)), - object::Body::Pumpkin => ("object.pumpkin", Vec3::new(-5.5, -4.0, 0.0)), - object::Body::Pumpkin2 => ("object.pumpkin_2", Vec3::new(-5.0, -4.0, 0.0)), - object::Body::Pumpkin3 => ("object.pumpkin_3", Vec3::new(-5.0, -4.0, 0.0)), - object::Body::Pumpkin4 => ("object.pumpkin_4", Vec3::new(-5.0, -4.0, 0.0)), - object::Body::Pumpkin5 => ("object.pumpkin_5", Vec3::new(-4.0, -5.0, 0.0)), - object::Body::Campfire => ("object.campfire", Vec3::new(-9.0, -10.0, 0.0)), - object::Body::LanternGround => ("object.lantern_ground", Vec3::new(-3.5, -3.5, 0.0)), - object::Body::LanternGroundOpen => { - ("object.lantern_ground_open", Vec3::new(-3.5, -3.5, 0.0)) - } - object::Body::LanternStanding => { - ("object.lantern_standing", Vec3::new(-7.5, -3.5, 0.0)) - } - object::Body::LanternStanding2 => { - ("object.lantern_standing_2", Vec3::new(-11.5, -3.5, 0.0)) - } - object::Body::PotionRed => ("object.potion_red", Vec3::new(-2.0, -2.0, 0.0)), - object::Body::PotionBlue => ("object.potion_blue", Vec3::new(-2.0, -2.0, 0.0)), - object::Body::PotionGreen => ("object.potion_green", Vec3::new(-2.0, -2.0, 0.0)), - object::Body::Crate => ("object.crate", Vec3::new(-7.0, -7.0, 0.0)), - object::Body::Tent => ("object.tent", Vec3::new(-18.5, -19.5, 0.0)), - object::Body::WindowSpooky => ("object.window_spooky", Vec3::new(-15.0, -1.5, -1.0)), - object::Body::DoorSpooky => ("object.door_spooky", Vec3::new(-15.0, -4.5, 0.0)), - object::Body::Table => ("object.table", Vec3::new(-12.0, -8.0, 0.0)), - object::Body::Table2 => ("object.table_2", Vec3::new(-8.0, -8.0, 0.0)), - object::Body::Table3 => ("object.table_3", Vec3::new(-10.0, -10.0, 0.0)), - object::Body::Drawer => ("object.drawer", Vec3::new(-11.0, -7.5, 0.0)), - object::Body::BedBlue => ("object.bed_human_blue", Vec3::new(-11.0, -15.0, 0.0)), - object::Body::Anvil => ("object.anvil", Vec3::new(-3.0, -7.0, 0.0)), - object::Body::Gravestone => ("object.gravestone", Vec3::new(-5.0, -2.0, 0.0)), - object::Body::Gravestone2 => ("object.gravestone_2", Vec3::new(-8.5, -3.0, 0.0)), - object::Body::Chair => ("object.chair", Vec3::new(-5.0, -4.5, 0.0)), - object::Body::Chair2 => ("object.chair_2", Vec3::new(-5.0, -4.5, 0.0)), - object::Body::Chair3 => ("object.chair_3", Vec3::new(-5.0, -4.5, 0.0)), - object::Body::Bench => ("object.bench", Vec3::new(-8.8, -5.0, 0.0)), - object::Body::Carpet => ("object.carpet", Vec3::new(-14.0, -14.0, -0.5)), - object::Body::Bedroll => ("object.bedroll", Vec3::new(-11.0, -19.5, -0.5)), - object::Body::CarpetHumanRound => { - ("object.carpet_human_round", Vec3::new(-14.0, -14.0, -0.5)) - } - object::Body::CarpetHumanSquare => { - ("object.carpet_human_square", Vec3::new(-13.5, -14.0, -0.5)) - } - object::Body::CarpetHumanSquare2 => ( - "object.carpet_human_square_2", - Vec3::new(-13.5, -14.0, -0.5), - ), - object::Body::CarpetHumanSquircle => ( - "object.carpet_human_squircle", - Vec3::new(-21.0, -21.0, -0.5), - ), - object::Body::Pouch => ("object.pouch", Vec3::new(-5.5, -4.5, 0.0)), - }; - Self::load_mesh(name, offset) - } -} - -pub struct FigureMgr { - model_cache: FigureModelCache, - character_states: HashMap>, - quadruped_states: HashMap>, - quadruped_medium_states: HashMap>, - object_states: HashMap>, -} - -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::() - .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::(), - &ecs.read_storage::(), - &ecs.read_storage::(), - ecs.read_storage::().maybe(), - &ecs.read_storage::(), - ecs.read_storage::().maybe(), - ecs.read_storage::>().maybe(), - ecs.read_storage::().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, - lights: &Consts, - 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::(), - &ecs.read_storage::(), - &ecs.read_storage::(), - &ecs.read_storage::(), - ecs.read_storage::().maybe(), - ecs.read_storage::().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::() - .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 { - bone_consts: Consts, - locals: Consts, - last_movement_change: Instant, - last_action_change: Instant, - skeleton: S, - pos: Vec3, - ori: Vec3, -} - -impl FigureState { - 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, - ori: Vec3, - scale: f32, - col: Rgba, - 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::::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 { - &self.locals - } - - pub fn bone_consts(&self) -> &Consts { - &self.bone_consts - } - - pub fn skeleton_mut(&mut self) -> &mut S { - &mut self.skeleton - } -} diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs new file mode 100644 index 0000000000..b3d0cf24f4 --- /dev/null +++ b/voxygen/src/scene/figure/cache.rs @@ -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), +} + +pub struct FigureModelCache { + models: HashMap, 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, 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); + } +} diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs new file mode 100644 index 0000000000..695c2bc8d1 --- /dev/null +++ b/voxygen/src/scene/figure/load.rs @@ -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::(full_specifier.as_str()).as_ref()) +} +fn graceful_load_vox(mesh_name: &str) -> Arc { + let full_specifier: String = ["voxygen.voxel.", mesh_name].concat(); + match assets::load::(full_specifier.as_str()) { + Ok(dot_vox) => dot_vox, + Err(_) => { + error!("Could not load vox file for figure: {}", full_specifier); + assets::load_expect::("voxygen.voxel.not_found") + } + } +} +fn graceful_load_segment(mesh_name: &str) -> Segment { + Segment::from(graceful_load_vox(mesh_name).as_ref()) +} +fn graceful_load_mat_segment(mesh_name: &str) -> MatSegment { + MatSegment::from(graceful_load_vox(mesh_name).as_ref()) +} + +pub fn load_mesh(mesh_name: &str, position: Vec3) -> Mesh { + Meshable::::generate_mesh(&load_segment(mesh_name), position).0 +} + +fn color_segment( + mat_segment: MatSegment, + skin: Rgb, + hair_color: Rgb, + eye_color: EyeColor, +) -> Segment { + // TODO move some of the colors to common + mat_segment.to_segment(|mat| match mat { + Material::Skin => skin, + Material::Hair => hair_color, + // TODO add back multiple colors + Material::EyeLight => eye_color.light_rgb(), + Material::EyeDark => eye_color.dark_rgb(), + Material::EyeWhite => eye_color.white_rgb(), + }) +} + +fn recolor_greys(segment: Segment, color: Rgb) -> Segment { + use common::util::{linear_to_srgb, srgb_to_linear}; + + segment.map_rgb(|rgb| { + const BASE_GREY: f32 = 178.0; + if rgb.r == rgb.g && rgb.g == rgb.b { + let c1 = srgb_to_linear(rgb.map(|e| e as f32 / BASE_GREY)); + let c2 = srgb_to_linear(color.map(|e| e as f32 / 255.0)); + + linear_to_srgb(c1 * c2).map(|e| (e.min(1.0).max(0.0) * 255.0) as u8) + } else { + rgb + } + }) +} + +#[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>, + beard: HashMap>, + accessory: HashMap>, +} +#[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) -> Result { + Ok(ron::de::from_reader(buf_reader).expect("Error parsing humanoid head spec")) + } +} + +impl HumHeadSpec { + pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { + assets::load_watched::("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 { + 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::::generate_mesh( + &head, + Vec3::from(spec.offset) + origin_offset.map(|e| e as f32 * -1.0), + ) + .0 + } +} + +pub fn mesh_chest(chest: Chest) -> Mesh { + 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::::generate_mesh(&chest, Vec3::new(-6.0, -3.5, 0.0)).0 +} + +pub fn mesh_belt(belt: Belt) -> Mesh { + 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 { + 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::::generate_mesh( + &pants_segment, + Vec3::new(-5.0, -3.5, 0.0), + ) + .0 +} + +pub fn mesh_left_hand(hand: Hand) -> Mesh { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + load_mesh("object.glider", Vec3::new(-26.0, -26.0, -5.0)) +} + +//pub fn mesh_right_equip(hand: Hand) -> Mesh { +// 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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) +} diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs new file mode 100644 index 0000000000..d8b11c4b39 --- /dev/null +++ b/voxygen/src/scene/figure/mod.rs @@ -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>, + quadruped_states: HashMap>, + quadruped_medium_states: HashMap>, + object_states: HashMap>, +} + +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::() + .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::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ecs.read_storage::().maybe(), + &ecs.read_storage::(), + ecs.read_storage::().maybe(), + ecs.read_storage::>().maybe(), + ecs.read_storage::().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, + lights: &Consts, + 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::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ecs.read_storage::().maybe(), + ecs.read_storage::().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::() + .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 { + bone_consts: Consts, + locals: Consts, + last_movement_change: Instant, + last_action_change: Instant, + skeleton: S, + pos: Vec3, + ori: Vec3, +} + +impl FigureState { + 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, + ori: Vec3, + scale: f32, + col: Rgba, + 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::::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 { + &self.locals + } + + pub fn bone_consts(&self) -> &Consts { + &self.bone_consts + } + + pub fn skeleton_mut(&mut self) -> &mut S { + &mut self.skeleton + } +}