diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 06af9c0230..33fcc0c7d0 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -61,9 +61,22 @@ force_s3tc_enable = true ; but will reduce performance and features when a modern graphics card is available. renderpath = default -; Prefer GLSL shaders over ARB shaders (not recommended) +;;;;; EXPERIMENTAL ;;;;; +; Prefer GLSL shaders over ARB shaders (not recommended). REQUIRES gentangents=true preferglsl = false +; Generate tangents for normal and parallax mapping. REQUIRES preferglsl=true. Incompatible with gpuskinning. +gentangents = false + +; Quality level of shader effects (set to 0 for lowest) +materialmgr.quality = 9.0 + +; Maximum distance to display parallax effect. Set to 0 to disable parallax. +materialmgr.PARALLAX_DIST.max = 150 +; Maximum distance to display high quality parallax effect. +materialmgr.PARALLAX_HQ_DIST.max = 75 +;;;;;;;;;;;;;;;;;;;;;;;; + ; Replace alpha-blending with alpha-testing, for performance experiments forcealphatest = false diff --git a/binaries/data/mods/public/art/actors/flora/trees/aleppo_pine.xml b/binaries/data/mods/public/art/actors/flora/trees/aleppo_pine.xml index 65ad725e16..21a3fc519f 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/aleppo_pine.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/aleppo_pine.xml @@ -23,5 +23,5 @@ gaia/tree_pine_aleppo_3.dae - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/apple_bloom.xml b/binaries/data/mods/public/art/actors/flora/trees/apple_bloom.xml index fdc2fe9d56..8600aebcca 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/apple_bloom.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/apple_bloom.xml @@ -39,6 +39,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/apple_top_a.xml b/binaries/data/mods/public/art/actors/flora/trees/apple_top_a.xml index 09373545bb..656392684e 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/apple_top_a.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/apple_top_a.xml @@ -17,5 +17,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/apple_top_b.xml b/binaries/data/mods/public/art/actors/flora/trees/apple_top_b.xml index c5d7285303..baad34bb9d 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/apple_top_b.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/apple_top_b.xml @@ -17,5 +17,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/apple_top_c.xml b/binaries/data/mods/public/art/actors/flora/trees/apple_top_c.xml index 4437d2032a..d84ef41e7a 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/apple_top_c.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/apple_top_c.xml @@ -17,5 +17,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/apple_top_d.xml b/binaries/data/mods/public/art/actors/flora/trees/apple_top_d.xml index 3c2934251f..7c7692fb69 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/apple_top_d.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/apple_top_d.xml @@ -17,5 +17,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/baobab.xml b/binaries/data/mods/public/art/actors/flora/trees/baobab.xml index 38d8907087..3f32152b19 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/baobab.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/baobab.xml @@ -40,4 +40,5 @@ + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/baobab_top.xml b/binaries/data/mods/public/art/actors/flora/trees/baobab_top.xml index 4ee93994df..84a85cd16d 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/baobab_top.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/baobab_top.xml @@ -17,5 +17,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/baobab_top_b.xml b/binaries/data/mods/public/art/actors/flora/trees/baobab_top_b.xml index 8f0fd89b1d..2a636499e0 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/baobab_top_b.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/baobab_top_b.xml @@ -21,6 +21,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/baobab_top_c.xml b/binaries/data/mods/public/art/actors/flora/trees/baobab_top_c.xml index 4f34412942..9ed681c21e 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/baobab_top_c.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/baobab_top_c.xml @@ -21,6 +21,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/baobab_top_d.xml b/binaries/data/mods/public/art/actors/flora/trees/baobab_top_d.xml index 826f6ef64c..32b9359872 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/baobab_top_d.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/baobab_top_d.xml @@ -17,5 +17,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/carob.xml b/binaries/data/mods/public/art/actors/flora/trees/carob.xml index 84ccf06ad0..c65fc26e32 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/carob.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/carob.xml @@ -56,4 +56,5 @@ + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/carob_top.xml b/binaries/data/mods/public/art/actors/flora/trees/carob_top.xml index c895e35b8a..ea9e0e772c 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/carob_top.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/carob_top.xml @@ -35,5 +35,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/cypress_test.xml b/binaries/data/mods/public/art/actors/flora/trees/cypress_test.xml index 2d47bae257..d5a1b6420d 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/cypress_test.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/cypress_test.xml @@ -7,5 +7,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/dead_a_1.xml b/binaries/data/mods/public/art/actors/flora/trees/dead_a_1.xml index 2612a503cf..7117289233 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/dead_a_1.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/dead_a_1.xml @@ -12,6 +12,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/dead_a_2.xml b/binaries/data/mods/public/art/actors/flora/trees/dead_a_2.xml index 973256ded3..5f9dfc78d6 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/dead_a_2.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/dead_a_2.xml @@ -12,6 +12,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/deci_1.xml b/binaries/data/mods/public/art/actors/flora/trees/deci_1.xml index 1a595a93f0..ce362835fe 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/deci_1.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/deci_1.xml @@ -12,6 +12,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/deci_100percent.xml b/binaries/data/mods/public/art/actors/flora/trees/deci_100percent.xml index c816412e74..fd0e48dbff 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/deci_100percent.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/deci_100percent.xml @@ -12,6 +12,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/deci_50percent.xml b/binaries/data/mods/public/art/actors/flora/trees/deci_50percent.xml index 457a3d8340..09df85db73 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/deci_50percent.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/deci_50percent.xml @@ -12,6 +12,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/european_beech.xml b/binaries/data/mods/public/art/actors/flora/trees/european_beech.xml index b5e467e97a..4377bec2e2 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/european_beech.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/european_beech.xml @@ -25,5 +25,7 @@ + + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/european_beech_top.xml b/binaries/data/mods/public/art/actors/flora/trees/european_beech_top.xml index 9b21b08ab3..65563b32a0 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/european_beech_top.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/european_beech_top.xml @@ -32,5 +32,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/fig.xml b/binaries/data/mods/public/art/actors/flora/trees/fig.xml index 2e2f9bdb78..f0f9055a43 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/fig.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/fig.xml @@ -20,4 +20,6 @@ + + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/fig_top_a.xml b/binaries/data/mods/public/art/actors/flora/trees/fig_top_a.xml index aabf70c4f8..6aecd67747 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/fig_top_a.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/fig_top_a.xml @@ -17,5 +17,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/fig_top_b.xml b/binaries/data/mods/public/art/actors/flora/trees/fig_top_b.xml index 4473129d4e..cbbde1daac 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/fig_top_b.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/fig_top_b.xml @@ -17,5 +17,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/grass_1.xml b/binaries/data/mods/public/art/actors/flora/trees/grass_1.xml index 3c85a06985..e3d0a838dd 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/grass_1.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/grass_1.xml @@ -12,6 +12,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/lumbardypoplar.xml b/binaries/data/mods/public/art/actors/flora/trees/lumbardypoplar.xml index 1d969fb4f7..00719d30ba 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/lumbardypoplar.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/lumbardypoplar.xml @@ -24,4 +24,6 @@ + basic_trans_wind.xml + diff --git a/binaries/data/mods/public/art/actors/flora/trees/lumbardypoplar_top.xml b/binaries/data/mods/public/art/actors/flora/trees/lumbardypoplar_top.xml index b8884ec9a5..2a488120ce 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/lumbardypoplar_top.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/lumbardypoplar_top.xml @@ -30,6 +30,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/mediterranean_cypress.xml b/binaries/data/mods/public/art/actors/flora/trees/mediterranean_cypress.xml index d0651cb21a..87d15065d2 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/mediterranean_cypress.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/mediterranean_cypress.xml @@ -27,4 +27,6 @@ + + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/mediterranean_cypress_tall.xml b/binaries/data/mods/public/art/actors/flora/trees/mediterranean_cypress_tall.xml index 75ea27f203..a0572304ae 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/mediterranean_cypress_tall.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/mediterranean_cypress_tall.xml @@ -7,4 +7,6 @@ + + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/oak.xml b/binaries/data/mods/public/art/actors/flora/trees/oak.xml index e54f3389fa..2b575564d1 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/oak.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/oak.xml @@ -25,5 +25,7 @@ + + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/oak_large.xml b/binaries/data/mods/public/art/actors/flora/trees/oak_large.xml index e1e8a72e2c..9e5757c522 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/oak_large.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/oak_large.xml @@ -29,4 +29,6 @@ + basic_trans_wind.xml + diff --git a/binaries/data/mods/public/art/actors/flora/trees/oak_large_top.xml b/binaries/data/mods/public/art/actors/flora/trees/oak_large_top.xml index c0160700f4..8892a20c3e 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/oak_large_top.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/oak_large_top.xml @@ -21,6 +21,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/oak_old.xml b/binaries/data/mods/public/art/actors/flora/trees/oak_old.xml index 67214850ac..dc0d2aa6f9 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/oak_old.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/oak_old.xml @@ -27,6 +27,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/oak_top.xml b/binaries/data/mods/public/art/actors/flora/trees/oak_top.xml index 70da642864..290573af40 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/oak_top.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/oak_top.xml @@ -30,6 +30,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/orchard_top.xml b/binaries/data/mods/public/art/actors/flora/trees/orchard_top.xml index d26a11fdec..039774bb49 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/orchard_top.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/orchard_top.xml @@ -33,6 +33,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/orchard_tree.xml b/binaries/data/mods/public/art/actors/flora/trees/orchard_tree.xml index 43abf6b38a..8b48820999 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/orchard_tree.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/orchard_tree.xml @@ -50,5 +50,7 @@ + + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date.xml b/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date.xml index b40cc51fa0..421a97e487 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date.xml @@ -35,5 +35,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_patch.xml b/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_patch.xml index e51e41b828..b027bba2eb 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_patch.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_patch.xml @@ -23,5 +23,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_short.xml b/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_short.xml index f2200938bc..2568d617c5 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_short.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_short.xml @@ -29,5 +29,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_tall.xml b/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_tall.xml index f6048df2fd..a6a4fc5bc1 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_tall.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/palm_cretan_date_tall.xml @@ -32,5 +32,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/palm_d.xml b/binaries/data/mods/public/art/actors/flora/trees/palm_d.xml index e1e7edd5b2..4b8628c8a6 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/palm_d.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/palm_d.xml @@ -12,6 +12,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/palm_date.xml b/binaries/data/mods/public/art/actors/flora/trees/palm_date.xml index dfc90c627e..82340b57b6 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/palm_date.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/palm_date.xml @@ -33,6 +33,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/palm_e.xml b/binaries/data/mods/public/art/actors/flora/trees/palm_e.xml index fec528159e..2eba9a8833 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/palm_e.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/palm_e.xml @@ -12,6 +12,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/palm_medit_fan_palm.xml b/binaries/data/mods/public/art/actors/flora/trees/palm_medit_fan_palm.xml index 43ed4a17c7..1d88e644ef 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/palm_medit_fan_palm.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/palm_medit_fan_palm.xml @@ -27,6 +27,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/palm_senegal_date.xml b/binaries/data/mods/public/art/actors/flora/trees/palm_senegal_date.xml index 01092fc4f8..5682fdc73b 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/palm_senegal_date.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/palm_senegal_date.xml @@ -41,5 +41,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/pine.xml b/binaries/data/mods/public/art/actors/flora/trees/pine.xml index f6c5adf75d..acb2e905d4 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/pine.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/pine.xml @@ -68,5 +68,5 @@ gaia/tree_pine_06.dae - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/pine_animated.xml b/binaries/data/mods/public/art/actors/flora/trees/pine_animated.xml index 5de428c05e..5c380f9127 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/pine_animated.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/pine_animated.xml @@ -18,5 +18,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/pine_w.xml b/binaries/data/mods/public/art/actors/flora/trees/pine_w.xml index e6c09bcafd..6e39451dbc 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/pine_w.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/pine_w.xml @@ -62,5 +62,5 @@ gaia/tree_pine_03.dae - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/poplar.xml b/binaries/data/mods/public/art/actors/flora/trees/poplar.xml index c98626433c..d9be417013 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/poplar.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/poplar.xml @@ -35,5 +35,7 @@ + + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/poplar_top.xml b/binaries/data/mods/public/art/actors/flora/trees/poplar_top.xml index 66606d4453..f8746dfc5f 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/poplar_top.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/poplar_top.xml @@ -36,6 +36,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/snow_pine2.xml b/binaries/data/mods/public/art/actors/flora/trees/snow_pine2.xml index bc3964939a..7858cbc90f 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/snow_pine2.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/snow_pine2.xml @@ -12,6 +12,6 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/flora/trees/tree_dead.xml b/binaries/data/mods/public/art/actors/flora/trees/tree_dead.xml index b5709dd5ef..f6eb290e5a 100644 --- a/binaries/data/mods/public/art/actors/flora/trees/tree_dead.xml +++ b/binaries/data/mods/public/art/actors/flora/trees/tree_dead.xml @@ -14,5 +14,5 @@ - basic_trans.xml + basic_trans_wind.xml diff --git a/binaries/data/mods/public/art/actors/structures/romans/civic_centre.xml b/binaries/data/mods/public/art/actors/structures/romans/civic_centre.xml index 07143a4cf8..9162183606 100644 --- a/binaries/data/mods/public/art/actors/structures/romans/civic_centre.xml +++ b/binaries/data/mods/public/art/actors/structures/romans/civic_centre.xml @@ -14,7 +14,12 @@ - + + + + + + @@ -36,5 +41,5 @@ - player_trans.xml + player_trans_ao_parallax_spec.xml diff --git a/binaries/data/mods/public/art/actors/structures/romans/temple_mars2.xml b/binaries/data/mods/public/art/actors/structures/romans/temple_mars2.xml new file mode 100644 index 0000000000..15815a829e --- /dev/null +++ b/binaries/data/mods/public/art/actors/structures/romans/temple_mars2.xml @@ -0,0 +1,36 @@ + + + + + + structural/rome_temple_big.dae + + + + + + + + + + + + + + + + + + + + + + + + + + + + + player_trans_ao_parallax_spec.xml + diff --git a/binaries/data/mods/public/art/materials/alphatest_ao_parallax_spec.xml b/binaries/data/mods/public/art/materials/alphatest_ao_parallax_spec.xml new file mode 100644 index 0000000000..6f25c12054 --- /dev/null +++ b/binaries/data/mods/public/art/materials/alphatest_ao_parallax_spec.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/art/materials/basic_trans_ao_parallax_spec.xml b/binaries/data/mods/public/art/materials/basic_trans_ao_parallax_spec.xml new file mode 100644 index 0000000000..ccd3507eb8 --- /dev/null +++ b/binaries/data/mods/public/art/materials/basic_trans_ao_parallax_spec.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/art/materials/basic_trans_wind.xml b/binaries/data/mods/public/art/materials/basic_trans_wind.xml new file mode 100644 index 0000000000..c2d8d063cc --- /dev/null +++ b/binaries/data/mods/public/art/materials/basic_trans_wind.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/binaries/data/mods/public/art/materials/player_trans_ao_parallax_spec.xml b/binaries/data/mods/public/art/materials/player_trans_ao_parallax_spec.xml new file mode 100644 index 0000000000..7fabffd0f6 --- /dev/null +++ b/binaries/data/mods/public/art/materials/player_trans_ao_parallax_spec.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/art/materials/player_trans_ao_spec.xml b/binaries/data/mods/public/art/materials/player_trans_ao_spec.xml new file mode 100644 index 0000000000..67a507b0eb --- /dev/null +++ b/binaries/data/mods/public/art/materials/player_trans_ao_spec.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/art/materials/player_trans_parallax.xml b/binaries/data/mods/public/art/materials/player_trans_parallax.xml new file mode 100644 index 0000000000..bf49c74cb5 --- /dev/null +++ b/binaries/data/mods/public/art/materials/player_trans_parallax.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/art/materials/trans_wind.xml b/binaries/data/mods/public/art/materials/trans_wind.xml new file mode 100644 index 0000000000..d4576d7de1 --- /dev/null +++ b/binaries/data/mods/public/art/materials/trans_wind.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/binaries/data/mods/public/art/meshes/structural/roman_cc.dae b/binaries/data/mods/public/art/meshes/structural/roman_cc.dae index d4966e532a..237885ca19 100644 --- a/binaries/data/mods/public/art/meshes/structural/roman_cc.dae +++ b/binaries/data/mods/public/art/meshes/structural/roman_cc.dae @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53a96f36189692aeb367f363a4e6d694ac51c782907cd32da8d7a5f5276cf0e7 -size 1330307 +oid sha256:c8df132d6bfb39fb8dfc365f3ec1ea39ed43f7d61d301268df50a1357c777f71 +size 1195242 diff --git a/binaries/data/mods/public/art/meshes/structural/rome_temple_big.dae b/binaries/data/mods/public/art/meshes/structural/rome_temple_big.dae index cf1912b704..ec6bf4ab57 100644 --- a/binaries/data/mods/public/art/meshes/structural/rome_temple_big.dae +++ b/binaries/data/mods/public/art/meshes/structural/rome_temple_big.dae @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15c89f182e245315300ade387bf4f8dd6c573f0b735f66e8bca7418ed40e643b -size 2309230 +oid sha256:c88e23d68af3e4b253f1d548a3eed664fb49cb9cac0b7d222a9e8611f1a8eb06 +size 1443964 diff --git a/binaries/data/mods/public/art/textures/skins/structural/roman_cc_ao.png b/binaries/data/mods/public/art/textures/skins/structural/roman_cc_ao.png new file mode 100644 index 0000000000..87f4fad328 --- /dev/null +++ b/binaries/data/mods/public/art/textures/skins/structural/roman_cc_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:482be76b02aa329f9c4c12fbb9128ad1071203719d4d700710adf1679d163cb7 +size 1050873 diff --git a/binaries/data/mods/public/art/textures/skins/structural/roman_norm.png b/binaries/data/mods/public/art/textures/skins/structural/roman_norm.png new file mode 100644 index 0000000000..107876fdbe --- /dev/null +++ b/binaries/data/mods/public/art/textures/skins/structural/roman_norm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:570b28d95bb2fbbc83c55dad9bf580cefd19afc5133292156d1b8db6c86cc3e0 +size 813415 diff --git a/binaries/data/mods/public/art/textures/skins/structural/rome_struct_spec.png b/binaries/data/mods/public/art/textures/skins/structural/rome_struct_spec.png new file mode 100644 index 0000000000..9eb465c569 --- /dev/null +++ b/binaries/data/mods/public/art/textures/skins/structural/rome_struct_spec.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:844189276e0bdc01a4e720b3c4a18b03f5c09aa42ea279e45b34082877629e28 +size 115380 diff --git a/binaries/data/mods/public/art/textures/skins/structural/rome_temple_ao.png b/binaries/data/mods/public/art/textures/skins/structural/rome_temple_ao.png new file mode 100644 index 0000000000..c08de9b1fe --- /dev/null +++ b/binaries/data/mods/public/art/textures/skins/structural/rome_temple_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58ddba4e7727a0f74b7e759e628caac8d525ff2635b64cb0c4b4a335de9360c6 +size 106706 diff --git a/binaries/data/mods/public/shaders/glsl/model_common.fs b/binaries/data/mods/public/shaders/glsl/model_common.fs index 7a3302c6a1..395505dbad 100644 --- a/binaries/data/mods/public/shaders/glsl/model_common.fs +++ b/binaries/data/mods/public/shaders/glsl/model_common.fs @@ -2,8 +2,12 @@ uniform sampler2D baseTex; uniform sampler2D losTex; +uniform sampler2D aoTex; +uniform sampler2D normTex; +uniform sampler2D specTex; #if USE_SHADOW + varying vec4 v_shadow; #if USE_SHADOW_SAMPLER uniform sampler2DShadow shadowTex; #if USE_SHADOW_PCF @@ -24,20 +28,41 @@ uniform sampler2D losTex; uniform vec3 shadingColor; uniform vec3 ambient; +uniform vec3 sunColor; +uniform vec3 sunDir; -varying vec3 v_lighting; +varying vec4 v_lighting; varying vec2 v_tex; -varying vec4 v_shadow; varying vec2 v_los; +#if USE_INSTANCING && USE_AO + varying vec2 v_tex2; +#endif + #if USE_SPECULAR uniform float specularPower; uniform vec3 specularColor; - uniform vec3 sunColor; - varying vec3 v_normal; - varying vec3 v_half; #endif +#if USE_SPECULAR || USE_NORMAL_MAP || USE_SPECULAR_MAP || USE_PARALLAX_MAP || USE_AO + uniform vec4 effectSettings; +#endif + +#if USE_SPECULAR || USE_NORMAL_MAP || USE_SPECULAR_MAP || USE_PARALLAX_MAP + varying vec4 v_normal; + #if USE_INSTANCING && (USE_NORMAL_MAP || USE_PARALLAX_MAP) + varying vec4 v_tangent; + //varying vec3 v_bitangent; + #endif + #if USE_SPECULAR || USE_SPECULAR_MAP + varying vec3 v_half; + #endif + #if USE_INSTANCING && USE_PARALLAX_MAP + varying vec3 v_eyeVec; + #endif +#endif + + float get_shadow() { #if USE_SHADOW && !DISABLE_RECEIVE_SHADOWS @@ -47,12 +72,12 @@ float get_shadow() vec4 size = vec4(offset + 1.0, 2.0 - offset); vec4 weight = (vec4(2.0 - 1.0 / size.xy, 1.0 / size.zw - 1.0) + (v_shadow.xy - offset).xyxy) * shadowScale.zwzw; return (1.0/9.0)*dot(size.zxzx*size.wwyy, - vec4(shadow2D(shadowTex, vec3(weight.zw, v_shadow.z)).a, - shadow2D(shadowTex, vec3(weight.xw, v_shadow.z)).a, - shadow2D(shadowTex, vec3(weight.zy, v_shadow.z)).a, - shadow2D(shadowTex, vec3(weight.xy, v_shadow.z)).a)); + vec4(shadow2D(shadowTex, vec3(weight.zw, v_shadow.z)).r, + shadow2D(shadowTex, vec3(weight.xw, v_shadow.z)).r, + shadow2D(shadowTex, vec3(weight.zy, v_shadow.z)).r, + shadow2D(shadowTex, vec3(weight.xy, v_shadow.z)).r)); #else - return shadow2D(shadowTex, v_shadow.xyz).a; + return shadow2D(shadowTex, v_shadow.xyz).r; #endif #else if (v_shadow.z >= 1.0) @@ -66,7 +91,68 @@ float get_shadow() void main() { - vec4 tex = texture2D(baseTex, v_tex); + vec2 coord = v_tex; + + #if USE_PARALLAX_MAP || USE_NORMAL_MAP + vec3 bitangent = vec3(v_normal.w, v_tangent.w, v_lighting.w); + mat3 tbn = mat3(v_tangent.xyz, bitangent, v_normal.xyz); + #endif + + #if USE_PARALLAX_MAP + { + float h = texture2D(normTex, coord).a; + + vec3 eyeDir = normalize(v_eyeVec * tbn); + float dist = length(v_eyeVec); + + float s; + vec2 move; + float height = 1.0; + float scale = effectSettings.z; + + scale = (scale * (PARALLAX_DIST_MAX - dist)) / PARALLAX_DIST_MAX; + + float iter = 8.0; + #if USE_HQ_PARALLAX + iter = 16.0; + #endif + + s = 1.0 / iter; + move = vec2(-eyeDir.x, eyeDir.y) * scale / (eyeDir.z * iter); + vec2 nil = vec2(0.0); + + #define PARALLAX_ITER {\ + height -= s;\ + vec2 temp = (h < height) ? move : nil;\ + coord += temp;\ + h = texture2D(normTex, coord).a;\ + } + + // 8 iterations of parallax + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + + // an additional 8 iterations for the HQ parallax + #if USE_HQ_PARALLAX + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + PARALLAX_ITER + #endif + } + #endif + + vec4 tex = texture2D(baseTex, coord); // Alpha-test as early as possible #ifdef REQUIRE_ALPHA_GEQUAL @@ -91,18 +177,50 @@ void main() #endif #endif - vec3 sundiffuse = v_lighting; - - #if USE_SPECULAR - // Interpolated v_normal needs to be re-normalized since it varies - // significantly between adjacent vertexes; - // v_half changes very gradually so don't bother normalizing that - vec3 specular = sunColor * specularColor * pow(max(0.0, dot(normalize(v_normal), v_half)), specularPower); - #else - vec3 specular = vec3(0.0); + #if USE_SPECULAR || USE_SPECULAR_MAP || USE_NORMAL_MAP + vec3 normal = v_normal.xyz; #endif - vec3 color = (texdiffuse * sundiffuse + specular) * get_shadow() + texdiffuse * ambient; + #if USE_INSTANCING && USE_NORMAL_MAP + vec3 ntex = texture2D(normTex, coord).rgb * 2.0 - 1.0; + ntex.y = -ntex.y; + normal = normalize(tbn * ntex); + vec3 bumplight = max(dot(-sunDir, normal), 0.0) * sunColor; + vec3 sundiffuse = (bumplight - v_lighting.rgb) * effectSettings.x + v_lighting.rgb; + #else + vec3 sundiffuse = v_lighting.rgb; + #endif + + vec4 specular = vec4(0.0); + #if USE_SPECULAR || USE_SPECULAR_MAP + vec3 specCol; + float specPow; + #if USE_INSTANCING && USE_SPECULAR_MAP + vec4 s = texture2D(specTex, coord); + specCol = s.rgb; + specular.a = s.a; + specPow = effectSettings.y; + #else + specCol = specularColor; + specPow = specularPower; + #endif + specular.rgb = sunColor * specCol * pow(max(0.0, dot(normalize(normal), v_half)), specPow); + #endif + + vec3 color = (texdiffuse * sundiffuse + specular.rgb) * get_shadow(); + vec3 ambColor = texdiffuse * ambient; + + #if USE_INSTANCING && USE_AO + vec3 ao = texture2D(aoTex, v_tex2).rrr; + ao = mix(vec3(1.0), ao * 2.0, effectSettings.w); + ambColor *= ao; + #endif + + color += ambColor; + + #if USE_INSTANCING && USE_SPECULAR_MAP && USE_SELF_LIGHT + color = mix(texdiffuse, color, specular.a); + #endif #if !IGNORE_LOS float los = texture2D(losTex, v_los).a; diff --git a/binaries/data/mods/public/shaders/glsl/model_common.vs b/binaries/data/mods/public/shaders/glsl/model_common.vs index a1f787a68a..b7a1718f49 100644 --- a/binaries/data/mods/public/shaders/glsl/model_common.vs +++ b/binaries/data/mods/public/shaders/glsl/model_common.vs @@ -17,19 +17,44 @@ uniform mat4 instancingTransform; uniform vec4 shadowScale; #endif -varying vec3 v_lighting; +#if USE_WIND + uniform vec4 time; + uniform vec4 windData; +#endif + +varying vec4 v_lighting; varying vec2 v_tex; -varying vec4 v_shadow; varying vec2 v_los; -#if USE_SPECULAR - varying vec3 v_normal; - varying vec3 v_half; +#if USE_SHADOW + varying vec4 v_shadow; +#endif + +#if USE_INSTANCING && USE_AO + varying vec2 v_tex2; +#endif + +#if USE_SPECULAR || USE_NORMAL_MAP || USE_SPECULAR_MAP || USE_PARALLAX_MAP + varying vec4 v_normal; + #if USE_INSTANCING && (USE_NORMAL_MAP || USE_PARALLAX_MAP) + varying vec4 v_tangent; + //varying vec3 v_bitangent; + #endif + #if USE_SPECULAR || USE_SPECULAR_MAP + varying vec3 v_half; + #endif + #if USE_INSTANCING && USE_PARALLAX_MAP + varying vec3 v_eyeVec; + #endif #endif attribute vec3 a_vertex; attribute vec3 a_normal; +#if USE_INSTANCING + attribute vec4 a_tangent; +#endif attribute vec2 a_uv0; +attribute vec2 a_uv1; #if USE_GPU_SKINNING const int MAX_INFLUENCES = 4; @@ -39,6 +64,14 @@ attribute vec2 a_uv0; attribute vec4 a_skinWeights; #endif + +vec4 fakeCos(vec4 x) +{ + vec4 tri = abs(fract(x + 0.5) * 2.0 - 1.0); + return tri * tri *(3.0 - 2.0 * tri); +} + + void main() { #if USE_GPU_SKINNING @@ -57,29 +90,90 @@ void main() #else #if USE_INSTANCING vec4 position = instancingTransform * vec4(a_vertex, 1.0); - vec3 normal = mat3(instancingTransform) * a_normal; + mat3 normalMatrix = mat3(instancingTransform[0].xyz, instancingTransform[1].xyz, instancingTransform[2].xyz); + vec3 normal = normalMatrix * a_normal; + #if (USE_NORMAL_MAP || USE_PARALLAX_MAP) + vec3 tangent = normalMatrix * a_tangent.xyz; + #endif #else vec4 position = vec4(a_vertex, 1.0); vec3 normal = a_normal; #endif #endif + +#if USE_WIND + vec2 wind = windData.xy; + + // fractional part of model position, clamped to >.4 + vec4 modelPos = instancingTransform[3]; + modelPos = fract(modelPos); + modelPos = clamp(modelPos, 0.4, 1); + + // crude measure of wind intensity + float abswind = abs(wind.x) + abs(wind.y); + + vec4 cosVec; + // these determine the speed of the wind's "cosine" waves. + cosVec.x = time.x * modelPos[0] + position.x; + cosVec.y = time.x * modelPos[2] / 3 + instancingTransform[3][0]; + cosVec.z = time.x * abswind / 4 + position.z; + + // calculate "cosines" in parallel, using a smoothed triangle wave + cosVec = fakeCos(cosVec); + + float limit = clamp((a_vertex.x * a_vertex.z * a_vertex.y) / 3000, 0, 0.2); + + float diff = cosVec.x * limit; + float diff2 = cosVec.y * clamp(a_vertex.y / 60, 0, 0.25); + + // fluttering of model parts based on distance from model center (ie longer branches) + position.xyz += cosVec.z * limit * clamp(abswind, 1.2, 1.7); + + // swaying of trunk based on distance from ground (higher parts sway more) + position.xz += diff + diff2 * wind; + #endif + + gl_Position = transform * position; - #if USE_SPECULAR - vec3 eyeVec = normalize(cameraPos.xyz - position.xyz); - vec3 sunVec = -sunDir; - v_half = normalize(sunVec + eyeVec); - v_normal = normal; + #if USE_SPECULAR || USE_NORMAL_MAP || USE_SPECULAR_MAP || USE_PARALLAX_MAP + v_normal.xyz = normal; + + #if USE_INSTANCING && (USE_NORMAL_MAP || USE_PARALLAX_MAP) + v_tangent.xyz = tangent; + vec3 bitangent = cross(v_normal.xyz, v_tangent.xyz) * a_tangent.w; + v_normal.w = bitangent.x; + v_tangent.w = bitangent.y; + v_lighting.w = bitangent.z; + #endif + + #if USE_SPECULAR || USE_SPECULAR_MAP || USE_PARALLAX_MAP + vec3 eyeVec = cameraPos.xyz - position.xyz; + #if USE_SPECULAR || USE_SPECULAR_MAP + vec3 sunVec = -sunDir; + v_half = normalize(sunVec + normalize(eyeVec)); + #endif + #if USE_INSTANCING && USE_PARALLAX_MAP + v_eyeVec = eyeVec; + #endif + #endif #endif - v_lighting = max(0.0, dot(normal, -sunDir)) * sunColor; + v_lighting.xyz = max(0.0, dot(normal, -sunDir)) * sunColor; + v_tex = a_uv0; + + #if USE_INSTANCING && USE_AO + v_tex2 = a_uv1; + #endif + #if USE_SHADOW v_shadow = shadowTransform * position; #if USE_SHADOW_SAMPLER && USE_SHADOW_PCF v_shadow.xy *= shadowScale.xy; #endif #endif + v_los = position.xz * losTransform.x + losTransform.y; } diff --git a/binaries/data/mods/public/shaders/glsl/model_common.xml b/binaries/data/mods/public/shaders/glsl/model_common.xml index 2b1e912aae..8b7c47ef7c 100644 --- a/binaries/data/mods/public/shaders/glsl/model_common.xml +++ b/binaries/data/mods/public/shaders/glsl/model_common.xml @@ -5,11 +5,14 @@ + + + diff --git a/binaries/data/mods/public/shaders/glsl/terrain_common.fs b/binaries/data/mods/public/shaders/glsl/terrain_common.fs index ee23c19d84..65fcfc0309 100644 --- a/binaries/data/mods/public/shaders/glsl/terrain_common.fs +++ b/binaries/data/mods/public/shaders/glsl/terrain_common.fs @@ -40,12 +40,12 @@ float get_shadow() vec4 size = vec4(offset + 1.0, 2.0 - offset); vec4 weight = (vec4(2.0 - 1.0 / size.xy, 1.0 / size.zw - 1.0) + (v_shadow.xy - offset).xyxy) * shadowScale.zwzw; return (1.0/9.0)*dot(size.zxzx*size.wwyy, - vec4(shadow2D(shadowTex, vec3(weight.zw, v_shadow.z)).a, - shadow2D(shadowTex, vec3(weight.xw, v_shadow.z)).a, - shadow2D(shadowTex, vec3(weight.zy, v_shadow.z)).a, - shadow2D(shadowTex, vec3(weight.xy, v_shadow.z)).a)); + vec4(shadow2D(shadowTex, vec3(weight.zw, v_shadow.z)).r, + shadow2D(shadowTex, vec3(weight.xw, v_shadow.z)).r, + shadow2D(shadowTex, vec3(weight.zy, v_shadow.z)).r, + shadow2D(shadowTex, vec3(weight.xy, v_shadow.z)).r)); #else - return shadow2D(shadowTex, v_shadow.xyz).a; + return shadow2D(shadowTex, v_shadow.xyz).r; #endif #else if (v_shadow.z >= 1.0) diff --git a/source/collada/PMDConvert.cpp b/source/collada/PMDConvert.cpp index 52f9061518..9c6b39d7eb 100644 --- a/source/collada/PMDConvert.cpp +++ b/source/collada/PMDConvert.cpp @@ -170,7 +170,6 @@ public: for (size_t i = 0; i < texcoordSources.size(); ++i) { dataTexcoords.push_back(texcoordSources[i]->GetData()); - assert(texcoordSources[i]->GetDataCount() == vertexCount*2); } // Transform mesh coordinate system to game coordinates @@ -444,7 +443,6 @@ public: for (size_t i = 0; i < texcoordSources.size(); ++i) { dataTexcoords.push_back(texcoordSources[i]->GetData()); - assert(texcoordSources[i]->GetDataCount() == vertexCount*2); } // Transform model coordinate system to game coordinates diff --git a/source/collada/PMDConvert.cpp.orig b/source/collada/PMDConvert.cpp.orig new file mode 100644 index 0000000000..52f9061518 --- /dev/null +++ b/source/collada/PMDConvert.cpp.orig @@ -0,0 +1,722 @@ +/* Copyright (C) 2012 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "PMDConvert.h" +#include "CommonConvert.h" + +#include "FCollada.h" +#include "FCDocument/FCDAsset.h" +#include "FCDocument/FCDocument.h" +#include "FCDocument/FCDocumentTools.h" +#include "FCDocument/FCDController.h" +#include "FCDocument/FCDControllerInstance.h" +#include "FCDocument/FCDGeometry.h" +#include "FCDocument/FCDGeometryMesh.h" +#include "FCDocument/FCDGeometryPolygons.h" +#include "FCDocument/FCDGeometryPolygonsInput.h" +#include "FCDocument/FCDGeometryPolygonsTools.h" +#include "FCDocument/FCDGeometrySource.h" +#include "FCDocument/FCDSceneNode.h" +#include "FCDocument/FCDSkinController.h" + +#include "StdSkeletons.h" +#include "Decompose.h" +#include "Maths.h" +#include "GeomReindex.h" + +#include +#include + +const size_t maxInfluences = 4; +struct VertexBlend +{ + uint8 bones[maxInfluences]; + float weights[maxInfluences]; +}; +VertexBlend defaultInfluences = { { 0xFF, 0xFF, 0xFF, 0xFF }, { 0, 0, 0, 0 } }; + +struct PropPoint +{ + std::string name; + float translation[3]; + float orientation[4]; + uint8 bone; +}; + +// Based on FMVector3::Normalize, but that function uses a static member +// FMVector3::XAxis which causes irritating linker errors. Rather than trying +// to make that XAxis work in a cross-platform way, just reimplement Normalize: +static FMVector3 FMVector3_Normalize(const FMVector3& vec) +{ + float l = vec.Length(); + if (l > 0.0f) + return FMVector3(vec.x/l, vec.y/l, vec.z/l); + else + return FMVector3(1.0f, 0.0f, 0.0f); +} + +static void AddStaticPropPoints(std::vector &propPoints, const FMMatrix44& upAxisTransform, FCDSceneNode* node) +{ + if (node->GetName().find("prop-") == 0 || node->GetName().find("prop_") == 0) + { + // Strip off the "prop-" from the name + std::string propPointName (node->GetName().substr(5)); + + Log(LOG_INFO, "Adding prop point %s", propPointName.c_str()); + + // CalculateWorldTransform applies transformations recursively for all parents of this node + // upAxisTransform transforms this node to right-handed Z_UP coordinates + + FMMatrix44 transform = upAxisTransform * node->CalculateWorldTransform(); + + HMatrix matrix; + memcpy(matrix, transform.Transposed().m, sizeof(matrix)); + + AffineParts parts; + decomp_affine(matrix, &parts); + + // Add prop point in game coordinates + + PropPoint p = { + propPointName, + + // Flip translation across the x-axis by swapping y and z + { parts.t.x, parts.t.z, parts.t.y }, + + // To convert the quaternions: imagine you're using the axis/angle + // representation, then swap the y,z basis vectors and change the + // direction of rotation by negating the angle ( => negating sin(angle) + // => negating x,y,z => changing (x,y,z,w) to (-x,-z,-y,w) + // but then (-x,-z,-y,w) == (x,z,y,-w) so do that instead) + { parts.q.x, parts.q.z, parts.q.y, -parts.q.w }, + + 0xff + }; + propPoints.push_back(p); + } + + // Search children for prop points + for (size_t i = 0; i < node->GetChildrenCount(); ++i) + AddStaticPropPoints(propPoints, upAxisTransform, node->GetChild(i)); +} + +class PMDConvert +{ +public: + /** + * Converts a COLLADA XML document into the PMD mesh format. + * + * @param input XML document to parse + * @param output callback for writing the PMD data; called lots of times + * with small strings + * @param xmlErrors output - errors reported by the XML parser + * @throws ColladaException on failure + */ + static void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors) + { + CommonConvert converter(input, xmlErrors); + + if (converter.GetInstance().GetEntity()->GetType() == FCDEntity::GEOMETRY) + { + Log(LOG_INFO, "Found static geometry"); + + FCDGeometryPolygons* polys = GetPolysFromGeometry((FCDGeometry*)converter.GetInstance().GetEntity()); + + // Convert the geometry into a suitable form for the game + ReindexGeometry(polys); + + std::vector boneWeights; // unused + std::vector boneTransforms; // unused + std::vector propPoints; + + // Get the raw vertex data + + FCDGeometryPolygonsInput* inputPosition = polys->FindInput(FUDaeGeometryInput::POSITION); + FCDGeometryPolygonsInput* inputNormal = polys->FindInput(FUDaeGeometryInput::NORMAL); + + const uint32* indicesCombined = inputPosition->GetIndices(); + size_t indicesCombinedCount = inputPosition->GetIndexCount(); + // (ReindexGeometry guarantees position/normal/texcoord have the same indexes) + + FCDGeometrySource* sourcePosition = inputPosition->GetSource(); + FCDGeometrySource* sourceNormal = inputNormal ->GetSource(); + + FCDGeometrySourceList texcoordSources; + polys->GetParent()->FindSourcesByType(FUDaeGeometryInput::TEXCOORD, texcoordSources); + + float* dataPosition = sourcePosition->GetData(); + float* dataNormal = sourceNormal ->GetData(); + size_t vertexCount = sourcePosition->GetDataCount() / 3; + assert(sourcePosition->GetDataCount() == vertexCount*3); + assert(sourceNormal ->GetDataCount() == vertexCount*3); + + std::vector dataTexcoords; + for (size_t i = 0; i < texcoordSources.size(); ++i) + { + dataTexcoords.push_back(texcoordSources[i]->GetData()); + assert(texcoordSources[i]->GetDataCount() == vertexCount*2); + } + + // Transform mesh coordinate system to game coordinates + // (doesn't modify prop points) + + TransformStaticModel(dataPosition, dataNormal, vertexCount, converter.GetEntityTransform(), converter.IsYUp()); + + // Add static prop points + // which are empty child nodes of the main parent + + // Default prop points are already given in game coordinates + AddDefaultPropPoints(propPoints); + + // Calculate transform to convert from COLLADA-defined up_axis to Z-up because + // it's relatively straightforward to convert that to game coordinates + FMMatrix44 upAxisTransform = FMMatrix44_Identity; + if (converter.IsYUp()) + { + // Prop points are rotated -90 degrees about the X-axis, reverse that rotation + // (do this once now because it's easier than messing with quaternions later) + upAxisTransform = FMMatrix44::XAxisRotationMatrix(1.57f); + } + + AddStaticPropPoints(propPoints, upAxisTransform, converter.GetInstance().GetParent()); + + WritePMD(output, indicesCombined, indicesCombinedCount, dataPosition, dataNormal, dataTexcoords, vertexCount, boneWeights, boneTransforms, propPoints); + } + else if (converter.GetInstance().GetType() == FCDEntityInstance::CONTROLLER) + { + Log(LOG_INFO, "Found skinned geometry"); + + FCDControllerInstance& controllerInstance = static_cast(converter.GetInstance()); + + // (NB: GetType is deprecated and should be replaced with HasType, + // except that has irritating linker errors when using a DLL, so don't + // bother) + + assert(converter.GetInstance().GetEntity()->GetType() == FCDEntity::CONTROLLER); // assume this is always true? + FCDController* controller = static_cast(converter.GetInstance().GetEntity()); + + FCDSkinController* skin = controller->GetSkinController(); + REQUIRE(skin != NULL, "is skin controller"); + + FixSkeletonRoots(controllerInstance); + + // Data for joints is stored in two places - avoid overflows by limiting + // to the minimum of the two sizes, and warn if they're different (which + // happens in practice for slightly-broken meshes) + size_t jointCount = std::min(skin->GetJointCount(), controllerInstance.GetJointCount()); + if (skin->GetJointCount() != controllerInstance.GetJointCount()) + { + Log(LOG_WARNING, "Mismatched bone counts (skin has %d, skeleton has %d)", + skin->GetJointCount(), controllerInstance.GetJointCount()); + for (size_t i = 0; i < skin->GetJointCount(); ++i) + Log(LOG_INFO, "Skin joint %d: %s", i, skin->GetJoint(i)->GetId().c_str()); + for (size_t i = 0; i < controllerInstance.GetJointCount(); ++i) + Log(LOG_INFO, "Skeleton joint %d: %s", i, controllerInstance.GetJoint(i)->GetName().c_str()); + } + + // Get the skinned mesh for this entity + FCDGeometry* baseGeometry = controller->GetBaseGeometry(); + REQUIRE(baseGeometry != NULL, "controller has base geometry"); + FCDGeometryPolygons* polys = GetPolysFromGeometry(baseGeometry); + + // Make sure it doesn't use more bones per vertex than the game can handle + SkinReduceInfluences(skin, maxInfluences, 0.001f); + + // Convert the geometry into a suitable form for the game + ReindexGeometry(polys, skin); + + const Skeleton& skeleton = FindSkeleton(controllerInstance); + + // Convert the bone influences into VertexBlend structures for the PMD: + + bool hasComplainedAboutNonexistentJoints = false; // because we want to emit a warning only once + + std::vector boneWeights; // one per vertex + + const FCDSkinControllerVertex* vertexInfluences = skin->GetVertexInfluences(); + for (size_t i = 0; i < skin->GetInfluenceCount(); ++i) + { + VertexBlend influences = defaultInfluences; + + assert(vertexInfluences[i].GetPairCount() <= maxInfluences); + // guaranteed by ReduceInfluences; necessary for avoiding + // out-of-bounds writes to the VertexBlend + + if (vertexInfluences[i].GetPairCount() == 0) + { + // Blender exports some models with vertices that have no influences, + // which I've not found details about in the COLLADA spec, however, + // it seems to work OK to treat these vertices the same as if they + // were only influenced by the bind-shape matrix (see comment below), + // so we use the same special case here. + influences.bones[0] = (uint8)jointCount; + influences.weights[0] = 1.0f; + } + + for (size_t j = 0; j < vertexInfluences[i].GetPairCount(); ++j) + { + if (vertexInfluences[i].GetPair(j)->jointIndex == -1) + { + // This is a special case we must handle, according to the COLLADA spec: + // "An index of -1 into the array of joints refers to the bind shape" + // + // which basically means when skinning the vertex it's relative to the + // bind-shape transform instead of an animated bone. Since our skinning + // is in world space, we will have already applied the bind-shape transform, + // so we don't have to worry about that, though we DO have to apply the + // world space transform of the model for the indicated vertex. + // + // To indicate this special case, we use a bone ID set to the total number + // of bones in the model, which will have a special "bone matrix" reserved + // that contains the world space transform of the model during skinning. + // (see http://trac.wildfiregames.com/ticket/1012) + influences.bones[j] = (uint8)jointCount; + influences.weights[j] = vertexInfluences[i].GetPair(j)->weight; + } + else + { + // Check for less than 254 joints because we store them in a u8, + // 0xFF is a reserved value (no influence), and we reserve one slot + // for the above special case. + uint32 jointIdx = vertexInfluences[i].GetPair(j)->jointIndex; + REQUIRE(jointIdx < 0xFE, "sensible number of joints (<254)"); + + // Find the joint on the skeleton, after checking it really exists + FCDSceneNode* joint = NULL; + if (jointIdx < controllerInstance.GetJointCount()) + joint = controllerInstance.GetJoint(jointIdx); + + // Complain on error + if (! joint) + { + if (! hasComplainedAboutNonexistentJoints) + { + Log(LOG_WARNING, "Vertexes influenced by nonexistent joint"); + hasComplainedAboutNonexistentJoints = true; + } + continue; + } + + // Store into the VertexBlend + int boneId = skeleton.GetBoneID(joint->GetName().c_str()); + if (boneId < 0) + { + // The relevant joint does exist, but it's not a recognised + // bone in our chosen skeleton structure + Log(LOG_ERROR, "Vertex influenced by unrecognised bone '%s'", joint->GetName().c_str()); + continue; + } + + influences.bones[j] = (uint8)boneId; + influences.weights[j] = vertexInfluences[i].GetPair(j)->weight; + } + } + + boneWeights.push_back(influences); + } + + // Convert the bind pose into BoneTransform structures for the PMD: + + BoneTransform boneDefault = { { 0, 0, 0 }, { 0, 0, 0, 1 } }; // identity transform + std::vector boneTransforms (skeleton.GetBoneCount(), boneDefault); + + for (size_t i = 0; i < jointCount; ++i) + { + FCDSceneNode* joint = controllerInstance.GetJoint(i); + + int boneId = skeleton.GetRealBoneID(joint->GetName().c_str()); + if (boneId < 0) + { + // unrecognised joint - it's probably just a prop point + // or something, so ignore it + continue; + } + + FMMatrix44 bindPose = skin->GetJoint(i)->GetBindPoseInverse().Inverted(); + + HMatrix matrix; + memcpy(matrix, bindPose.Transposed().m, sizeof(matrix)); + // set matrix = bindPose^T, to match what decomp_affine wants + + AffineParts parts; + decomp_affine(matrix, &parts); + + BoneTransform b = { + { parts.t.x, parts.t.y, parts.t.z }, + { parts.q.x, parts.q.y, parts.q.z, parts.q.w } + }; + + boneTransforms[boneId] = b; + } + + // Construct the list of prop points. + // Currently takes all objects that are directly attached to a + // standard bone, and whose name begins with "prop-" or "prop_". + + std::vector propPoints; + AddDefaultPropPoints(propPoints); + + for (size_t i = 0; i < jointCount; ++i) + { + FCDSceneNode* joint = controllerInstance.GetJoint(i); + + int boneId = skeleton.GetBoneID(joint->GetName().c_str()); + if (boneId < 0) + { + // unrecognised joint name - ignore, same as before + continue; + } + + // Check all the objects attached to this bone + for (size_t j = 0; j < joint->GetChildrenCount(); ++j) + { + FCDSceneNode* child = joint->GetChild(j); + if (child->GetName().find("prop-") != 0 && child->GetName().find("prop_") != 0) + { + // doesn't begin with "prop-", so skip it + continue; + } + // Strip off the "prop-" from the name + std::string propPointName (child->GetName().substr(5)); + + Log(LOG_INFO, "Adding prop point %s", propPointName.c_str()); + + // Get translation and orientation of local transform + + FMMatrix44 localTransform = child->ToMatrix(); + + HMatrix matrix; + memcpy(matrix, localTransform.Transposed().m, sizeof(matrix)); + + AffineParts parts; + decomp_affine(matrix, &parts); + + // Add prop point to list + + PropPoint p = { + propPointName, + { parts.t.x, parts.t.y, parts.t.z }, + { parts.q.x, parts.q.y, parts.q.z, parts.q.w }, + (uint8)boneId + }; + propPoints.push_back(p); + } + } + + // Get the raw vertex data + + FCDGeometryPolygonsInput* inputPosition = polys->FindInput(FUDaeGeometryInput::POSITION); + FCDGeometryPolygonsInput* inputNormal = polys->FindInput(FUDaeGeometryInput::NORMAL); + + const uint32* indicesCombined = inputPosition->GetIndices(); + size_t indicesCombinedCount = inputPosition->GetIndexCount(); + // (ReindexGeometry guarantees position/normal/texcoord have the same indexes) + + FCDGeometrySource* sourcePosition = inputPosition->GetSource(); + FCDGeometrySource* sourceNormal = inputNormal ->GetSource(); + + FCDGeometrySourceList texcoordSources; + polys->GetParent()->FindSourcesByType(FUDaeGeometryInput::TEXCOORD, texcoordSources); + + float* dataPosition = sourcePosition->GetData(); + float* dataNormal = sourceNormal ->GetData(); + size_t vertexCount = sourcePosition->GetDataCount() / 3; + assert(sourcePosition->GetDataCount() == vertexCount*3); + assert(sourceNormal ->GetDataCount() == vertexCount*3); + + std::vector dataTexcoords; + for (size_t i = 0; i < texcoordSources.size(); ++i) + { + dataTexcoords.push_back(texcoordSources[i]->GetData()); + assert(texcoordSources[i]->GetDataCount() == vertexCount*2); + } + + // Transform model coordinate system to game coordinates + + TransformSkinnedModel(dataPosition, dataNormal, vertexCount, boneTransforms, propPoints, + converter.GetEntityTransform(), skin->GetBindShapeTransform(), + converter.IsYUp(), converter.IsXSI()); + + WritePMD(output, indicesCombined, indicesCombinedCount, dataPosition, dataNormal, dataTexcoords, vertexCount, boneWeights, boneTransforms, propPoints); + } + else + { + throw ColladaException("Unrecognised object type"); + } + + } + + /** + * Adds the default "root" prop-point. + */ + static void AddDefaultPropPoints(std::vector& propPoints) + { + PropPoint root; + root.name = "root"; + root.translation[0] = root.translation[1] = root.translation[2] = 0.0f; + root.orientation[0] = root.orientation[1] = root.orientation[2] = 0.0f; + root.orientation[3] = 1.0f; + root.bone = 0xFF; + propPoints.push_back(root); + } + + /** + * Writes the model data in the PMD format. + */ + static void WritePMD(OutputCB& output, + const uint32* indices, size_t indexCount, + const float* position, const float* normal, + const std::vector& texcoords, + size_t vertexCount, + const std::vector& boneWeights, const std::vector& boneTransforms, + const std::vector& propPoints) + { + static const VertexBlend noBlend = { { 0xFF, 0xFF, 0xFF, 0xFF }, { 0, 0, 0, 0 } }; + + size_t faceCount = indexCount/3; + size_t boneCount = boneTransforms.size(); + if (boneCount) + assert(boneWeights.size() == vertexCount); + + size_t propPointsSize = 0; // can't calculate this statically, so loop over all the prop points + for (size_t i = 0; i < propPoints.size(); ++i) + { + propPointsSize += 4 + propPoints[i].name.length(); + propPointsSize += 3*4 + 4*4 + 1; + } + + output("PSMD", 4); // magic number + write(output, (uint32)4); // version number + write(output, (uint32)( + // for UVs, we add one uint32 (i.e. 4 bytes) per model that gives the number of + // texcoord sets in the model, plus 2 floats per new UV + // pair per vertex (i.e. 8 bytes * number of pairs * vertex count) + 4 + 11*4*vertexCount + 4 + 8*texcoords.size()*vertexCount + // vertices + 4 + 6*faceCount + // faces + 4 + 7*4*boneCount + // bones + 4 + propPointsSize // props + )); // data size + + // Vertex data + write(output, (uint32)vertexCount); + write(output, (uint32)texcoords.size()); // UV pairs per vertex + for (size_t i = 0; i < vertexCount; ++i) + { + output((char*)&position[i*3], 12); + output((char*)&normal [i*3], 12); + + for (size_t s = 0; s < texcoords.size(); ++s) + { + output((char*)&texcoords[s][i*2], 8); + } + + if (boneCount) + write(output, boneWeights[i]); + else + write(output, noBlend); + } + + // Face data + write(output, (uint32)faceCount); + for (size_t i = 0; i < indexCount; ++i) + { + write(output, (uint16)indices[i]); + } + + // Bones data + write(output, (uint32)boneCount); + for (size_t i = 0; i < boneCount; ++i) + { + output((char*)&boneTransforms[i], 7*4); + } + + // Prop points data + write(output, (uint32)propPoints.size()); + for (size_t i = 0; i < propPoints.size(); ++i) + { + uint32 nameLen = (uint32)propPoints[i].name.length(); + write(output, nameLen); + output(propPoints[i].name.c_str(), nameLen); + write(output, propPoints[i].translation); + write(output, propPoints[i].orientation); + write(output, propPoints[i].bone); + } + } + + static FCDGeometryPolygons* GetPolysFromGeometry(FCDGeometry* geom) + { + REQUIRE(geom->IsMesh(), "geometry is mesh"); + FCDGeometryMesh* mesh = geom->GetMesh(); + + if (! mesh->IsTriangles()) + FCDGeometryPolygonsTools::Triangulate(mesh); + + REQUIRE(mesh->IsTriangles(), "mesh is made of triangles"); + REQUIRE(mesh->GetPolygonsCount() == 1, "mesh has single set of polygons"); + FCDGeometryPolygons* polys = mesh->GetPolygons(0); + REQUIRE(polys->FindInput(FUDaeGeometryInput::POSITION) != NULL, "mesh has vertex positions"); + REQUIRE(polys->FindInput(FUDaeGeometryInput::NORMAL) != NULL, "mesh has vertex normals"); + REQUIRE(polys->FindInput(FUDaeGeometryInput::TEXCOORD) != NULL, "mesh has vertex tex coords"); + return polys; + } + + /** + * Applies world-space transform to vertex data and transforms Collada's right-handed + * Y-up / Z-up coordinates to the game's left-handed Y-up coordinate system + * + * TODO: Maybe we should use FCDocumentTools::StandardizeUpAxisAndLength in addition + * to this, so we'd only have one up-axis case to worry about, but it doesn't seem to + * correctly adjust the prop points in Y_UP models. + */ + static void TransformStaticModel(float* position, float* normal, size_t vertexCount, + const FMMatrix44& transform, bool yUp) + { + for (size_t i = 0; i < vertexCount; ++i) + { + FMVector3 pos (&position[i*3], 0); + FMVector3 norm (&normal[i*3], 0); + + // Apply the scene-node transforms + pos = transform.TransformCoordinate(pos); + norm = FMVector3_Normalize(transform.TransformVector(norm)); + + // Convert from right-handed Y_UP or Z_UP to the game's coordinate system (left-handed Y-up) + + if (yUp) + { + pos.z = -pos.z; + norm.z = -norm.z; + } + else + { + std::swap(pos.y, pos.z); + std::swap(norm.y, norm.z); + } + + // Copy back to array + + position[i*3] = pos.x; + position[i*3+1] = pos.y; + position[i*3+2] = pos.z; + + normal[i*3] = norm.x; + normal[i*3+1] = norm.y; + normal[i*3+2] = norm.z; + } + } + + /** + * Applies world-space transform to vertex data and transforms Collada's right-handed + * Y-up / Z-up coordinates to the game's left-handed Y-up coordinate system + * + * TODO: Maybe we should use FCDocumentTools::StandardizeUpAxisAndLength in addition + * to this, so we'd only have one up-axis case to worry about, but it doesn't seem to + * correctly adjust the prop points in Y_UP models. + */ + static void TransformSkinnedModel(float* position, float* normal, size_t vertexCount, + std::vector& bones, std::vector& propPoints, + const FMMatrix44& transform, const FMMatrix44& bindTransform, bool yUp, bool isXSI) + { + FMMatrix44 scaledTransform; // for vertexes + FMMatrix44 scaleMatrix; // for bones + + // HACK: see comment in PSAConvert::TransformVertices + if (isXSI) + { + scaleMatrix = DecomposeToScaleMatrix(transform); + scaledTransform = DecomposeToScaleMatrix(bindTransform) * transform; + } + else + { + scaleMatrix = FMMatrix44_Identity; + scaledTransform = bindTransform; + } + + // Update the vertex positions and normals + for (size_t i = 0; i < vertexCount; ++i) + { + FMVector3 pos (&position[i*3], 0); + FMVector3 norm (&normal[i*3], 0); + + // Apply the scene-node transforms + pos = scaledTransform.TransformCoordinate(pos); + norm = FMVector3_Normalize(scaledTransform.TransformVector(norm)); + + // Convert from right-handed Y_UP or Z_UP to the game's coordinate system (left-handed Y-up) + + if (yUp) + { + pos.z = -pos.z; + norm.z = -norm.z; + } + else + { + std::swap(pos.y, pos.z); + std::swap(norm.y, norm.z); + } + + // and copy back into the original array + + position[i*3] = pos.x; + position[i*3+1] = pos.y; + position[i*3+2] = pos.z; + + normal[i*3] = norm.x; + normal[i*3+1] = norm.y; + normal[i*3+2] = norm.z; + } + + TransformBones(bones, scaleMatrix, yUp); + + // And do the same for prop points + for (size_t i = 0; i < propPoints.size(); ++i) + { + if (yUp) + { + propPoints[i].translation[0] = -propPoints[i].translation[0]; + propPoints[i].orientation[0] = -propPoints[i].orientation[0]; + propPoints[i].orientation[3] = -propPoints[i].orientation[3]; + } + else + { + // Flip translation across the x-axis by swapping y and z + std::swap(propPoints[i].translation[1], propPoints[i].translation[2]); + + // To convert the quaternions: imagine you're using the axis/angle + // representation, then swap the y,z basis vectors and change the + // direction of rotation by negating the angle ( => negating sin(angle) + // => negating x,y,z => changing (x,y,z,w) to (-x,-z,-y,w) + // but then (-x,-z,-y,w) == (x,z,y,-w) so do that instead) + std::swap(propPoints[i].orientation[1], propPoints[i].orientation[2]); + propPoints[i].orientation[3] = -propPoints[i].orientation[3]; + } + } + + } +}; + + +// The above stuff is just in a class since I don't like having to bother +// with forward declarations of functions - but provide the plain function +// interface here: + +void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors) +{ + PMDConvert::ColladaToPMD(input, output, xmlErrors); +} diff --git a/source/graphics/Material.cpp b/source/graphics/Material.cpp index 29eb5e9b8b..fa8d4444d6 100644 --- a/source/graphics/Material.cpp +++ b/source/graphics/Material.cpp @@ -37,6 +37,11 @@ void CMaterial::AddShaderDefine(const char* key, const char* value) m_ShaderDefines.Add(key, value); } +void CMaterial::AddConditionalDefine(const char* defname, const char* defvalue, int type, std::vector &args) +{ + m_ConditionalDefines.Add(defname, defvalue, type, args); +} + void CMaterial::AddStaticUniform(const char* key, const CVector4D& value) { m_StaticUniforms.Add(key, value); @@ -47,4 +52,9 @@ void CMaterial::AddSampler(const TextureSampler& texture) m_Samplers.push_back(texture); if (texture.Name == CStrIntern("baseTex")) m_DiffuseTexture = texture.Sampler; +} + +void CMaterial::AddRenderQuery(const char* key) +{ + m_RenderQueries.Add(key); } \ No newline at end of file diff --git a/source/graphics/Material.h b/source/graphics/Material.h index e5d957dff3..eebe5d2dd2 100644 --- a/source/graphics/Material.h +++ b/source/graphics/Material.h @@ -54,12 +54,18 @@ public: void AddShaderDefine(const char* key, const char* value); const CShaderDefines& GetShaderDefines() const { return m_ShaderDefines; } + + void AddConditionalDefine(const char* defname, const char* defvalue, int type, std::vector &args); + const CShaderConditionalDefines& GetConditionalDefines() const { return m_ConditionalDefines; } void AddStaticUniform(const char* key, const CVector4D& value); const CShaderUniforms& GetStaticUniforms() const { return m_StaticUniforms; } void AddSampler(const TextureSampler& texture); const SamplersVector& GetSamplers() const { return m_Samplers; } + + void AddRenderQuery(const char* key); + const CShaderRenderQueries& GetRenderQueries() const { return m_RenderQueries; } private: @@ -71,7 +77,9 @@ private: CStrIntern m_ShaderEffect; CShaderDefines m_ShaderDefines; + CShaderConditionalDefines m_ConditionalDefines; CShaderUniforms m_StaticUniforms; + CShaderRenderQueries m_RenderQueries; bool m_AlphaBlending; diff --git a/source/graphics/MaterialManager.cpp b/source/graphics/MaterialManager.cpp index 3cd9071804..6e7420dd3d 100644 --- a/source/graphics/MaterialManager.cpp +++ b/source/graphics/MaterialManager.cpp @@ -20,7 +20,9 @@ #include "MaterialManager.h" #include "lib/ogl.h" +#include "maths/MathUtil.h" #include "maths/Vector4D.h" +#include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/PreprocessorWrapper.h" #include "ps/XML/Xeromyces.h" @@ -28,6 +30,13 @@ #include +CMaterialManager::CMaterialManager() +{ + qualityLevel = 5.0; + CFG_GET_USER_VAL("materialmgr.quality", Float, qualityLevel); + qualityLevel = clamp(qualityLevel, 0.0f, 10.0f); +} + CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname) { if (pathname.empty()) @@ -48,11 +57,18 @@ CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname) EL(define); EL(shader); EL(uniform); + EL(renderquery); + EL(conditional_define); AT(effect); AT(if); + AT(quality); AT(material); AT(name); AT(value); + AT(type); + AT(min); + AT(max); + AT(conf); #undef AT #undef EL @@ -62,6 +78,9 @@ CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname) CPreprocessorWrapper preprocessor; preprocessor.AddDefine("CFG_FORCE_ALPHATEST", g_Renderer.m_Options.m_ForceAlphaTest ? "1" : "0"); + + CVector4D vec(qualityLevel,0,0,0); + material.AddStaticUniform("qualityLevel", vec); XERO_ITER_EL(root, node) { @@ -69,11 +88,21 @@ CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname) XMBAttributeList attrs = node.GetAttributes(); if (token == el_alternative) { - if (preprocessor.TestConditional(attrs.GetNamedItem(at_if))) + CStr cond = attrs.GetNamedItem(at_if); + if (!(!cond.empty() && preprocessor.TestConditional(cond))) { - material = LoadMaterial(VfsPath("art/materials") / attrs.GetNamedItem(at_material).FromUTF8()); - break; + cond = attrs.GetNamedItem(at_quality); + if (cond.empty()) + continue; + else + { + if (cond.ToFloat() <= qualityLevel) + continue; + } } + + material = LoadMaterial(VfsPath("art/materials") / attrs.GetNamedItem(at_material).FromUTF8()); + break; } else if (token == el_alpha_blending) { @@ -87,6 +116,59 @@ CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname) { material.AddShaderDefine(attrs.GetNamedItem(at_name).c_str(), attrs.GetNamedItem(at_value).c_str()); } + else if (token == el_conditional_define) + { + std::vector args; + + CStr type = attrs.GetNamedItem(at_type).c_str(); + int typeID = -1; + + if (type == CStr("draw_range")) + { + typeID = DCOND_DISTANCE; + + float valmin = -1.0f; + float valmax = -1.0f; + + CStr conf = attrs.GetNamedItem(at_conf); + if (!conf.empty()) + { + CFG_GET_USER_VAL("materialmgr." + conf + ".min", Float, valmin); + CFG_GET_USER_VAL("materialmgr." + conf + ".max", Float, valmax); + } + else + { + CStr dmin = attrs.GetNamedItem(at_min); + if (!dmin.empty()) + valmin = attrs.GetNamedItem(at_min).ToFloat(); + + CStr dmax = attrs.GetNamedItem(at_max); + if (!dmax.empty()) + valmax = attrs.GetNamedItem(at_max).ToFloat(); + } + + args.push_back(valmin); + args.push_back(valmax); + + if (valmin >= 0.0f) + { + std::stringstream sstr; + sstr << valmin; + material.AddShaderDefine((conf + "_MIN").c_str(), sstr.str().c_str()); + } + + if (valmax >= 0.0f) + { + std::stringstream sstr; + sstr << valmax; + material.AddShaderDefine((conf + "_MAX").c_str(), sstr.str().c_str()); + } + } + + material.AddConditionalDefine(attrs.GetNamedItem(at_name).c_str(), + attrs.GetNamedItem(at_value).c_str(), + typeID, args); + } else if (token == el_uniform) { std::stringstream str(attrs.GetNamedItem(at_value)); @@ -94,6 +176,10 @@ CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname) str >> vec.X >> vec.Y >> vec.Z >> vec.W; material.AddStaticUniform(attrs.GetNamedItem(at_name).c_str(), vec); } + else if (token == el_renderquery) + { + material.AddRenderQuery(attrs.GetNamedItem(at_name).c_str()); + } } m_Materials[pathname] = material; diff --git a/source/graphics/MaterialManager.h b/source/graphics/MaterialManager.h index e2fe2b73b6..3c846983f1 100644 --- a/source/graphics/MaterialManager.h +++ b/source/graphics/MaterialManager.h @@ -25,10 +25,12 @@ class CMaterialManager { public: + CMaterialManager(); CMaterial LoadMaterial(const VfsPath& pathname); private: std::map m_Materials; + float qualityLevel; }; #endif // INCLUDED_MATERIALMANAGER diff --git a/source/graphics/ShaderDefines.cpp b/source/graphics/ShaderDefines.cpp index 8763f3b332..263ffe4f0c 100644 --- a/source/graphics/ShaderDefines.cpp +++ b/source/graphics/ShaderDefines.cpp @@ -234,6 +234,46 @@ void CShaderUniforms::BindUniforms(const CShaderProgramPtr& shader) const } } +void CShaderRenderQueries::Add(const char* name) +{ + if (name == std::string("time")) + { + m_Items.push_back(std::make_pair(RQUERY_TIME, CStrIntern(name))); + } +} + +size_t CShaderRenderQueries::GetSize() +{ + return m_Items.size(); +} + +CShaderRenderQueries::RenderQuery CShaderRenderQueries::GetItem(size_t i) +{ + return m_Items[i]; +} + +void CShaderConditionalDefines::Add(const char* defname, const char* defvalue, int type, std::vector &args) +{ + CondDefine cd; + cd.m_DefName = CStrIntern(defname); + cd.m_DefValue = CStrIntern(defvalue); + cd.m_CondArgs = args; + cd.m_CondType = type; + + m_Defines.push_back(cd); +} + +size_t CShaderConditionalDefines::GetSize() +{ + return m_Defines.size(); +} + +CShaderConditionalDefines::CondDefine& CShaderConditionalDefines::GetItem(size_t i) +{ + return m_Defines[i]; +} + + // Explicit instantiations: template<> CShaderParams::InternedItems_t CShaderParams::s_InternedItems = CShaderParams::InternedItems_t(); diff --git a/source/graphics/ShaderDefines.h b/source/graphics/ShaderDefines.h index 0f58e04fd0..dec1a23246 100644 --- a/source/graphics/ShaderDefines.h +++ b/source/graphics/ShaderDefines.h @@ -167,4 +167,52 @@ public: void BindUniforms(const CShaderProgramPtr& shader) const; }; +// Add here the types of queries we can make in the renderer +enum RENDER_QUERIES +{ + RQUERY_TIME +}; + +/** + * Uniform values that need to be evaluated in the renderer. + * + * Not thread-safe - must only be used from the main thread. + */ +class CShaderRenderQueries +{ +public: + typedef std::pair RenderQuery; + + void Add(const char* name); + size_t GetSize(); + RenderQuery GetItem(size_t i); +private: + std::vector m_Items; +}; + + +enum DEFINE_CONDITION_TYPES +{ + DCOND_DISTANCE +}; + +class CShaderConditionalDefines +{ +public: + struct CondDefine + { + CStrIntern m_DefName; + CStrIntern m_DefValue; + int m_CondType; + std::vector m_CondArgs; + }; + + void Add(const char* defname, const char* defvalue, int type, std::vector &args); + size_t GetSize(); + CondDefine& GetItem(size_t i); + +private: + std::vector m_Defines; +}; + #endif // INCLUDED_SHADERDEFINES diff --git a/source/graphics/ShaderManager.cpp b/source/graphics/ShaderManager.cpp index c38f4cfb80..0b79483191 100644 --- a/source/graphics/ShaderManager.cpp +++ b/source/graphics/ShaderManager.cpp @@ -59,6 +59,10 @@ CShaderManager::CShaderManager() // Allow hotloading of textures RegisterFileReloadFunc(ReloadChangedFileCB, this); + + //CShaderRenderQueries rq; + + //QueryTime = CStrIntern("time"); } CShaderManager::~CShaderManager() diff --git a/source/graphics/ShaderManager.h b/source/graphics/ShaderManager.h index 0cc89e37c2..7c40f14d2f 100644 --- a/source/graphics/ShaderManager.h +++ b/source/graphics/ShaderManager.h @@ -130,6 +130,9 @@ private: static Status ReloadChangedFileCB(void* param, const VfsPath& path); Status ReloadChangedFile(const VfsPath& path); + +public: + //CStrIntern QueryTime; }; #endif // INCLUDED_SHADERMANAGER diff --git a/source/graphics/mikktspace.cpp b/source/graphics/mikktspace.cpp new file mode 100644 index 0000000000..f364f30df2 --- /dev/null +++ b/source/graphics/mikktspace.cpp @@ -0,0 +1,1896 @@ +#include "precompiled.h" + +/** \file mikktspace/mikktspace.c + * \ingroup mikktspace + */ +/** + * Copyright (C) 2011 by Morten S. Mikkelsen + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include +#include +#include +#include +#include +#include + +#include "mikktspace.h" + +#define TFALSE 0 +#define TTRUE 1 + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define INTERNAL_RND_SORT_SEED 39871946 + +// internal structure +typedef struct +{ + float x, y, z; +} SVec3; + +static tbool veq( const SVec3 v1, const SVec3 v2 ) +{ + return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); +} + +static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) +{ + SVec3 vRes; + + vRes.x = v1.x + v2.x; + vRes.y = v1.y + v2.y; + vRes.z = v1.z + v2.z; + + return vRes; +} + + +static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) +{ + SVec3 vRes; + + vRes.x = v1.x - v2.x; + vRes.y = v1.y - v2.y; + vRes.z = v1.z - v2.z; + + return vRes; +} + +static SVec3 vscale(const float fS, const SVec3 v) +{ + SVec3 vRes; + + vRes.x = fS * v.x; + vRes.y = fS * v.y; + vRes.z = fS * v.z; + + return vRes; +} + +static float LengthSquared( const SVec3 v ) +{ + return v.x*v.x + v.y*v.y + v.z*v.z; +} + +static float Length( const SVec3 v ) +{ + return sqrtf(LengthSquared(v)); +} + +static SVec3 Normalize( const SVec3 v ) +{ + return vscale(1 / Length(v), v); +} + +static float vdot( const SVec3 v1, const SVec3 v2) +{ + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; +} + + +static tbool NotZero(const float fX) +{ + // could possibly use FLT_EPSILON instead + return fabsf(fX) > FLT_MIN; +} + +static tbool VNotZero(const SVec3 v) +{ + // might change this to an epsilon based test + return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); +} + + + +typedef struct +{ + int iNrFaces; + int * pTriMembers; +} SSubGroup; + +typedef struct +{ + int iNrFaces; + int * pFaceIndices; + int iVertexRepresentitive; + tbool bOrientPreservering; +} SGroup; + +// +#define MARK_DEGENERATE 1 +#define QUAD_ONE_DEGEN_TRI 2 +#define GROUP_WITH_ANY 4 +#define ORIENT_PRESERVING 8 + + + +typedef struct +{ + int FaceNeighbors[3]; + SGroup * AssignedGroup[3]; + + // normalized first order face derivatives + SVec3 vOs, vOt; + float fMagS, fMagT; // original magnitudes + + // determines if the current and the next triangle are a quad. + int iOrgFaceNumber; + int iFlag, iTSpacesOffs; + unsigned char vert_num[4]; +} STriInfo; + +typedef struct +{ + SVec3 vOs; + float fMagS; + SVec3 vOt; + float fMagT; + int iCounter; // this is to average back into quads. + tbool bOrient; +} STSpace; + +static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); +static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], + const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, + const SMikkTSpaceContext * pContext); + +static int MakeIndex(const int iFace, const int iVert) +{ + assert(iVert>=0 && iVert<4 && iFace>=0); + return (iFace<<2) | (iVert&0x3); +} + +static void IndexToData(int * piFace, int * piVert, const int iIndexIn) +{ + piVert[0] = iIndexIn&0x3; + piFace[0] = iIndexIn>>2; +} + +static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) +{ + STSpace ts_res; + + // this if is important. Due to floating point precision + // averaging when ts0==ts1 will cause a slight difference + // which results in tangent space splits later on + if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && + veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) + { + ts_res.fMagS = pTS0->fMagS; + ts_res.fMagT = pTS0->fMagT; + ts_res.vOs = pTS0->vOs; + ts_res.vOt = pTS0->vOt; + } + else + { + ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); + ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); + ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); + ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); + if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); + if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); + } + + return ts_res; +} + + + +static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); +static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); +static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); + + +// degen triangles +static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); +static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); + + +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) +{ + return genTangSpace(pContext, 180.0f); +} + +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) +{ + // count nr_triangles + int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; + STriInfo * pTriInfos = NULL; + SGroup * pGroups = NULL; + STSpace * psTspace = NULL; + int iNrTrianglesIn = 0, f=0, t=0, i=0; + int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; + int iNrActiveGroups = 0, index = 0; + const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); + tbool bRes = TFALSE; + const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); + + // verify all call-backs have been set + if ( pContext->m_pInterface->m_getNumFaces==NULL || + pContext->m_pInterface->m_getNumVerticesOfFace==NULL || + pContext->m_pInterface->m_getPosition==NULL || + pContext->m_pInterface->m_getNormal==NULL || + pContext->m_pInterface->m_getTexCoord==NULL ) + return TFALSE; + + // count triangles on supported faces + for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts==3) ++iNrTrianglesIn; + else if(verts==4) iNrTrianglesIn += 2; + } + if (iNrTrianglesIn<=0) return TFALSE; + + // allocate memory for an index list + piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); + pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); + if (piTriListIn==NULL || pTriInfos==NULL) + { + if (piTriListIn!=NULL) free(piTriListIn); + if (pTriInfos!=NULL) free(pTriInfos); + return TFALSE; + } + + // make an initial triangle --> face index list + iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); + + // make a welded index list of identical positions and attributes (pos, norm, texc) + //printf("gen welded index list begin\n"); + GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); + //printf("gen welded index list end\n"); + + // Mark all degenerate triangles + iTotTris = iNrTrianglesIn; + iDegenTriangles = 0; + for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts!=3 && verts!=4) continue; + + + // I've decided to let degenerate triangles and group-with-anythings + // vary between left/right hand coordinate systems at the vertices. + // All healthy triangles on the other hand are built to always be either or. + + /*// force the coordinate system orientation to be uniform for every face. + // (this is already the case for good triangles but not for + // degenerate ones and those with bGroupWithAnything==true) + bool bOrient = psTspace[index].bOrient; + if (psTspace[index].iCounter == 0) // tspace was not derived from a group + { + // look for a space created in GenerateTSpaces() by iCounter>0 + bool bNotFound = true; + int i=1; + while (i 0) bNotFound=false; + else ++i; + } + if (!bNotFound) bOrient = psTspace[index+i].bOrient; + }*/ + + // set data + for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; + float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; + if (pContext->m_pInterface->m_setTSpace!=NULL) + pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); + if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) + pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); + + ++index; + } + } + + free(psTspace); + + + return TTRUE; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + float vert[3]; + int index; +} STmpVert; + +const int g_iCells = 2048; + +#ifdef _MSC_VER + #define NOINLINE __declspec(noinline) +#else + #define NOINLINE __attribute__ ((noinline)) +#endif + +// it is IMPORTANT that this function is called to evaluate the hash since +// inlining could potentially reorder instructions and generate different +// results for the same effective input value fVal. +NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) +{ + const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); + const int iIndex = fIndex<0?0:((int)fIndex); + return iIndex vP.x) vMin.x = vP.x; + else if(vMax.x < vP.x) vMax.x = vP.x; + if (vMin.y > vP.y) vMin.y = vP.y; + else if(vMax.y < vP.y) vMax.y = vP.y; + if (vMin.z > vP.z) vMin.z = vP.z; + else if(vMax.z < vP.z) vMax.z = vP.z; + } + + vDim = vsub(vMax,vMin); + iChannel = 0; + fMin = vMin.x; fMax=vMax.x; + if (vDim.y>vDim.x && vDim.y>vDim.z) + { + iChannel=1; + fMin = vMin.y, fMax=vMax.y; + } + else if(vDim.z>vDim.x) + { + iChannel=2; + fMin = vMin.z, fMax=vMax.z; + } + + // make allocations + piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); + piHashCount = (int *) malloc(sizeof(int)*g_iCells); + piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); + piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); + + if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) + { + if (piHashTable!=NULL) free(piHashTable); + if (piHashCount!=NULL) free(piHashCount); + if (piHashOffsets!=NULL) free(piHashOffsets); + if (piHashCount2!=NULL) free(piHashCount2); + GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); + return; + } + memset(piHashCount, 0, sizeof(int)*g_iCells); + memset(piHashCount2, 0, sizeof(int)*g_iCells); + + // count amount of elements in each cell unit + for (i=0; i<(iNrTrianglesIn*3); i++) + { + const int index = piTriList_in_and_out[i]; + const SVec3 vP = GetPosition(pContext, index); + const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); + const int iCell = FindGridCell(fMin, fMax, fVal); + ++piHashCount[iCell]; + } + + // evaluate start index of each cell. + piHashOffsets[0]=0; + for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; + else if(fvMax[c]dx && dy>dz) channel=1; + else if(dz>dx) channel=2; + + fSep = 0.5f*(fvMax[channel]+fvMin[channel]); + + // terminate recursion when the separation/average value + // is no longer strictly between fMin and fMax values. + if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) + { + // complete the weld + for (l=iL_in; l<=iR_in; l++) + { + int i = pTmpVert[l].index; + const int index = piTriList_in_and_out[i]; + const SVec3 vP = GetPosition(pContext, index); + const SVec3 vN = GetNormal(pContext, index); + const SVec3 vT = GetTexCoord(pContext, index); + + tbool bNotFound = TTRUE; + int l2=iL_in, i2rec=-1; + while (l20); // at least 2 entries + + // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] + while (iL < iR) + { + tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; + while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); + bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); + bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) + { + const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts!=3 && verts!=4) continue; + + pTriInfos[iDstTriIndex].iOrgFaceNumber = f; + pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; + + if (verts==3) + { + unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; + pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; + piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); + piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); + piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); + ++iDstTriIndex; // next + } + else + { + { + pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; + pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; + } + + { + // need an order independent way to evaluate + // tspace on quads. This is done by splitting + // along the shortest diagonal. + const int i0 = MakeIndex(f, 0); + const int i1 = MakeIndex(f, 1); + const int i2 = MakeIndex(f, 2); + const int i3 = MakeIndex(f, 3); + const SVec3 T0 = GetTexCoord(pContext, i0); + const SVec3 T1 = GetTexCoord(pContext, i1); + const SVec3 T2 = GetTexCoord(pContext, i2); + const SVec3 T3 = GetTexCoord(pContext, i3); + const float distSQ_02 = LengthSquared(vsub(T2,T0)); + const float distSQ_13 = LengthSquared(vsub(T3,T1)); + tbool bQuadDiagIs_02; + if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); + res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; + return res; +} + +static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) +{ + int iF, iI; + SVec3 res; float norm[3]; + IndexToData(&iF, &iI, index); + pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); + res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; + return res; +} + +static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) +{ + int iF, iI; + SVec3 res; float texc[2]; + IndexToData(&iF, &iI, index); + pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); + res.x=texc[0]; res.y=texc[1]; res.z=1.0f; + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef union +{ + struct + { + int i0, i1, f; + }; + int array[3]; +} SEdge; + +static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); +static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); + +// returns the texture area times 2 +static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) +{ + const SVec3 t1 = GetTexCoord(pContext, indices[0]); + const SVec3 t2 = GetTexCoord(pContext, indices[1]); + const SVec3 t3 = GetTexCoord(pContext, indices[2]); + + const float t21x = t2.x-t1.x; + const float t21y = t2.y-t1.y; + const float t31x = t3.x-t1.x; + const float t31y = t3.y-t1.y; + + const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; + + return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; +} + +static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) +{ + int f=0, i=0, t=0; + // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. + + // generate neighbor info list + for (f=0; f0 ? ORIENT_PRESERVING : 0); + + if ( NotZero(fSignedAreaSTx2) ) + { + const float fAbsArea = fabsf(fSignedAreaSTx2); + const float fLenOs = Length(vOs); + const float fLenOt = Length(vOt); + const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; + if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); + if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); + + // evaluate magnitudes prior to normalization of vOs and vOt + pTriInfos[f].fMagS = fLenOs / fAbsArea; + pTriInfos[f].fMagT = fLenOt / fAbsArea; + + // if this is a good triangle + if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) + pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); + } + } + + // force otherwise healthy quads to a fixed orientation + while (t<(iNrTrianglesIn-1)) + { + const int iFO_a = pTriInfos[t].iOrgFaceNumber; + const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; + if (iFO_a==iFO_b) // this is a quad + { + const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + + // bad triangles should already have been removed by + // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false + if ((bIsDeg_a||bIsDeg_b)==TFALSE) + { + const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + // if this happens the quad has extremely bad mapping!! + if (bOrientA!=bOrientB) + { + //printf("found quad with bad mapping\n"); + tbool bChooseOrientFirstTri = TFALSE; + if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; + else if( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) + bChooseOrientFirstTri = TTRUE; + + // force match + { + const int t0 = bChooseOrientFirstTri ? t : (t+1); + const int t1 = bChooseOrientFirstTri ? (t+1) : t; + pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first + pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit + } + } + } + t += 2; + } + else + ++t; + } + + // match up edge pairs + { + SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); + if (pEdges==NULL) + BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); + else + { + BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); + + free(pEdges); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); +static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); + +static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) +{ + const int iNrMaxGroups = iNrTrianglesIn*3; + int iNrActiveGroups = 0; + int iOffset = 0, f=0, i=0; + for (f=0; fiVertexRepresentitive = vert_index; + pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; + pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; + pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; + ++iNrActiveGroups; + + AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); + bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + neigh_indexL = pTriInfos[f].FaceNeighbors[i]; + neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; + if (neigh_indexL>=0) // neighbor + { + const tbool bAnswer = + AssignRecur(piTriListIn, pTriInfos, neigh_indexL, + pTriInfos[f].AssignedGroup[i] ); + + const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; + assert(bAnswer || bDiff); + } + if (neigh_indexR>=0) // neighbor + { + const tbool bAnswer = + AssignRecur(piTriListIn, pTriInfos, neigh_indexR, + pTriInfos[f].AssignedGroup[i] ); + + const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; + assert(bAnswer || bDiff); + } + + // update offset + iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; + // since the groups are disjoint a triangle can never + // belong to more than 3 groups. Subsequently something + // is completely screwed if this assertion ever hits. + assert(iOffset <= iNrMaxGroups); + } + } + } + + return iNrActiveGroups; +} + +static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) +{ + pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; + ++pGroup->iNrFaces; +} + +static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], + const int iMyTriIndex, SGroup * pGroup) +{ + STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; + + // track down vertex + const int iVertRep = pGroup->iVertexRepresentitive; + const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; + int i=-1; + if (pVerts[0]==iVertRep) i=0; + else if(pVerts[1]==iVertRep) i=1; + else if(pVerts[2]==iVertRep) i=2; + assert(i>=0 && i<3); + + // early out + if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; + else if(pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; + if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) + { + // first to group with a group-with-anything triangle + // determines it's orientation. + // This is the only existing order dependency in the code!! + if ( pMyTriInfo->AssignedGroup[0] == NULL && + pMyTriInfo->AssignedGroup[1] == NULL && + pMyTriInfo->AssignedGroup[2] == NULL ) + { + pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); + pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); + } + } + { + const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + if (bOrient != pGroup->bOrientPreservering) return TFALSE; + } + + AddTriToGroup(pGroup, iMyTriIndex); + pMyTriInfo->AssignedGroup[i] = pGroup; + + { + const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; + const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; + if (neigh_indexL>=0) + AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); + if (neigh_indexR>=0) + AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); + } + + + + return TTRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); +static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); +static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); + +static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], + const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, + const SMikkTSpaceContext * pContext) +{ + STSpace * pSubGroupTspace = NULL; + SSubGroup * pUniSubGroups = NULL; + int * pTmpMembers = NULL; + int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; + for (g=0; giNrFaces; i++) // triangles + { + const int f = pGroup->pFaceIndices[i]; // triangle number + int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; + SSubGroup tmp_group; + tbool bFound; + SVec3 n, vOs, vOt; + if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; + else if(pTriInfos[f].AssignedGroup[1]==pGroup) index=1; + else if(pTriInfos[f].AssignedGroup[2]==pGroup) index=2; + assert(index>=0 && index<3); + + iVertIndex = piTriListIn[f*3+index]; + assert(iVertIndex==pGroup->iVertexRepresentitive); + + // is normalized already + n = GetNormal(pContext, iVertIndex); + + // project + vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); + vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); + if ( VNotZero(vOs) ) vOs = Normalize(vOs); + if ( VNotZero(vOt) ) vOt = Normalize(vOt); + + // original face number + iOF_1 = pTriInfos[f].iOrgFaceNumber; + + iMembers = 0; + for (j=0; jiNrFaces; j++) + { + const int t = pGroup->pFaceIndices[j]; // triangle number + const int iOF_2 = pTriInfos[t].iOrgFaceNumber; + + // project + SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); + SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); + if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); + if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); + + { + const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; + // make sure triangles which belong to the same quad are joined. + const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; + + const float fCosS = vdot(vOs,vOs2); + const float fCosT = vdot(vOt,vOt2); + + assert(f!=t || bSameOrgFace); // sanity check + if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) + pTmpMembers[iMembers++] = t; + } + } + + // sort pTmpMembers + tmp_group.iNrFaces = iMembers; + tmp_group.pTriMembers = pTmpMembers; + if (iMembers>1) + { + unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + QuickSort(pTmpMembers, 0, iMembers-1, uSeed); + } + + // look for an existing match + bFound = TFALSE; + l=0; + while (liVertexRepresentitive); + ++iUniqueSubGroups; + } + + // output tspace + { + const int iOffs = pTriInfos[f].iTSpacesOffs; + const int iVert = pTriInfos[f].vert_num[index]; + STSpace * pTS_out = &psTspace[iOffs+iVert]; + assert(pTS_out->iCounter<2); + assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); + if (pTS_out->iCounter==1) + { + *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); + pTS_out->iCounter = 2; // update counter + pTS_out->bOrient = pGroup->bOrientPreservering; + } + else + { + assert(pTS_out->iCounter==0); + *pTS_out = pSubGroupTspace[l]; + pTS_out->iCounter = 1; // update counter + pTS_out->bOrient = pGroup->bOrientPreservering; + } + } + } + + // clean up and offset iUniqueTspaces + for (s=0; s=0 && i<3); + + // project + index = piTriListIn[3*f+i]; + n = GetNormal(pContext, index); + vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); + vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); + if ( VNotZero(vOs) ) vOs = Normalize(vOs); + if ( VNotZero(vOt) ) vOt = Normalize(vOt); + + i2 = piTriListIn[3*f + (i<2?(i+1):0)]; + i1 = piTriListIn[3*f + i]; + i0 = piTriListIn[3*f + (i>0?(i-1):2)]; + + p0 = GetPosition(pContext, i0); + p1 = GetPosition(pContext, i1); + p2 = GetPosition(pContext, i2); + v1 = vsub(p0,p1); + v2 = vsub(p2,p1); + + // project + v1 = vsub(v1, vscale(vdot(n,v1),n)); if( VNotZero(v1) ) v1 = Normalize(v1); + v2 = vsub(v2, vscale(vdot(n,v2),n)); if( VNotZero(v2) ) v2 = Normalize(v2); + + // weight contribution by the angle + // between the two edge vectors + fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); + fAngle = (float) acos(fCos); + fMagS = pTriInfos[f].fMagS; + fMagT = pTriInfos[f].fMagT; + + res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); + res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); + res.fMagS+=(fAngle*fMagS); + res.fMagT+=(fAngle*fMagT); + fAngleSum += fAngle; + } + } + + // normalize + if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); + if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); + if (fAngleSum>0) + { + res.fMagS /= fAngleSum; + res.fMagT /= fAngleSum; + } + + return res; +} + +static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) +{ + tbool bStillSame=TTRUE; + int i=0; + if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; + while (iiNrFaces && bStillSame) + { + bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; + if (bStillSame) ++i; + } + return bStillSame; +} + +static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) +{ + int iL, iR, n, index, iMid, iTmp; + + // Random + unsigned int t=uSeed&31; + t=(uSeed<>(32-t)); + uSeed=uSeed+t+3; + // Random end + + iL=iLeft; iR=iRight; + n = (iR-iL)+1; + assert(n>=0); + index = (int) (uSeed%n); + + iMid=pSortBuffer[index + iL]; + + + do + { + while (pSortBuffer[iL] < iMid) + ++iL; + while (pSortBuffer[iR] > iMid) + --iR; + + if (iL <= iR) + { + iTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = iTmp; + ++iL; --iR; + } + } + while (iL <= iR); + + if (iLeft < iR) + QuickSort(pSortBuffer, iLeft, iR, uSeed); + if (iL < iRight) + QuickSort(pSortBuffer, iL, iRight, uSeed); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); + +static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) +{ + // build array of edges + unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + int iEntries=0, iCurStartIndex=-1, f=0, i=0; + for (f=0; f pSortBuffer[iRight].array[channel]) + { + sTmp = pSortBuffer[iLeft]; + pSortBuffer[iLeft] = pSortBuffer[iRight]; + pSortBuffer[iRight] = sTmp; + } + return; + } + + // Random + t=uSeed&31; + t=(uSeed<>(32-t)); + uSeed=uSeed+t+3; + // Random end + + iL=iLeft, iR=iRight; + n = (iR-iL)+1; + assert(n>=0); + index = (int) (uSeed%n); + + iMid=pSortBuffer[index + iL].array[channel]; + + do + { + while (pSortBuffer[iL].array[channel] < iMid) + ++iL; + while (pSortBuffer[iR].array[channel] > iMid) + --iR; + + if (iL <= iR) + { + sTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = sTmp; + ++iL; --iR; + } + } + while (iL <= iR); + + if (iLeft < iR) + QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); + if (iL < iRight) + QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); +} + +// resolve ordering and edge number +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) +{ + *edgenum_out = -1; + + // test if first index is on the edge + if (indices[0]==i0_in || indices[0]==i1_in) + { + // test if second index is on the edge + if (indices[1]==i0_in || indices[1]==i1_in) + { + edgenum_out[0]=0; // first edge + i0_out[0]=indices[0]; + i1_out[0]=indices[1]; + } + else + { + edgenum_out[0]=2; // third edge + i0_out[0]=indices[2]; + i1_out[0]=indices[0]; + } + } + else + { + // only second and third index is on the edge + edgenum_out[0]=1; // second edge + i0_out[0]=indices[1]; + i1_out[0]=indices[2]; + } +} + + +///////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////// Degenerate triangles //////////////////////////////////// + +static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) +{ + int iNextGoodTriangleSearchIndex=-1; + tbool bStillFindingGoodOnes; + + // locate quads with only one good triangle + int t=0; + while (t<(iTotTris-1)) + { + const int iFO_a = pTriInfos[t].iOrgFaceNumber; + const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; + if (iFO_a==iFO_b) // this is a quad + { + const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + if ((bIsDeg_a^bIsDeg_b)!=0) + { + pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; + pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; + } + t += 2; + } + else + ++t; + } + + // reorder list so all degen triangles are moved to the back + // without reordering the good triangles + iNextGoodTriangleSearchIndex = 1; + t=0; + bStillFindingGoodOnes = TTRUE; + while (t (t+1)); + + // swap triangle t0 and t1 + if (!bJustADegenerate) + { + int i=0; + for (i=0; i<3; i++) + { + const int index = piTriList_out[t0*3+i]; + piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; + piTriList_out[t1*3+i] = index; + } + { + const STriInfo tri_info = pTriInfos[t0]; + pTriInfos[t0] = pTriInfos[t1]; + pTriInfos[t1] = tri_info; + } + } + else + bStillFindingGoodOnes = TFALSE; // this is not supposed to happen + } + + if (bStillFindingGoodOnes) ++t; + } + + assert(bStillFindingGoodOnes); // code will still work. + assert(iNrTrianglesIn == t); +} + +static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) +{ + int t=0, i=0; + // deal with degenerate triangles + // punishment for degenerate triangles is O(N^2) + for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf + * Note that though the tangent spaces at the vertices are generated in an order-independent way, + * by this implementation, the interpolated tangent space is still affected by which diagonal is + * chosen to split each quad. A sensible solution is to have your tools pipeline always + * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. + * If these have the same length then compare the diagonals defined by the texture coordinates. + * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin + * and also quad triangulator plugin. + */ + + +typedef int tbool; +typedef struct SMikkTSpaceContext SMikkTSpaceContext; + +typedef struct +{ + // Returns the number of faces (triangles/quads) on the mesh to be processed. + int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); + + // Returns the number of vertices on face number iFace + // iFace is a number in the range {0, 1, ..., getNumFaces()-1} + int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); + + // returns the position/normal/texcoord of the referenced face of vertex number iVert. + // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. + void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); + void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); + void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); + + // either (or both) of the two setTSpace callbacks can be set. + // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. + + // This function is used to return the tangent and fSign to the application. + // fvTangent is a unit length vector. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); + + // This function is used to return tangent space results to the application. + // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their + // true magnitudes which can be used for relief mapping effects. + // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. + // However, both are perpendicular to the vertex normal. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert); +} SMikkTSpaceInterface; + +struct SMikkTSpaceContext +{ + SMikkTSpaceInterface * m_pInterface; // initialized with callback functions + void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) +}; + +// these are both thread safe! +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); + + +// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the +// normal map sampler must use the exact inverse of the pixel shader transformation. +// The most efficient transformation we can possibly do in the pixel shader is +// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. +// pixel shader (fast transform out) +// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); +// where vNt is the tangent space normal. The normal map sampler must likewise use the +// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. +// sampler does (exact inverse of pixel shader): +// float3 row0 = cross(vB, vN); +// float3 row1 = cross(vN, vT); +// float3 row2 = cross(vT, vB); +// float fSign = dot(vT, row0)<0 ? -1 : 1; +// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); +// where vNout is the sampled normal in some chosen 3D space. +// +// Should you choose to reconstruct the bitangent in the pixel shader instead +// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. +// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of +// quads as your renderer then problems will occur since the interpolated tangent spaces will differ +// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before +// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. +// However, this must be used both by the sampler and your tools/rendering pipeline. + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/graphics/weldmesh.cpp b/source/graphics/weldmesh.cpp new file mode 100644 index 0000000000..d601295352 --- /dev/null +++ b/source/graphics/weldmesh.cpp @@ -0,0 +1,196 @@ +#include "precompiled.h" + +/** + * Copyright (C) 2011 by Morten S. Mikkelsen + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + + +#include "weldmesh.h" +#include +#include + +#ifdef __APPLE__ +#include /* OSX gets its malloc stuff through here */ +#else +#include +#endif + +static void MergeVertsFast(int * piCurNrUniqueVertices, int * piRemapTable, float * pfVertexDataOut, int * piVertexIDs, + const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert, + const int iL_in, const int iR_in, const int iChannelNum); + +int WeldMesh(int * piRemapTable, float * pfVertexDataOut, + const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert) +{ + int iUniqueVertices = 0, i=0; + int * piVertexIDs = NULL; + if(iNrVerticesIn<=0) return 0; + + + iUniqueVertices = 0; + piVertexIDs = (int *) malloc(sizeof(int)*iNrVerticesIn); + if(piVertexIDs!=NULL) + { + for(i=0; i=0); + } + + return iUniqueVertices; +} + + + + + +static void MergeVertsFast(int * piCurNrUniqueVertices, int * piRemapTable, float * pfVertexDataOut, int * piVertexIDs, + const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert, + const int iL_in, const int iR_in, const int iChannelNum) +{ + const int iCount = iR_in-iL_in+1; + int l=0; + float fMin, fMax, fAvg; + assert(iCount>0); + // make bbox + fMin = pfVertexDataIn[ piVertexIDs[iL_in]*iFloatsPerVert + iChannelNum]; fMax = fMin; + for(l=(iL_in+1); l<=iR_in; l++) + { + const int index = piVertexIDs[l]*iFloatsPerVert + iChannelNum; + const float fVal = pfVertexDataIn[index]; + if(fMin>fVal) fMin=fVal; + else if(fMax=fMax || iCount==1) + { + if((iChannelNum+1) == iFloatsPerVert || iCount==1) // we are done, weld by hand + { + int iUniqueNewVertices = 0; + float * pfNewUniVertsOut = &pfVertexDataOut[ piCurNrUniqueVertices[0]*iFloatsPerVert ]; + + for(l=iL_in; l<=iR_in; l++) + { + const int index = piVertexIDs[l]*iFloatsPerVert; + + int iFound = 0; // didn't find copy yet. + int l2=0; + while(l2=iL_in && iL<=iR_in); + index = piVertexIDs[iL]*iFloatsPerVert+iChannelNum; + iReadyLeftSwap = !(pfVertexDataIn[index]=iL_in && iR<=iR_in); + index = piVertexIDs[iR]*iFloatsPerVert+iChannelNum; + iReadyRightSwap = pfVertexDataIn[index]GetLOSTexture().MakeDirty(); } + + if (CRenderer::IsInitialised()) + g_Renderer.GetTimeManager().Update(deltaSimTime); } if (doInterpolate) diff --git a/source/renderer/InstancingModelRenderer.cpp b/source/renderer/InstancingModelRenderer.cpp index 2ceaddceb9..d7c37faf3b 100644 --- a/source/renderer/InstancingModelRenderer.cpp +++ b/source/renderer/InstancingModelRenderer.cpp @@ -31,6 +31,7 @@ #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" +#include "graphics/weldmesh.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/Renderer.h" @@ -49,6 +50,7 @@ struct IModelDef : public CModelDefRPrivate /// Position and normals are static VertexArray::Attribute m_Position; VertexArray::Attribute m_Normal; + VertexArray::Attribute m_Tangent; VertexArray::Attribute m_BlendJoints; // valid iff gpuSkinning == true VertexArray::Attribute m_BlendWeights; // valid iff gpuSkinning == true @@ -58,11 +60,11 @@ struct IModelDef : public CModelDefRPrivate /// Indices are the same for all models, so share them VertexIndexArray m_IndexArray; - IModelDef(const CModelDefPtr& mdef, bool gpuSkinning); + IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateTangents); }; -IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning) +IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateTangents) : m_IndexArray(GL_STATIC_DRAW), m_Array(GL_STATIC_DRAW) { size_t numVertices = mdef->GetNumVertices(); @@ -82,61 +84,148 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning) m_UVs[i].elems = 2; m_Array.AddAttribute(&m_UVs[i]); } - - if (gpuSkinning) + + if (calculateTangents) { - m_BlendJoints.type = GL_UNSIGNED_BYTE; - m_BlendJoints.elems = 4; - m_Array.AddAttribute(&m_BlendJoints); + // Generate tangents for the geometry:- + + m_Tangent.type = GL_FLOAT; + m_Tangent.elems = 4; + m_Array.AddAttribute(&m_Tangent); + + // floats per vertex; position + normal + tangent + UV*sets + int numVertexAttrs = 3 + 3 + 4 + 2 * mdef->GetNumUVsPerVertex(); + + // the tangent generation can increase the number of vertices temporarily + // so reserve a bit more memory to avoid reallocations in GenTangents (in most cases) + std::vector newVertices; + newVertices.reserve(numVertexAttrs * numVertices * 2); + + // Generate the tangents + ModelRenderer::GenTangents(mdef, newVertices); + + // how many vertices do we have after generating tangents? + int newNumVert = newVertices.size() / numVertexAttrs; + + std::vector remapTable(newNumVert); + std::vector vertexDataOut(newNumVert * numVertexAttrs); - m_BlendWeights.type = GL_UNSIGNED_BYTE; - m_BlendWeights.elems = 4; - m_Array.AddAttribute(&m_BlendWeights); - } + // re-weld the mesh to remove duplicated vertices + int numVertices2 = WeldMesh(&remapTable[0], &vertexDataOut[0], + &newVertices[0], newNumVert, numVertexAttrs); - m_Array.SetNumVertices(numVertices); - m_Array.Layout(); + // Copy the model data to graphics memory:- + + m_Array.SetNumVertices(numVertices2); + m_Array.Layout(); - VertexArrayIterator Position = m_Position.GetIterator(); - VertexArrayIterator Normal = m_Normal.GetIterator(); - - ModelRenderer::CopyPositionAndNormals(mdef, Position, Normal); - - for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); i++) - { - VertexArrayIterator UVit = m_UVs[i].GetIterator(); - ModelRenderer::BuildUV(mdef, UVit, i); - } - - if (gpuSkinning) - { - VertexArrayIterator BlendJoints = m_BlendJoints.GetIterator(); - VertexArrayIterator BlendWeights = m_BlendWeights.GetIterator(); - for (size_t i = 0; i < numVertices; ++i) + VertexArrayIterator Position = m_Position.GetIterator(); + VertexArrayIterator Normal = m_Normal.GetIterator(); + VertexArrayIterator Tangent = m_Tangent.GetIterator(); + + // copy everything into the vertex array + for (int i = 0; i < numVertices2; i++) { - const SModelVertex& vtx = mdef->GetVertices()[i]; - for (size_t j = 0; j < 4; ++j) + int q = numVertexAttrs * i; + + Position[i] = CVector3D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2]); + + Normal[i] = CVector3D(vertexDataOut[q + 3], vertexDataOut[q + 4], vertexDataOut[q + 5]); + + Tangent[i] = CVector4D(vertexDataOut[q + 6], vertexDataOut[q + 7], vertexDataOut[q + 8], + vertexDataOut[q + 9]); + + for (size_t j = 0; j < mdef->GetNumUVsPerVertex(); j++) { - BlendJoints[i][j] = vtx.m_Blend.m_Bone[j]; - BlendWeights[i][j] = (u8)(255.f * vtx.m_Blend.m_Weight[j]); + VertexArrayIterator UVit = m_UVs[j].GetIterator(); + UVit[i][0] = vertexDataOut[q + 10 + 2 * j]; + UVit[i][1] = vertexDataOut[q + 11 + 2 * j]; } } + + // upload vertex data + m_Array.Upload(); + m_Array.FreeBackingStore(); + + m_IndexArray.SetNumVertices(mdef->GetNumFaces() * 3); + m_IndexArray.Layout(); + + VertexArrayIterator Indices = m_IndexArray.GetIterator(); + + size_t idxidx = 0; + + // reindex geometry and upload index + for (size_t j = 0; j < mdef->GetNumFaces(); ++j) + { + Indices[idxidx++] = remapTable[j * 3 + 0]; + Indices[idxidx++] = remapTable[j * 3 + 1]; + Indices[idxidx++] = remapTable[j * 3 + 2]; + } + + m_IndexArray.Upload(); + m_IndexArray.FreeBackingStore(); } + else + { + // Upload model without calculating tangents:- + + if (gpuSkinning) + { + m_BlendJoints.type = GL_UNSIGNED_BYTE; + m_BlendJoints.elems = 4; + m_Array.AddAttribute(&m_BlendJoints); - m_Array.Upload(); - m_Array.FreeBackingStore(); + m_BlendWeights.type = GL_UNSIGNED_BYTE; + m_BlendWeights.elems = 4; + m_Array.AddAttribute(&m_BlendWeights); + } + + m_Array.SetNumVertices(numVertices); + m_Array.Layout(); - m_IndexArray.SetNumVertices(mdef->GetNumFaces()*3); - m_IndexArray.Layout(); - ModelRenderer::BuildIndices(mdef, m_IndexArray.GetIterator()); - m_IndexArray.Upload(); - m_IndexArray.FreeBackingStore(); + VertexArrayIterator Position = m_Position.GetIterator(); + VertexArrayIterator Normal = m_Normal.GetIterator(); + + ModelRenderer::CopyPositionAndNormals(mdef, Position, Normal); + + for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); i++) + { + VertexArrayIterator UVit = m_UVs[i].GetIterator(); + ModelRenderer::BuildUV(mdef, UVit, i); + } + + if (gpuSkinning) + { + VertexArrayIterator BlendJoints = m_BlendJoints.GetIterator(); + VertexArrayIterator BlendWeights = m_BlendWeights.GetIterator(); + for (size_t i = 0; i < numVertices; ++i) + { + const SModelVertex& vtx = mdef->GetVertices()[i]; + for (size_t j = 0; j < 4; ++j) + { + BlendJoints[i][j] = vtx.m_Blend.m_Bone[j]; + BlendWeights[i][j] = (u8)(255.f * vtx.m_Blend.m_Weight[j]); + } + } + } + + m_Array.Upload(); + m_Array.FreeBackingStore(); + + m_IndexArray.SetNumVertices(mdef->GetNumFaces()*3); + m_IndexArray.Layout(); + ModelRenderer::BuildIndices(mdef, m_IndexArray.GetIterator()); + m_IndexArray.Upload(); + m_IndexArray.FreeBackingStore(); + } } struct InstancingModelRendererInternals { bool gpuSkinning; + + bool calculateTangents; /// Previously prepared modeldef IModelDef* imodeldef; @@ -147,10 +236,11 @@ struct InstancingModelRendererInternals // Construction and Destruction -InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning) +InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning, bool calculateTangents) { m = new InstancingModelRendererInternals; m->gpuSkinning = gpuSkinning; + m->calculateTangents = calculateTangents; m->imodeldef = 0; } @@ -173,7 +263,7 @@ CModelRData* InstancingModelRenderer::CreateModelData(const void* key, CModel* m if (!imodeldef) { - imodeldef = new IModelDef(mdef, m->gpuSkinning); + imodeldef = new IModelDef(mdef, m->gpuSkinning, m->calculateTangents); mdef->SetRenderData(m, imodeldef); } @@ -217,6 +307,9 @@ void InstancingModelRenderer::PrepareModelDef(const CShaderProgramPtr& shader, i if (streamflags & STREAM_NORMAL) shader->NormalPointer(GL_FLOAT, stride, base + m->imodeldef->m_Normal.offset); + + if (m->calculateTangents) + shader->VertexAttribPointer("a_tangent", 4, GL_FLOAT, GL_TRUE, stride, base + m->imodeldef->m_Tangent.offset); if (streamflags & STREAM_UV0) shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, stride, base + m->imodeldef->m_UVs[0].offset); diff --git a/source/renderer/InstancingModelRenderer.h b/source/renderer/InstancingModelRenderer.h index 338ca6069e..251729140c 100644 --- a/source/renderer/InstancingModelRenderer.h +++ b/source/renderer/InstancingModelRenderer.h @@ -35,7 +35,7 @@ struct InstancingModelRendererInternals; class InstancingModelRenderer : public ModelVertexRenderer { public: - InstancingModelRenderer(bool gpuSkinning); + InstancingModelRenderer(bool gpuSkinning, bool calculateTangents); ~InstancingModelRenderer(); // Implementations diff --git a/source/renderer/MikktspaceWrap.cpp b/source/renderer/MikktspaceWrap.cpp new file mode 100644 index 0000000000..dab209ef18 --- /dev/null +++ b/source/renderer/MikktspaceWrap.cpp @@ -0,0 +1,129 @@ + +#include "precompiled.h" + +#include + +#include "graphics/Color.h" +#include "graphics/LightEnv.h" +#include "graphics/Model.h" +#include "graphics/ModelDef.h" +#include "graphics/ShaderManager.h" +#include "graphics/TextureManager.h" +#include + +#include + + + +MikkTSpace::MikkTSpace(const CModelDefPtr& m, std::vector& v) : model(m), newVertices(v) +{ + // ensure that newVertices is empty + newVertices.clear(); + + // set up SMikkTSpaceInterface struct + interface.m_getNumFaces = getNumFaces; + interface.m_getNumVerticesOfFace = getNumVerticesOfFace; + interface.m_getPosition = getPosition; + interface.m_getNormal = getNormal; + interface.m_getTexCoord = getTexCoord; + interface.m_setTSpaceBasic = NULL; + interface.m_setTSpace = setTSpace; + + // set up SMikkTSpaceContext struct + context.m_pInterface = &interface; + context.m_pUserData = (void*)this; +} + +void MikkTSpace::generate() +{ + genTangSpaceDefault(&context); +} + + +int MikkTSpace::getNumFaces(const SMikkTSpaceContext *pContext) +{ + return ((MikkTSpace*)pContext->m_pUserData)->model->GetNumFaces(); +} + + +int MikkTSpace::getNumVerticesOfFace(const SMikkTSpaceContext* UNUSED(pContext), const int UNUSED(iFace)) +{ + return 3; +} + + +void MikkTSpace::getPosition(const SMikkTSpaceContext *pContext, + float fvPosOut[], const int iFace, const int iVert) +{ + SModelFace &face = ((MikkTSpace*)pContext->m_pUserData)->model->GetFaces()[iFace]; + long i = face.m_Verts[iVert]; + const CVector3D &p = ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices()[i].m_Coords; + + fvPosOut[0] = p.X; + fvPosOut[1] = p.Y; + fvPosOut[2] = p.Z; +} + + +void MikkTSpace::getNormal(const SMikkTSpaceContext *pContext, + float fvNormOut[], const int iFace, const int iVert) +{ + SModelFace &face = ((MikkTSpace*)pContext->m_pUserData)->model->GetFaces()[iFace]; + long i = face.m_Verts[iVert]; + const CVector3D &n = ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices()[i].m_Norm; + + fvNormOut[0] = n.X; + fvNormOut[1] = n.Y; + fvNormOut[2] = n.Z; +} + + +void MikkTSpace::getTexCoord(const SMikkTSpaceContext *pContext, + float fvTexcOut[], const int iFace, const int iVert) +{ + SModelFace &face = ((MikkTSpace*)pContext->m_pUserData)->model->GetFaces()[iFace]; + long i = face.m_Verts[iVert]; + SModelVertex &v = ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices()[i]; + + // the tangents are calculated according to the 'default' UV set + fvTexcOut[0] = v.m_UVs[0]; + fvTexcOut[1] = 1.0-v.m_UVs[1]; +} + + +void MikkTSpace::setTSpace(const SMikkTSpaceContext * pContext, const float fvTangent[], + const float UNUSED(fvBiTangent)[], const float UNUSED(fMagS), const float UNUSED(fMagT), + const tbool bIsOrientationPreserving, const int iFace, const int iVert) +{ + SModelFace &face = ((MikkTSpace*)pContext->m_pUserData)->model->GetFaces()[iFace]; + long i = face.m_Verts[iVert]; + + SModelVertex* vertices = ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices(); + size_t numUVsPerVertex = ((MikkTSpace*)pContext->m_pUserData)->model->GetNumUVsPerVertex(); + std::vector& newVertices = ((MikkTSpace*)pContext->m_pUserData)->newVertices; + + const CVector3D &p = vertices[i].m_Coords; + const CVector3D &n = vertices[i].m_Norm; + + newVertices.push_back(p.X); + newVertices.push_back(p.Y); + newVertices.push_back(p.Z); + + newVertices.push_back(n.X); + newVertices.push_back(n.Y); + newVertices.push_back(n.Z); + + newVertices.push_back(fvTangent[0]); + newVertices.push_back(fvTangent[1]); + newVertices.push_back(fvTangent[2]); + newVertices.push_back(bIsOrientationPreserving > 0.5 ? 1.0f : (-1.0f)); + + for (size_t UVset = 0; UVset < numUVsPerVertex; ++UVset) + { + newVertices.push_back(vertices[i].m_UVs[UVset * 2]); + newVertices.push_back(1.0 - vertices[i].m_UVs[UVset * 2 + 1]); + } +} + + + diff --git a/source/renderer/MikktspaceWrap.h b/source/renderer/MikktspaceWrap.h new file mode 100644 index 0000000000..4a0dcc7156 --- /dev/null +++ b/source/renderer/MikktspaceWrap.h @@ -0,0 +1,80 @@ +#ifndef INCLUDED_MIKKWRAP +#define INCLUDED_MIKKWRAP + + +#include + +class MikkTSpace +{ + +public: + + MikkTSpace(const CModelDefPtr& m, std::vector& v); + + void generate(); + +private: + + SMikkTSpaceInterface interface; + SMikkTSpaceContext context; + + const CModelDefPtr& model; + + std::vector& newVertices; + + + // Returns the number of faces (triangles/quads) on the mesh to be processed. + static int getNumFaces(const SMikkTSpaceContext *pContext); + + + // Returns the number of vertices on face number iFace + // iFace is a number in the range {0, 1, ..., getNumFaces()-1} + static int getNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace); + + + // returns the position/normal/texcoord of the referenced face of vertex number iVert. + // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. + static void getPosition(const SMikkTSpaceContext *pContext, + float fvPosOut[], const int iFace, const int iVert); + + static void getNormal(const SMikkTSpaceContext *pContext, + float fvNormOut[], const int iFace, const int iVert); + + static void getTexCoord(const SMikkTSpaceContext *pContext, + float fvTexcOut[], const int iFace, const int iVert); + + + // either (or both) of the two setTSpace callbacks can be set. + // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. + + // This function is used to return the tangent and fSign to the application. + // fvTangent is a unit length vector. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + //void setTSpaceBasic(const MikkTSpace *parent, const SMikkTSpaceContext *pContext, + // const float fvTangent[], const float fSign, const int iFace, const int iVert); + + + // This function is used to return tangent space results to the application. + // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their + // true magnitudes which can be used for relief mapping effects. + // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. + // However, both are perpendicular to the vertex normal. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + static void setTSpace(const SMikkTSpaceContext * pContext, const float fvTangent[], + const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert); + + +}; + + +#endif // INCLUDED_MIKKWRAP \ No newline at end of file diff --git a/source/renderer/ModelRenderer.cpp b/source/renderer/ModelRenderer.cpp index 248606db47..54b3a80d48 100644 --- a/source/renderer/ModelRenderer.cpp +++ b/source/renderer/ModelRenderer.cpp @@ -32,6 +32,7 @@ #include "graphics/ShaderManager.h" #include "graphics/TextureManager.h" +#include "renderer/MikktspaceWrap.h" #include "renderer/ModelRenderer.h" #include "renderer/ModelVertexRenderer.h" #include "renderer/Renderer.h" @@ -146,6 +147,14 @@ void ModelRenderer::BuildColor4ub( } +void ModelRenderer::GenTangents(const CModelDefPtr& mdef, std::vector& newVertices) +{ + MikkTSpace ms(mdef, newVertices); + + ms.generate(); +} + + // Copy UV coordinates void ModelRenderer::BuildUV( const CModelDefPtr& mdef, @@ -305,7 +314,7 @@ struct SMRMaterialBucketKey : effect(effect), defines(defines) { } CStrIntern effect; - const CShaderDefines& defines; + CShaderDefines defines; bool operator==(const SMRMaterialBucketKey& b) const { @@ -415,8 +424,33 @@ void ShaderModelRenderer::Render(const RenderModifierPtr& modifier, const CShade for (size_t i = 0; i < m->submissions.size(); ++i) { CModel* model = m->submissions[i]; + + CShaderDefines defs = model->GetMaterial().GetShaderDefines(); + CShaderConditionalDefines condefs = model->GetMaterial().GetConditionalDefines(); + + for (size_t j = 0; j < condefs.GetSize(); ++j) + { + CShaderConditionalDefines::CondDefine &item = condefs.GetItem(j); + int type = item.m_CondType; + switch (type) + { + case DCOND_DISTANCE: + { + CVector3D modelpos = model->GetTransform().GetTranslation(); + float dist = worldToCam.Transform(modelpos).Z; + + float dmin = item.m_CondArgs[0]; + float dmax = item.m_CondArgs[1]; + + if ((dmin < 0 || dist >= dmin) && (dmax < 0 || dist < dmax)) + defs.Add(item.m_DefName.c_str(), item.m_DefValue.c_str()); + + break; + } + } + } - SMRMaterialBucketKey key(model->GetMaterial().GetShaderEffect(), model->GetMaterial().GetShaderDefines()); + SMRMaterialBucketKey key(model->GetMaterial().GetShaderEffect(), defs); std::vector& bucketItems = materialBuckets[key]; bucketItems.push_back(model); } @@ -651,6 +685,27 @@ void ShaderModelRenderer::Render(const RenderModifierPtr& modifier, const CShade currentStaticUniforms = newStaticUniforms; currentStaticUniforms.BindUniforms(shader); } + + CShaderRenderQueries renderQueries = model->GetMaterial().GetRenderQueries(); + + for (size_t q = 0; q < renderQueries.GetSize(); q++) + { + CShaderRenderQueries::RenderQuery rq = renderQueries.GetItem(q); + //if (str == g_Renderer.GetShaderManager().QueryTime) + if (rq.first == RQUERY_TIME) + { + + //renderQueries.Set(str, (float)time, 0.0f, 0.0f, 0.0f); + //shader->Uniform(rq.second, CVector3D(time,0,0)); + CShaderProgram::Binding binding = shader->GetUniformBinding(rq.second); + if (binding.Active()) + { + double time = g_Renderer.GetTimeManager().GetGlobalTime(); + shader->Uniform(binding, time, 0,0,0); + } + } + } + //renderQueries.BindUniforms(shader); modifier->PrepareModel(shader, model); diff --git a/source/renderer/ModelRenderer.h b/source/renderer/ModelRenderer.h index 7019524411..dde9a359d2 100644 --- a/source/renderer/ModelRenderer.h +++ b/source/renderer/ModelRenderer.h @@ -254,6 +254,15 @@ public: static void BuildIndices( const CModelDefPtr& mdef, const VertexArrayIterator& Indices); + + /** + * GenTangents: Generate tangents for the given CModelDef. + * + * @param mdef The model definition object. + * @param newVertices An out vector of the unindexed vertices with tangents added. + * The new vertices cannot be used with existing face index and must be welded/reindexed. + */ + static void GenTangents(const CModelDefPtr& mdef, std::vector& newVertices); }; diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp index 9dcefff892..bf0baa87e4 100644 --- a/source/renderer/Renderer.cpp +++ b/source/renderer/Renderer.cpp @@ -64,6 +64,7 @@ #include "renderer/SkyManager.h" #include "renderer/TerrainOverlay.h" #include "renderer/TerrainRenderer.h" +#include "renderer/TimeManager.h" #include "renderer/VertexBufferManager.h" #include "renderer/WaterManager.h" @@ -268,6 +269,9 @@ public: /// Material manager CMaterialManager materialManager; + + /// Time manager + CTimeManager timeManager; /// Shadow map ShadowMap shadow; @@ -430,11 +434,13 @@ CRenderer::CRenderer() m_Options.m_PreferGLSL = false; m_Options.m_ForceAlphaTest = false; m_Options.m_GPUSkinning = false; + m_Options.m_GenTangents = false; // TODO: be more consistent in use of the config system CFG_GET_USER_VAL("preferglsl", Bool, m_Options.m_PreferGLSL); CFG_GET_USER_VAL("forcealphatest", Bool, m_Options.m_ForceAlphaTest); CFG_GET_USER_VAL("gpuskinning", Bool, m_Options.m_GPUSkinning); + CFG_GET_USER_VAL("gentangents", Bool, m_Options.m_GenTangents); #if CONFIG2_GLES // Override config option since GLES only supports GLSL @@ -564,11 +570,11 @@ void CRenderer::ReloadShaders() bool cpuLighting = (GetRenderPath() == RP_FIXED); m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer(cpuLighting)); - m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false)); + m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, m_Options.m_GenTangents)); if (GetRenderPath() == RP_SHADER && m_Options.m_GPUSkinning) // TODO: should check caps and GLSL etc too { - m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true)); + m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, false)); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); } @@ -1975,6 +1981,11 @@ TerrainRenderer& CRenderer::GetTerrainRenderer() return m->terrainRenderer; } +CTimeManager& CRenderer::GetTimeManager() +{ + return m->timeManager; +} + CMaterialManager& CRenderer::GetMaterialManager() { return m->materialManager; diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h index 7edabe0de2..7f012c5b95 100644 --- a/source/renderer/Renderer.h +++ b/source/renderer/Renderer.h @@ -31,6 +31,7 @@ #include "scripting/ScriptableObject.h" #include "renderer/Scene.h" +#include "renderer/TimeManager.h" // necessary declarations class CPatch; @@ -46,6 +47,7 @@ class CTextureManager; class CShaderManager; class CParticleManager; class TerrainRenderer; +class CTimeManager; class CMaterialManager; // rendering modes @@ -129,6 +131,7 @@ public: bool m_ForceAlphaTest; bool m_GPUSkinning; bool m_Silhouettes; + bool m_GenTangents; } m_Options; struct Caps { @@ -276,6 +279,8 @@ public: CMaterialManager& GetMaterialManager(); CShaderDefines GetSystemShaderDefines(); + + CTimeManager& GetTimeManager(); /** * GetCapabilities: Return which OpenGL capabilities are available and enabled. diff --git a/source/renderer/TimeManager.cpp b/source/renderer/TimeManager.cpp new file mode 100644 index 0000000000..f190b4f525 --- /dev/null +++ b/source/renderer/TimeManager.cpp @@ -0,0 +1,28 @@ +#include "precompiled.h" + +#include "lib/timer.h" +#include "maths/MathUtil.h" +#include "renderer/TimeManager.h" + + +CTimeManager::CTimeManager() +{ + m_frameDelta = 0.0; + m_globalTime = 0.0; +} + +double CTimeManager::GetFrameDelta() +{ + return m_frameDelta; +} + +double CTimeManager::GetGlobalTime() +{ + return m_globalTime; +} + +void CTimeManager::Update(double delta) +{ + m_frameDelta = delta; + m_globalTime += delta; +} diff --git a/source/renderer/TimeManager.h b/source/renderer/TimeManager.h new file mode 100644 index 0000000000..e71c7eab1c --- /dev/null +++ b/source/renderer/TimeManager.h @@ -0,0 +1,26 @@ +#ifndef INCLUDED_TIMEMANAGER +#define INCLUDED_TIMEMANAGER + +#include "graphics/Texture.h" +#include "ps/Overlay.h" +#include "maths/Matrix3D.h" +#include "lib/ogl.h" + + +class CTimeManager +{ +public: + CTimeManager(); + + double GetFrameDelta(); + double GetGlobalTime(); + + void Update(double delta); + +private: + double m_frameDelta; + double m_globalTime; +}; + + +#endif // INCLUDED_TIMEMANAGER \ No newline at end of file diff --git a/source/renderer/VertexBuffer.h b/source/renderer/VertexBuffer.h index c4268681d9..b26cab4375 100644 --- a/source/renderer/VertexBuffer.h +++ b/source/renderer/VertexBuffer.h @@ -31,7 +31,7 @@ // Make it large enough for the maximum feasible mesh size (64K vertexes, // 32 bytes per vertex in ShaderModelRenderer). // TODO: measure what influence this has on performance -#define MAX_VB_SIZE_BYTES (2*1024*1024) +#define MAX_VB_SIZE_BYTES (4*1024*1024) /** * CVertexBuffer: encapsulation of ARB_vertex_buffer_object, also supplying