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