rig.cpp 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696
  1. #include "rig.h"
  2. #include "../../game/core/component.h"
  3. #include "../entity/registry.h"
  4. #include "../geom/affine_matrix.h"
  5. #include "../geom/math_utils.h"
  6. #include "../geom/transforms.h"
  7. #include "../gl/primitives.h"
  8. #include "../humanoid/rig.h"
  9. #include "../submitter.h"
  10. #include "../template_cache.h"
  11. #include <QMatrix4x4>
  12. #include <QVector3D>
  13. #include <algorithm>
  14. #include <cmath>
  15. #include <cstdint>
  16. #include <numbers>
  17. #include <qmatrix4x4.h>
  18. #include <qvectornd.h>
  19. #include <unordered_map>
  20. namespace Render::GL {
  21. static ElephantRenderStats s_elephantRenderStats;
  22. auto get_elephant_render_stats() -> const ElephantRenderStats & {
  23. return s_elephantRenderStats;
  24. }
  25. void reset_elephant_render_stats() { s_elephantRenderStats.reset(); }
  26. using Render::Geom::clamp01;
  27. using Render::Geom::cone_from_to;
  28. using Render::Geom::cylinder_between;
  29. using Render::Geom::lerp;
  30. using Render::Geom::smoothstep;
  31. namespace {
  32. struct CachedElephantProfileEntry {
  33. ElephantProfile profile;
  34. QVector3D fabric_base;
  35. QVector3D metal_base;
  36. uint32_t frame_number{0};
  37. };
  38. using ElephantProfileCacheKey = uint64_t;
  39. static std::unordered_map<ElephantProfileCacheKey, CachedElephantProfileEntry>
  40. s_elephant_profile_cache;
  41. static uint32_t s_elephant_cache_frame = 0;
  42. constexpr uint32_t k_elephant_profile_cache_max_age = 600;
  43. constexpr uint32_t k_cache_cleanup_interval_mask = 0x1FFU;
  44. constexpr float k_color_hash_multiplier = 31.0F;
  45. constexpr float k_color_comparison_tolerance = 0.001F;
  46. inline auto resolve_variant_key_from_seed(std::uint32_t seed) -> std::uint8_t {
  47. std::uint32_t v = seed ^ (seed >> 16);
  48. return static_cast<std::uint8_t>(v % k_template_variant_count);
  49. }
  50. inline auto resolve_variant_seed(const Engine::Core::UnitComponent *unit_comp,
  51. std::uint8_t variant_key) -> std::uint32_t {
  52. std::uint32_t seed = 0U;
  53. if (unit_comp != nullptr) {
  54. seed ^= static_cast<std::uint32_t>(unit_comp->spawn_type) * 2654435761U;
  55. seed ^= static_cast<std::uint32_t>(unit_comp->owner_id) * 1013904223U;
  56. }
  57. seed ^= static_cast<std::uint32_t>(variant_key) * 2246822519U;
  58. return seed;
  59. }
  60. inline auto make_elephant_profile_cache_key(
  61. uint32_t seed, const QVector3D &fabric_base,
  62. const QVector3D &metal_base) -> ElephantProfileCacheKey {
  63. auto color_to_5bit = [](float c) -> uint32_t {
  64. return static_cast<uint32_t>(std::clamp(c, 0.0F, 1.0F) *
  65. k_color_hash_multiplier);
  66. };
  67. uint32_t color_hash = color_to_5bit(fabric_base.x());
  68. color_hash |= color_to_5bit(fabric_base.y()) << 5;
  69. color_hash |= color_to_5bit(fabric_base.z()) << 10;
  70. color_hash |= color_to_5bit(metal_base.x()) << 15;
  71. color_hash |= color_to_5bit(metal_base.y()) << 20;
  72. color_hash |= color_to_5bit(metal_base.z()) << 25;
  73. return (static_cast<uint64_t>(seed) << 32) |
  74. static_cast<uint64_t>(color_hash);
  75. }
  76. constexpr float k_pi = std::numbers::pi_v<float>;
  77. constexpr int k_hash_shift_16 = 16;
  78. constexpr int k_hash_shift_15 = 15;
  79. constexpr uint32_t k_hash_mult_1 = 0x7Feb352dU;
  80. constexpr uint32_t k_hash_mult_2 = 0x846ca68bU;
  81. constexpr uint32_t k_hash_mask_24bit = 0xFFFFFF;
  82. constexpr float k_hash_divisor = 16777216.0F;
  83. constexpr float k_rgb_max = 255.0F;
  84. constexpr int k_rgb_shift_red = 16;
  85. constexpr int k_rgb_shift_green = 8;
  86. inline auto hash01(uint32_t x) -> float {
  87. x ^= x >> k_hash_shift_16;
  88. x *= k_hash_mult_1;
  89. x ^= x >> k_hash_shift_15;
  90. x *= k_hash_mult_2;
  91. x ^= x >> k_hash_shift_16;
  92. return (x & k_hash_mask_24bit) / k_hash_divisor;
  93. }
  94. inline auto rand_between(uint32_t seed, uint32_t salt, float min_val,
  95. float max_val) -> float {
  96. const float t = hash01(seed ^ salt);
  97. return min_val + (max_val - min_val) * t;
  98. }
  99. inline auto saturate(float x) -> float {
  100. return std::min(1.0F, std::max(0.0F, x));
  101. }
  102. inline auto darken(const QVector3D &c, float k) -> QVector3D { return c * k; }
  103. inline auto lighten(const QVector3D &c, float k) -> QVector3D {
  104. return {saturate(c.x() * k), saturate(c.y() * k), saturate(c.z() * k)};
  105. }
  106. inline auto lerp3(const QVector3D &a, const QVector3D &b,
  107. float t) -> QVector3D {
  108. return {a.x() + (b.x() - a.x()) * t, a.y() + (b.y() - a.y()) * t,
  109. a.z() + (b.z() - a.z()) * t};
  110. }
  111. inline auto bezier(const QVector3D &p0, const QVector3D &p1,
  112. const QVector3D &p2, float t) -> QVector3D {
  113. float const u = 1.0F - t;
  114. return p0 * (u * u) + p1 * (2.0F * u * t) + p2 * (t * t);
  115. }
  116. inline auto color_hash(const QVector3D &c) -> uint32_t {
  117. auto const r = uint32_t(saturate(c.x()) * k_rgb_max);
  118. auto const g = uint32_t(saturate(c.y()) * k_rgb_max);
  119. auto const b = uint32_t(saturate(c.z()) * k_rgb_max);
  120. uint32_t v = (r << k_rgb_shift_red) | (g << k_rgb_shift_green) | b;
  121. v ^= v >> k_hash_shift_16;
  122. v *= k_hash_mult_1;
  123. v ^= v >> k_hash_shift_15;
  124. v *= k_hash_mult_2;
  125. v ^= v >> k_hash_shift_16;
  126. return v;
  127. }
  128. inline void draw_cylinder(ISubmitter &out, const QMatrix4x4 &model,
  129. const QVector3D &a, const QVector3D &b, float radius,
  130. const QVector3D &color, float alpha = 1.0F,
  131. int material_id = 0) {
  132. out.mesh(get_unit_cylinder(), cylinder_between(model, a, b, radius), color,
  133. nullptr, alpha, material_id);
  134. }
  135. inline void draw_cone(ISubmitter &out, const QMatrix4x4 &model,
  136. const QVector3D &tip, const QVector3D &base, float radius,
  137. const QVector3D &color, float alpha = 1.0F,
  138. int material_id = 0) {
  139. out.mesh(get_unit_cone(), cone_from_to(model, tip, base, radius), color,
  140. nullptr, alpha, material_id);
  141. }
  142. constexpr float k_skin_highlight_base = 0.50F;
  143. constexpr float k_skin_vertical_factor = 0.30F;
  144. constexpr float k_skin_longitudinal_factor = 0.15F;
  145. constexpr float k_skin_seed_factor = 0.10F;
  146. constexpr float k_skin_bright_factor = 1.06F;
  147. constexpr float k_skin_shadow_factor = 0.88F;
  148. inline auto skin_gradient(const QVector3D &skin, float vertical_factor,
  149. float longitudinal_factor, float seed) -> QVector3D {
  150. float const highlight = saturate(
  151. k_skin_highlight_base + vertical_factor * k_skin_vertical_factor -
  152. longitudinal_factor * k_skin_longitudinal_factor +
  153. seed * k_skin_seed_factor);
  154. QVector3D const bright = lighten(skin, k_skin_bright_factor);
  155. QVector3D const shadow = darken(skin, k_skin_shadow_factor);
  156. return shadow * (1.0F - highlight) + bright * highlight;
  157. }
  158. } // namespace
  159. namespace ElephantDimensionRange {
  160. constexpr float kBodyLengthMin = 0.7333333F;
  161. constexpr float kBodyLengthMax = 0.8666667F;
  162. constexpr float kBodyWidthMin = 0.30F;
  163. constexpr float kBodyWidthMax = 0.3666667F;
  164. constexpr float kBodyHeightMin = 0.40F;
  165. constexpr float kBodyHeightMax = 0.50F;
  166. constexpr float kNeckLengthMin = 0.175F;
  167. constexpr float kNeckLengthMax = 0.25F;
  168. constexpr float kNeckWidthMin = 0.225F;
  169. constexpr float kNeckWidthMax = 0.275F;
  170. constexpr float kHeadLengthMin = 0.275F;
  171. constexpr float kHeadLengthMax = 0.35F;
  172. constexpr float kHeadWidthMin = 0.25F;
  173. constexpr float kHeadWidthMax = 0.325F;
  174. constexpr float kHeadHeightMin = 0.275F;
  175. constexpr float kHeadHeightMax = 0.35F;
  176. constexpr float kTrunkLengthMin = 0.80F;
  177. constexpr float kTrunkLengthMax = 1.00F;
  178. constexpr float kTrunkBaseRadiusMin = 0.09F;
  179. constexpr float kTrunkBaseRadiusMax = 0.12F;
  180. constexpr float kTrunkTipRadiusMin = 0.02F;
  181. constexpr float kTrunkTipRadiusMax = 0.035F;
  182. constexpr float kEarWidthMin = 0.35F;
  183. constexpr float kEarWidthMax = 0.45F;
  184. constexpr float kEarHeightMin = 0.40F;
  185. constexpr float kEarHeightMax = 0.50F;
  186. constexpr float kEarThicknessMin = 0.012F;
  187. constexpr float kEarThicknessMax = 0.022F;
  188. constexpr float kLegLengthMin = 0.70F;
  189. constexpr float kLegLengthMax = 0.85F;
  190. constexpr float kLegRadiusMin = 0.09F;
  191. constexpr float kLegRadiusMax = 0.125F;
  192. constexpr float kFootRadiusMin = 0.11F;
  193. constexpr float kFootRadiusMax = 0.15F;
  194. constexpr float kTailLengthMin = 0.35F;
  195. constexpr float kTailLengthMax = 0.475F;
  196. constexpr float kTuskLengthMin = 0.25F;
  197. constexpr float kTuskLengthMax = 0.425F;
  198. constexpr float kTuskRadiusMin = 0.02F;
  199. constexpr float kTuskRadiusMax = 0.035F;
  200. constexpr float kHowdahWidthMin = 0.40F;
  201. constexpr float kHowdahWidthMax = 0.50F;
  202. constexpr float kHowdahLengthMin = 0.50F;
  203. constexpr float kHowdahLengthMax = 0.65F;
  204. constexpr float kHowdahHeightMin = 0.20F;
  205. constexpr float kHowdahHeightMax = 0.275F;
  206. constexpr float kIdleBobAmplitudeMin = 0.004F;
  207. constexpr float kIdleBobAmplitudeMax = 0.0075F;
  208. constexpr float kMoveBobAmplitudeMin = 0.0175F;
  209. constexpr float kMoveBobAmplitudeMax = 0.0275F;
  210. constexpr uint32_t kSaltBodyLength = 0x12U;
  211. constexpr uint32_t kSaltBodyWidth = 0x34U;
  212. constexpr uint32_t kSaltBodyHeight = 0x56U;
  213. constexpr uint32_t kSaltNeckLength = 0x78U;
  214. constexpr uint32_t kSaltNeckWidth = 0x9AU;
  215. constexpr uint32_t kSaltHeadLength = 0xBCU;
  216. constexpr uint32_t kSaltHeadWidth = 0xDEU;
  217. constexpr uint32_t kSaltHeadHeight = 0xF0U;
  218. constexpr uint32_t kSaltTrunkLength = 0x123U;
  219. constexpr uint32_t kSaltTrunkBaseRadius = 0x234U;
  220. constexpr uint32_t kSaltTrunkTipRadius = 0x345U;
  221. constexpr uint32_t kSaltEarWidth = 0x456U;
  222. constexpr uint32_t kSaltEarHeight = 0x567U;
  223. constexpr uint32_t kSaltEarThickness = 0x678U;
  224. constexpr uint32_t kSaltLegLength = 0x789U;
  225. constexpr uint32_t kSaltLegRadius = 0x89AU;
  226. constexpr uint32_t kSaltFootRadius = 0x9ABU;
  227. constexpr uint32_t kSaltTailLength = 0xABCU;
  228. constexpr uint32_t kSaltTuskLength = 0xBCDU;
  229. constexpr uint32_t kSaltTuskRadius = 0xCDEU;
  230. constexpr uint32_t kSaltHowdahWidth = 0xDEFU;
  231. constexpr uint32_t kSaltHowdahLength = 0xEF0U;
  232. constexpr uint32_t kSaltHowdahHeight = 0xF01U;
  233. constexpr uint32_t kSaltIdleBob = 0x102U;
  234. constexpr uint32_t kSaltMoveBob = 0x213U;
  235. } // namespace ElephantDimensionRange
  236. auto make_elephant_dimensions(uint32_t seed) -> ElephantDimensions {
  237. using namespace ElephantDimensionRange;
  238. ElephantDimensions d{};
  239. d.body_length =
  240. rand_between(seed, kSaltBodyLength, kBodyLengthMin, kBodyLengthMax);
  241. d.body_width =
  242. rand_between(seed, kSaltBodyWidth, kBodyWidthMin, kBodyWidthMax);
  243. d.body_height =
  244. rand_between(seed, kSaltBodyHeight, kBodyHeightMin, kBodyHeightMax);
  245. d.neck_length =
  246. rand_between(seed, kSaltNeckLength, kNeckLengthMin, kNeckLengthMax);
  247. d.neck_width =
  248. rand_between(seed, kSaltNeckWidth, kNeckWidthMin, kNeckWidthMax);
  249. d.head_length =
  250. rand_between(seed, kSaltHeadLength, kHeadLengthMin, kHeadLengthMax);
  251. d.head_width =
  252. rand_between(seed, kSaltHeadWidth, kHeadWidthMin, kHeadWidthMax);
  253. d.head_height =
  254. rand_between(seed, kSaltHeadHeight, kHeadHeightMin, kHeadHeightMax);
  255. d.trunk_length =
  256. rand_between(seed, kSaltTrunkLength, kTrunkLengthMin, kTrunkLengthMax);
  257. d.trunk_base_radius = rand_between(seed, kSaltTrunkBaseRadius,
  258. kTrunkBaseRadiusMin, kTrunkBaseRadiusMax);
  259. d.trunk_tip_radius = rand_between(seed, kSaltTrunkTipRadius,
  260. kTrunkTipRadiusMin, kTrunkTipRadiusMax);
  261. d.ear_width = rand_between(seed, kSaltEarWidth, kEarWidthMin, kEarWidthMax);
  262. d.ear_height =
  263. rand_between(seed, kSaltEarHeight, kEarHeightMin, kEarHeightMax);
  264. d.ear_thickness =
  265. rand_between(seed, kSaltEarThickness, kEarThicknessMin, kEarThicknessMax);
  266. d.leg_length =
  267. rand_between(seed, kSaltLegLength, kLegLengthMin, kLegLengthMax);
  268. d.leg_radius =
  269. rand_between(seed, kSaltLegRadius, kLegRadiusMin, kLegRadiusMax);
  270. d.foot_radius =
  271. rand_between(seed, kSaltFootRadius, kFootRadiusMin, kFootRadiusMax);
  272. d.foot_radius *= (1.0F / 1.2F);
  273. d.tail_length =
  274. rand_between(seed, kSaltTailLength, kTailLengthMin, kTailLengthMax);
  275. d.tusk_length =
  276. rand_between(seed, kSaltTuskLength, kTuskLengthMin, kTuskLengthMax);
  277. d.tusk_radius =
  278. rand_between(seed, kSaltTuskRadius, kTuskRadiusMin, kTuskRadiusMax);
  279. d.howdah_width =
  280. rand_between(seed, kSaltHowdahWidth, kHowdahWidthMin, kHowdahWidthMax);
  281. d.howdah_length =
  282. rand_between(seed, kSaltHowdahLength, kHowdahLengthMin, kHowdahLengthMax);
  283. d.howdah_height =
  284. rand_between(seed, kSaltHowdahHeight, kHowdahHeightMin, kHowdahHeightMax);
  285. d.idle_bob_amplitude = rand_between(seed, kSaltIdleBob, kIdleBobAmplitudeMin,
  286. kIdleBobAmplitudeMax);
  287. d.move_bob_amplitude = rand_between(seed, kSaltMoveBob, kMoveBobAmplitudeMin,
  288. kMoveBobAmplitudeMax);
  289. d.barrel_center_y =
  290. d.leg_length + d.body_height * 0.35F + d.foot_radius * 0.8F;
  291. return d;
  292. }
  293. namespace ElephantVariantConstants {
  294. constexpr float kSkinBaseR = 0.45F;
  295. constexpr float kSkinBaseG = 0.42F;
  296. constexpr float kSkinBaseB = 0.40F;
  297. constexpr float kSkinVariationMin = 0.85F;
  298. constexpr float kSkinVariationMax = 1.15F;
  299. constexpr float kHighlightBlend = 0.15F;
  300. constexpr float kShadowBlend = 0.20F;
  301. constexpr float kEarInnerR = 0.55F;
  302. constexpr float kEarInnerG = 0.45F;
  303. constexpr float kEarInnerB = 0.42F;
  304. constexpr float kTuskR = 0.95F;
  305. constexpr float kTuskG = 0.92F;
  306. constexpr float kTuskB = 0.85F;
  307. constexpr float kToenailR = 0.35F;
  308. constexpr float kToenailG = 0.32F;
  309. constexpr float kToenailB = 0.28F;
  310. constexpr float kWoodR = 0.45F;
  311. constexpr float kWoodG = 0.32F;
  312. constexpr float kWoodB = 0.22F;
  313. constexpr uint32_t kSaltSkinVariation = 0x324U;
  314. constexpr uint32_t kSaltHighlight = 0x435U;
  315. constexpr uint32_t kSaltShadow = 0x546U;
  316. } // namespace ElephantVariantConstants
  317. auto make_elephant_variant(uint32_t seed, const QVector3D &fabric_base,
  318. const QVector3D &metal_base) -> ElephantVariant {
  319. using namespace ElephantVariantConstants;
  320. ElephantVariant v;
  321. float const skin_variation = rand_between(
  322. seed, kSaltSkinVariation, kSkinVariationMin, kSkinVariationMax);
  323. v.skin_color =
  324. QVector3D(kSkinBaseR * skin_variation, kSkinBaseG * skin_variation,
  325. kSkinBaseB * skin_variation);
  326. float const highlight_t =
  327. rand_between(seed, kSaltHighlight, 0.0F, kHighlightBlend);
  328. v.skin_highlight = lighten(v.skin_color, 1.0F + highlight_t);
  329. float const shadow_t = rand_between(seed, kSaltShadow, 0.0F, kShadowBlend);
  330. v.skin_shadow = darken(v.skin_color, 1.0F - shadow_t);
  331. v.ear_inner_color = QVector3D(kEarInnerR, kEarInnerG, kEarInnerB);
  332. v.tusk_color = QVector3D(kTuskR, kTuskG, kTuskB);
  333. v.toenail_color = QVector3D(kToenailR, kToenailG, kToenailB);
  334. v.howdah_wood_color = QVector3D(kWoodR, kWoodG, kWoodB);
  335. v.howdah_fabric_color = fabric_base;
  336. v.howdah_metal_color = metal_base;
  337. return v;
  338. }
  339. namespace ElephantGaitConstants {
  340. constexpr float kCycleTimeMin = 2.20F;
  341. constexpr float kCycleTimeMax = 2.80F;
  342. constexpr float kFrontLegPhaseMin = 0.0F;
  343. constexpr float kFrontLegPhaseMax = 0.10F;
  344. constexpr float kDiagonalLeadMin = 0.48F;
  345. constexpr float kDiagonalLeadMax = 0.52F;
  346. constexpr float kStrideSwingMin = 0.55F;
  347. constexpr float kStrideSwingMax = 0.75F;
  348. constexpr float kStrideLiftMin = 0.18F;
  349. constexpr float kStrideLiftMax = 0.26F;
  350. constexpr uint32_t kSaltCycleTime = 0x657U;
  351. constexpr uint32_t kSaltFrontLegPhase = 0x768U;
  352. constexpr uint32_t kSaltDiagonalLead = 0x879U;
  353. constexpr uint32_t kSaltStrideSwing = 0x98AU;
  354. constexpr uint32_t kSaltStrideLift = 0xA9BU;
  355. } // namespace ElephantGaitConstants
  356. auto make_elephant_profile(uint32_t seed, const QVector3D &fabric_base,
  357. const QVector3D &metal_base) -> ElephantProfile {
  358. using namespace ElephantGaitConstants;
  359. ElephantProfile profile;
  360. profile.dims = make_elephant_dimensions(seed);
  361. profile.variant = make_elephant_variant(seed, fabric_base, metal_base);
  362. profile.gait.cycle_time =
  363. rand_between(seed, kSaltCycleTime, kCycleTimeMin, kCycleTimeMax);
  364. profile.gait.front_leg_phase = rand_between(
  365. seed, kSaltFrontLegPhase, kFrontLegPhaseMin, kFrontLegPhaseMax);
  366. float const diagonal_lead =
  367. rand_between(seed, kSaltDiagonalLead, kDiagonalLeadMin, kDiagonalLeadMax);
  368. profile.gait.rear_leg_phase =
  369. std::fmod(profile.gait.front_leg_phase + diagonal_lead, 1.0F);
  370. profile.gait.stride_swing =
  371. rand_between(seed, kSaltStrideSwing, kStrideSwingMin, kStrideSwingMax);
  372. profile.gait.stride_lift =
  373. rand_between(seed, kSaltStrideLift, kStrideLiftMin, kStrideLiftMax);
  374. return profile;
  375. }
  376. auto get_or_create_cached_elephant_profile(
  377. uint32_t seed, const QVector3D &fabric_base,
  378. const QVector3D &metal_base) -> ElephantProfile {
  379. ElephantProfileCacheKey cache_key =
  380. make_elephant_profile_cache_key(seed, fabric_base, metal_base);
  381. auto cache_it = s_elephant_profile_cache.find(cache_key);
  382. if (cache_it != s_elephant_profile_cache.end()) {
  383. CachedElephantProfileEntry &entry = cache_it->second;
  384. if ((entry.fabric_base - fabric_base).lengthSquared() <
  385. k_color_comparison_tolerance &&
  386. (entry.metal_base - metal_base).lengthSquared() <
  387. k_color_comparison_tolerance) {
  388. entry.frame_number = s_elephant_cache_frame;
  389. return entry.profile;
  390. }
  391. }
  392. ElephantProfile profile =
  393. make_elephant_profile(seed, fabric_base, metal_base);
  394. CachedElephantProfileEntry &new_entry = s_elephant_profile_cache[cache_key];
  395. new_entry.profile = profile;
  396. new_entry.fabric_base = fabric_base;
  397. new_entry.metal_base = metal_base;
  398. new_entry.frame_number = s_elephant_cache_frame;
  399. return profile;
  400. }
  401. void advance_elephant_profile_cache_frame() {
  402. ++s_elephant_cache_frame;
  403. if ((s_elephant_cache_frame & k_cache_cleanup_interval_mask) == 0) {
  404. auto it = s_elephant_profile_cache.begin();
  405. while (it != s_elephant_profile_cache.end()) {
  406. if (s_elephant_cache_frame - it->second.frame_number >
  407. k_elephant_profile_cache_max_age) {
  408. it = s_elephant_profile_cache.erase(it);
  409. } else {
  410. ++it;
  411. }
  412. }
  413. }
  414. }
  415. namespace HowdahFrameConstants {
  416. constexpr float kHowdahBodyHeightOffset = 0.55F;
  417. constexpr float kHowdahBodyLengthOffset = -0.10F;
  418. constexpr float kSeatHeightOffset = 0.15F;
  419. constexpr float kLegRevealLiftScale = 0.75F;
  420. } // namespace HowdahFrameConstants
  421. auto compute_howdah_frame(const ElephantProfile &profile)
  422. -> HowdahAttachmentFrame {
  423. using namespace HowdahFrameConstants;
  424. const ElephantDimensions &d = profile.dims;
  425. HowdahAttachmentFrame frame{};
  426. frame.seat_forward = QVector3D(0.0F, 0.0F, 1.0F);
  427. frame.seat_right = QVector3D(1.0F, 0.0F, 0.0F);
  428. frame.seat_up = QVector3D(0.0F, 1.0F, 0.0F);
  429. frame.ground_offset = QVector3D(
  430. 0.0F, -d.barrel_center_y + d.leg_length * kLegRevealLiftScale, 0.0F);
  431. frame.howdah_center = QVector3D(
  432. 0.0F, d.barrel_center_y + d.body_height * kHowdahBodyHeightOffset,
  433. d.body_length * kHowdahBodyLengthOffset);
  434. frame.seat_position =
  435. frame.howdah_center +
  436. QVector3D(0.0F, d.howdah_height * kSeatHeightOffset, 0.0F);
  437. return frame;
  438. }
  439. auto evaluate_elephant_motion(ElephantProfile &profile,
  440. const AnimationInputs &anim)
  441. -> ElephantMotionSample {
  442. ElephantMotionSample sample{};
  443. const ElephantGait &g = profile.gait;
  444. const ElephantDimensions &d = profile.dims;
  445. sample.is_moving = anim.is_moving;
  446. if (sample.is_moving) {
  447. float const cycle_progress = std::fmod(anim.time / g.cycle_time, 1.0F);
  448. sample.phase = cycle_progress;
  449. sample.bob = std::sin(cycle_progress * 2.0F * k_pi) * d.move_bob_amplitude;
  450. } else {
  451. sample.phase = std::fmod(anim.time * 0.3F, 1.0F);
  452. sample.bob = std::sin(anim.time * 0.5F) * d.idle_bob_amplitude;
  453. }
  454. float const trunk_primary = std::sin(anim.time * 0.8F) * 0.15F;
  455. float const trunk_secondary = std::sin(anim.time * 1.3F + 0.5F) * 0.08F;
  456. sample.trunk_swing = trunk_primary + trunk_secondary;
  457. float const ear_base = std::sin(anim.time * 0.6F);
  458. sample.ear_flap = sample.is_moving ? ear_base * 0.25F : ear_base * 0.12F;
  459. return sample;
  460. }
  461. void apply_howdah_vertical_offset(HowdahAttachmentFrame &frame, float bob) {
  462. QVector3D const offset(0.0F, bob, 0.0F);
  463. frame.howdah_center += offset;
  464. frame.seat_position += offset;
  465. }
  466. namespace GaitSystemConstants {
  467. constexpr float kLegPhaseFL = 0.00F;
  468. constexpr float kLegPhaseFR = 0.50F;
  469. constexpr float kLegPhaseRL = 0.75F;
  470. constexpr float kLegPhaseRR = 0.25F;
  471. constexpr float kSwingDuration = 0.25F;
  472. constexpr float kSwingLiftPeak = 0.22F;
  473. constexpr float kSwingForwardReach = 0.60F;
  474. constexpr float kWeightShiftLateral = 0.025F;
  475. constexpr float kWeightShiftForeAft = 0.015F;
  476. constexpr float kShoulderLagFactor = 0.08F;
  477. constexpr float kHipLagFactor = 0.06F;
  478. constexpr float kFootSettleDepth = 0.015F;
  479. constexpr float kFootSettleDuration = 0.10F;
  480. } // namespace GaitSystemConstants
  481. inline auto swing_ease(float t) -> float { return t * t * (3.0F - 2.0F * t); }
  482. inline auto swing_arc(float t) -> float { return 4.0F * t * (1.0F - t); }
  483. auto evaluate_swing_position(const ElephantLegState &leg,
  484. float lift_height) -> QVector3D {
  485. float const t = leg.swing_progress;
  486. float const eased_t = swing_ease(t);
  487. QVector3D const horizontal =
  488. leg.swing_start * (1.0F - eased_t) + leg.swing_target * eased_t;
  489. float const lift = swing_arc(t) * lift_height;
  490. return QVector3D(horizontal.x(), horizontal.y() + lift, horizontal.z());
  491. }
  492. auto solve_elephant_leg_ik(const QVector3D &hip, const QVector3D &foot_target,
  493. float upper_len, float lower_len,
  494. float lateral_sign) -> ElephantLegPose {
  495. ElephantLegPose pose{};
  496. pose.hip = hip;
  497. QVector3D const to_foot = foot_target - hip;
  498. float const reach = to_foot.length();
  499. float const max_reach = upper_len + lower_len - 0.01F;
  500. float const min_reach = std::abs(upper_len - lower_len) + 0.01F;
  501. float const clamped_reach = std::clamp(reach, min_reach, max_reach);
  502. QVector3D const reach_dir =
  503. reach > 0.001F ? to_foot / reach : QVector3D(0.0F, -1.0F, 0.0F);
  504. QVector3D const actual_foot = hip + reach_dir * clamped_reach;
  505. pose.foot = actual_foot;
  506. pose.ankle = actual_foot + QVector3D(0.0F, lower_len * 0.08F, 0.0F);
  507. float const a2 = upper_len * upper_len;
  508. float const b2 = lower_len * lower_len;
  509. float const c2 = clamped_reach * clamped_reach;
  510. float const cos_hip_angle =
  511. (a2 + c2 - b2) / (2.0F * upper_len * clamped_reach);
  512. float const hip_angle = std::acos(std::clamp(cos_hip_angle, -1.0F, 1.0F));
  513. QVector3D const up(0.0F, 1.0F, 0.0F);
  514. QVector3D bend_axis = QVector3D::crossProduct(reach_dir, up);
  515. if (bend_axis.lengthSquared() < 0.001F) {
  516. bend_axis = QVector3D(lateral_sign, 0.0F, 0.0F);
  517. }
  518. bend_axis.normalize();
  519. QMatrix4x4 rot;
  520. rot.setToIdentity();
  521. rot.rotate(hip_angle * 180.0F / 3.14159265F, bend_axis);
  522. QVector3D const knee_dir = rot.map(reach_dir);
  523. pose.knee = hip + knee_dir * upper_len;
  524. return pose;
  525. }
  526. inline auto get_leg_phase_offset(int leg_index) -> float {
  527. using namespace GaitSystemConstants;
  528. switch (leg_index) {
  529. case 0:
  530. return kLegPhaseFL;
  531. case 1:
  532. return kLegPhaseFR;
  533. case 2:
  534. return kLegPhaseRL;
  535. case 3:
  536. return kLegPhaseRR;
  537. default:
  538. return 0.0F;
  539. }
  540. }
  541. inline auto is_leg_in_swing(float cycle_phase, int leg_index) -> bool {
  542. using namespace GaitSystemConstants;
  543. float const leg_phase =
  544. std::fmod(cycle_phase - get_leg_phase_offset(leg_index) + 1.0F, 1.0F);
  545. return leg_phase < kSwingDuration;
  546. }
  547. inline auto get_swing_progress(float cycle_phase, int leg_index) -> float {
  548. using namespace GaitSystemConstants;
  549. float const leg_phase =
  550. std::fmod(cycle_phase - get_leg_phase_offset(leg_index) + 1.0F, 1.0F);
  551. if (leg_phase < kSwingDuration) {
  552. return leg_phase / kSwingDuration;
  553. }
  554. return -1.0F;
  555. }
  556. inline auto
  557. get_default_foot_position(const ElephantDimensions &d, int leg_index,
  558. const QVector3D &barrel_center) -> QVector3D {
  559. bool const is_front = (leg_index < 2);
  560. bool const is_left = (leg_index == 0 || leg_index == 2);
  561. float const lateral_sign = is_left ? 1.0F : -1.0F;
  562. float const forward_offset =
  563. is_front ? d.body_length * 0.48F : -d.body_length * 0.48F;
  564. QVector3D const hip =
  565. barrel_center + QVector3D(lateral_sign * d.body_width * 0.52F,
  566. -d.body_height * 0.42F, forward_offset);
  567. return QVector3D(hip.x(), 0.0F, hip.z());
  568. }
  569. inline auto calculate_swing_target(const ElephantDimensions &d, int leg_index,
  570. const QVector3D &barrel_center,
  571. float stride_length) -> QVector3D {
  572. QVector3D const default_pos =
  573. get_default_foot_position(d, leg_index, barrel_center);
  574. return default_pos + QVector3D(0.0F, 0.0F, stride_length);
  575. }
  576. void update_elephant_gait(ElephantGaitState &state,
  577. const ElephantProfile &profile,
  578. const AnimationInputs &anim,
  579. const QVector3D &body_world_pos,
  580. float body_forward_z) {
  581. using namespace GaitSystemConstants;
  582. const ElephantDimensions &d = profile.dims;
  583. const ElephantGait &g = profile.gait;
  584. if (!state.initialized) {
  585. QVector3D const barrel_center(0.0F, d.barrel_center_y, 0.0F);
  586. for (int i = 0; i < 4; ++i) {
  587. state.legs[i].planted_foot =
  588. get_default_foot_position(d, i, barrel_center);
  589. state.legs[i].swing_start = state.legs[i].planted_foot;
  590. state.legs[i].swing_target = state.legs[i].planted_foot;
  591. state.legs[i].in_swing = false;
  592. state.legs[i].swing_progress = 0.0F;
  593. }
  594. state.initialized = true;
  595. }
  596. if (anim.is_moving) {
  597. state.cycle_phase = std::fmod(anim.time / g.cycle_time, 1.0F);
  598. } else {
  599. state.cycle_phase = 0.0F;
  600. for (int i = 0; i < 4; ++i) {
  601. state.legs[i].in_swing = false;
  602. }
  603. }
  604. QVector3D const barrel_center(0.0F, d.barrel_center_y, 0.0F);
  605. float const stride_length = g.stride_swing * 1.8F;
  606. for (int i = 0; i < 4; ++i) {
  607. ElephantLegState &leg = state.legs[i];
  608. float const swing_progress = get_swing_progress(state.cycle_phase, i);
  609. if (swing_progress >= 0.0F && anim.is_moving) {
  610. if (!leg.in_swing) {
  611. leg.swing_start = leg.planted_foot;
  612. leg.swing_target =
  613. calculate_swing_target(d, i, barrel_center, stride_length);
  614. leg.in_swing = true;
  615. }
  616. leg.swing_progress = swing_progress;
  617. } else {
  618. if (leg.in_swing) {
  619. leg.planted_foot = leg.swing_target;
  620. leg.in_swing = false;
  621. }
  622. leg.swing_progress = 0.0F;
  623. }
  624. }
  625. float total_x = 0.0F;
  626. float total_z = 0.0F;
  627. int planted_count = 0;
  628. for (int i = 0; i < 4; ++i) {
  629. if (!state.legs[i].in_swing) {
  630. total_x += state.legs[i].planted_foot.x();
  631. total_z += state.legs[i].planted_foot.z();
  632. ++planted_count;
  633. }
  634. }
  635. if (planted_count > 0) {
  636. float const center_x = total_x / static_cast<float>(planted_count);
  637. float const center_z = total_z / static_cast<float>(planted_count);
  638. state.weight_shift_x = -center_x * kWeightShiftLateral;
  639. state.weight_shift_z = -center_z * kWeightShiftForeAft * 0.5F;
  640. }
  641. if (anim.is_moving) {
  642. float const cycle_sin = std::sin(state.cycle_phase * 2.0F * k_pi);
  643. state.shoulder_lag = cycle_sin * kShoulderLagFactor;
  644. state.hip_lag = -cycle_sin * kHipLagFactor;
  645. } else {
  646. state.shoulder_lag *= 0.9F;
  647. state.hip_lag *= 0.9F;
  648. }
  649. }
  650. void ElephantRendererBase::render_full(
  651. const DrawContext &ctx, const AnimationInputs &anim,
  652. ElephantProfile &profile, const HowdahAttachmentFrame *shared_howdah,
  653. const ElephantMotionSample *shared_motion, ISubmitter &out) const {
  654. const ElephantDimensions &d = profile.dims;
  655. const ElephantVariant &v = profile.variant;
  656. const ElephantGait &g = profile.gait;
  657. ElephantMotionSample const motion =
  658. shared_motion ? *shared_motion : evaluate_elephant_motion(profile, anim);
  659. float const phase = motion.phase;
  660. float const bob = motion.bob;
  661. const bool is_moving = motion.is_moving;
  662. bool const is_fighting =
  663. anim.is_attacking || (anim.combat_phase != CombatAnimPhase::Idle);
  664. float const trunk_swing = motion.trunk_swing;
  665. float const ear_flap = motion.ear_flap;
  666. HowdahAttachmentFrame howdah =
  667. shared_howdah ? *shared_howdah : compute_howdah_frame(profile);
  668. if (!shared_howdah) {
  669. apply_howdah_vertical_offset(howdah, bob);
  670. }
  671. DrawContext elephant_ctx = ctx;
  672. elephant_ctx.model = ctx.model;
  673. elephant_ctx.model.translate(howdah.ground_offset);
  674. uint32_t const vhash = color_hash(v.skin_color);
  675. float const skin_seed_a = hash01(vhash ^ 0x701U);
  676. float const skin_seed_b = hash01(vhash ^ 0x702U);
  677. float const body_sway = is_moving ? std::sin(phase * 2.0F * k_pi) * 0.015F
  678. : std::sin(anim.time * 0.3F) * 0.008F;
  679. QVector3D const barrel_center(body_sway, d.barrel_center_y + bob, 0.0F);
  680. {
  681. QMatrix4x4 body_main = elephant_ctx.model;
  682. body_main.translate(barrel_center);
  683. body_main.scale(d.body_width * 1.05F * 1.2F, d.body_height * 0.95F * 1.2F,
  684. d.body_length * 0.55F * 1.2F);
  685. QVector3D const body_color =
  686. skin_gradient(v.skin_color, 0.60F, 0.0F, skin_seed_a);
  687. out.mesh(get_unit_sphere(), body_main, body_color, nullptr, 1.0F, 6);
  688. }
  689. QVector3D const chest_center =
  690. barrel_center +
  691. QVector3D(0.0F, d.body_height * 0.10F, d.body_length * 0.30F);
  692. {
  693. QMatrix4x4 chest = elephant_ctx.model;
  694. chest.translate(chest_center);
  695. chest.scale(d.body_width * 1.18F * 1.1F, d.body_height * 1.00F * 1.1F,
  696. d.body_length * 0.36F * 1.1F);
  697. out.mesh(get_unit_sphere(), chest,
  698. skin_gradient(v.skin_color, 0.70F, 0.15F, skin_seed_a), nullptr,
  699. 1.0F, 6);
  700. }
  701. QVector3D const rump_center =
  702. barrel_center +
  703. QVector3D(0.0F, d.body_height * 0.02F, -d.body_length * 0.32F);
  704. {
  705. QMatrix4x4 rump = elephant_ctx.model;
  706. rump.translate(rump_center);
  707. rump.scale(d.body_width * 1.10F * 1.1F, d.body_height * 0.98F * 1.1F,
  708. d.body_length * 0.34F * 1.1F);
  709. out.mesh(get_unit_sphere(), rump,
  710. skin_gradient(v.skin_color, 0.55F, -0.20F, skin_seed_b), nullptr,
  711. 1.0F, 6);
  712. }
  713. QVector3D const belly_center =
  714. barrel_center +
  715. QVector3D(0.0F, -d.body_height * 0.22F, d.body_length * 0.05F);
  716. {
  717. QMatrix4x4 belly = elephant_ctx.model;
  718. belly.translate(belly_center);
  719. belly.scale(d.body_width * 1.00F, d.body_height * 0.70F,
  720. d.body_length * 0.55F);
  721. out.mesh(get_unit_sphere(), belly, darken(v.skin_color, 0.92F), nullptr,
  722. 1.0F, 6);
  723. }
  724. QVector3D const neck_base =
  725. chest_center +
  726. QVector3D(0.0F, d.body_height * 0.25F, d.body_length * 0.15F);
  727. QVector3D const neck_top =
  728. neck_base + QVector3D(0.0F, d.neck_length * 0.60F, d.neck_length * 0.50F);
  729. draw_cylinder(out, elephant_ctx.model, neck_base, neck_top, d.neck_width,
  730. skin_gradient(v.skin_color, 0.65F, 0.10F, skin_seed_a), 1.0F);
  731. QVector3D const head_center =
  732. neck_top + QVector3D(0.0F, d.head_height * 0.20F, d.head_length * 0.35F);
  733. {
  734. QMatrix4x4 head = elephant_ctx.model;
  735. head.translate(head_center);
  736. head.scale(d.head_width * 1.0F, d.head_height * 0.90F,
  737. d.head_length * 0.80F);
  738. out.mesh(get_unit_sphere(), head, v.skin_color, nullptr, 1.0F);
  739. }
  740. {
  741. QMatrix4x4 forehead = elephant_ctx.model;
  742. forehead.translate(head_center + QVector3D(0.0F, d.head_height * 0.35F,
  743. d.head_length * 0.10F));
  744. forehead.scale(d.head_width * 0.85F, d.head_height * 0.45F,
  745. d.head_length * 0.50F);
  746. out.mesh(get_unit_sphere(), forehead, lighten(v.skin_color, 1.05F), nullptr,
  747. 1.0F);
  748. }
  749. QVector3D const trunk_base =
  750. head_center +
  751. QVector3D(0.0F, -d.head_height * 0.25F, d.head_length * 0.55F);
  752. constexpr int k_trunk_segments = 12;
  753. QVector3D prev_trunk = trunk_base;
  754. float prev_radius = d.trunk_base_radius;
  755. for (int i = 1; i <= k_trunk_segments; ++i) {
  756. float const t =
  757. static_cast<float>(i) / static_cast<float>(k_trunk_segments);
  758. float const segment_angle = t * k_pi * 0.6F;
  759. float const swing_offset = trunk_swing * t * t;
  760. float const curl_x = std::sin(anim.time * 0.5F + t * 2.0F) * 0.03F * t;
  761. QVector3D const segment_offset(
  762. curl_x + swing_offset,
  763. -d.trunk_length * t * std::cos(segment_angle) * 0.7F,
  764. d.trunk_length * t * std::sin(segment_angle) * 0.5F);
  765. QVector3D const curr_trunk = trunk_base + segment_offset;
  766. float const curr_radius = lerp(d.trunk_base_radius, d.trunk_tip_radius, t);
  767. draw_cylinder(out, elephant_ctx.model, prev_trunk, curr_trunk,
  768. (prev_radius + curr_radius) * 0.5F,
  769. skin_gradient(v.skin_color, 0.50F - t * 0.15F, 0.0F,
  770. skin_seed_a * (1.0F - t * 0.3F)),
  771. 1.0F, 6);
  772. prev_trunk = curr_trunk;
  773. prev_radius = curr_radius;
  774. }
  775. {
  776. QMatrix4x4 trunk_tip_sphere = elephant_ctx.model;
  777. trunk_tip_sphere.translate(prev_trunk);
  778. trunk_tip_sphere.scale(d.trunk_tip_radius * 1.2F);
  779. out.mesh(get_unit_sphere(), trunk_tip_sphere, darken(v.skin_color, 0.85F),
  780. nullptr, 1.0F);
  781. }
  782. auto draw_ear = [&](float side) {
  783. float const flap_angle = ear_flap * side;
  784. QVector3D const ear_base =
  785. head_center + QVector3D(side * d.head_width * 0.75F,
  786. d.head_height * 0.10F, -d.head_length * 0.15F);
  787. QVector3D const ear_tip =
  788. ear_base + QVector3D(side * d.ear_width * (0.85F + flap_angle * 0.3F),
  789. -d.ear_height * 0.40F, -d.ear_width * 0.20F);
  790. QVector3D const ear_top =
  791. ear_base + QVector3D(side * d.ear_width * 0.50F, d.ear_height * 0.45F,
  792. -d.ear_width * 0.10F);
  793. draw_cylinder(out, elephant_ctx.model, ear_base, ear_tip, d.ear_thickness,
  794. v.skin_color, 1.0F, 6);
  795. draw_cylinder(out, elephant_ctx.model, ear_base, ear_top,
  796. d.ear_thickness * 0.8F, v.skin_color, 1.0F, 6);
  797. {
  798. QMatrix4x4 ear_main = elephant_ctx.model;
  799. QVector3D const ear_center = (ear_base + ear_tip + ear_top) * 0.33F;
  800. ear_main.translate(ear_center);
  801. ear_main.rotate(side * (15.0F + flap_angle * 20.0F), 0.0F, 0.0F, 1.0F);
  802. ear_main.scale(d.ear_width * 0.70F, d.ear_height * 0.65F,
  803. d.ear_thickness * 0.25F);
  804. out.mesh(get_unit_sphere(), ear_main, v.skin_color, nullptr, 1.0F, 6);
  805. }
  806. {
  807. QMatrix4x4 ear_inner = elephant_ctx.model;
  808. QVector3D const inner_center =
  809. (ear_base + ear_tip + ear_top) * 0.33F +
  810. QVector3D(side * d.ear_thickness * 0.5F, 0.0F, d.ear_thickness);
  811. ear_inner.translate(inner_center);
  812. ear_inner.rotate(side * (15.0F + flap_angle * 20.0F), 0.0F, 0.0F, 1.0F);
  813. ear_inner.scale(d.ear_width * 0.62F, d.ear_height * 0.55F,
  814. d.ear_thickness * 0.10F);
  815. out.mesh(get_unit_sphere(), ear_inner, v.ear_inner_color, nullptr, 1.0F,
  816. 6);
  817. }
  818. };
  819. draw_ear(1.0F);
  820. draw_ear(-1.0F);
  821. auto draw_tusk = [&](float side) {
  822. QVector3D const tusk_base =
  823. head_center + QVector3D(side * d.head_width * 0.35F,
  824. -d.head_height * 0.30F, d.head_length * 0.45F);
  825. QVector3D const tusk_tip =
  826. tusk_base + QVector3D(side * d.tusk_length * 0.25F,
  827. -d.tusk_length * 0.15F, d.tusk_length * 0.90F);
  828. QVector3D const tusk_ctrl =
  829. (tusk_base + tusk_tip) * 0.5F +
  830. QVector3D(side * d.tusk_length * 0.08F, -d.tusk_length * 0.10F, 0.0F);
  831. constexpr int k_tusk_segments = 6;
  832. QVector3D prev_tusk = tusk_base;
  833. for (int i = 1; i <= k_tusk_segments; ++i) {
  834. float const t =
  835. static_cast<float>(i) / static_cast<float>(k_tusk_segments);
  836. QVector3D const curr_tusk = bezier(tusk_base, tusk_ctrl, tusk_tip, t);
  837. float const seg_radius = d.tusk_radius * (1.0F - t * 0.6F);
  838. draw_cylinder(out, elephant_ctx.model, prev_tusk, curr_tusk, seg_radius,
  839. v.tusk_color, 1.0F, 8);
  840. prev_tusk = curr_tusk;
  841. }
  842. };
  843. draw_tusk(1.0F);
  844. draw_tusk(-1.0F);
  845. QVector3D const eye_left =
  846. head_center + QVector3D(d.head_width * 0.45F, d.head_height * 0.15F,
  847. d.head_length * 0.25F);
  848. QVector3D const eye_right =
  849. head_center + QVector3D(-d.head_width * 0.45F, d.head_height * 0.15F,
  850. d.head_length * 0.25F);
  851. float const eye_radius = d.head_width * 0.08F;
  852. QVector3D const eye_color(0.08F, 0.06F, 0.05F);
  853. {
  854. QMatrix4x4 eye_l = elephant_ctx.model;
  855. eye_l.translate(eye_left);
  856. eye_l.scale(eye_radius);
  857. out.mesh(get_unit_sphere(), eye_l, eye_color, nullptr, 1.0F);
  858. }
  859. {
  860. QMatrix4x4 eye_r = elephant_ctx.model;
  861. eye_r.translate(eye_right);
  862. eye_r.scale(eye_radius);
  863. out.mesh(get_unit_sphere(), eye_r, eye_color, nullptr, 1.0F);
  864. }
  865. float const upper_len = d.leg_length * 0.55F;
  866. float const lower_len = d.leg_length * 0.45F;
  867. float const full_stride = g.stride_swing * 1.2F;
  868. float const lift_height = d.leg_length * 0.18F;
  869. auto draw_leg_phase = [&](int leg_index) {
  870. bool const is_front = (leg_index < 2);
  871. bool const is_left = (leg_index == 0 || leg_index == 2);
  872. float const lateral_sign = is_left ? 1.0F : -1.0F;
  873. float const base_forward =
  874. is_front ? d.body_length * 0.42F : -d.body_length * 0.42F;
  875. float phase_offset = 0.0F;
  876. switch (leg_index) {
  877. case 0:
  878. phase_offset = 0.00F;
  879. break;
  880. case 1:
  881. phase_offset = 0.50F;
  882. break;
  883. case 2:
  884. phase_offset = 0.75F;
  885. break;
  886. case 3:
  887. phase_offset = 0.25F;
  888. break;
  889. }
  890. float stride_offset = 0.0F;
  891. float lift = 0.0F;
  892. float forward_bias = 0.0F;
  893. if (is_fighting) {
  894. float const stomp_period = 1.15F;
  895. float const t = std::fmod(anim.time / stomp_period + phase_offset, 1.0F);
  896. float intensity = 0.70F;
  897. switch (anim.combat_phase) {
  898. case CombatAnimPhase::WindUp:
  899. intensity = 0.85F;
  900. break;
  901. case CombatAnimPhase::Strike:
  902. case CombatAnimPhase::Impact:
  903. intensity = 1.0F;
  904. break;
  905. case CombatAnimPhase::Recover:
  906. intensity = 0.80F;
  907. break;
  908. default:
  909. break;
  910. }
  911. float const local = t;
  912. float const base_stomp_height = d.leg_length * 0.62F;
  913. float const stomp_height = base_stomp_height * (0.7F + 0.3F * intensity);
  914. float const leg_multiplier = is_front ? 1.0F : 0.75F;
  915. float const final_stomp_height = stomp_height * leg_multiplier;
  916. float const stomp_reach = full_stride * 0.35F * intensity;
  917. float const impact_sink = d.foot_radius * (0.22F + 0.10F * intensity) *
  918. (is_front ? 1.0F : 0.85F);
  919. if (local < 0.45F) {
  920. float const u = local / 0.45F;
  921. float const ease = 1.0F - std::cos(u * k_pi * 0.5F);
  922. lift = ease * final_stomp_height;
  923. stride_offset = stomp_reach * ease * 0.35F;
  924. forward_bias = 1.0F;
  925. } else if (local < 0.65F) {
  926. lift = final_stomp_height;
  927. stride_offset = stomp_reach * 0.35F;
  928. forward_bias = 1.0F;
  929. } else if (local < 0.78F) {
  930. float const u = (local - 0.65F) / 0.13F;
  931. float const slam = (1.0F - u);
  932. float const slam_pow = slam * slam * slam * slam;
  933. lift = slam_pow * final_stomp_height - impact_sink * (u * u);
  934. stride_offset = stomp_reach * (0.35F + u * 0.65F);
  935. forward_bias = -1.0F;
  936. } else {
  937. float const u = (local - 0.78F) / 0.22F;
  938. float const recover = 1.0F - (u * u);
  939. lift = -impact_sink * recover;
  940. stride_offset = stomp_reach * (1.0F - u * 0.25F);
  941. forward_bias = -0.6F;
  942. }
  943. } else {
  944. float const leg_phase = std::fmod(phase + phase_offset, 1.0F);
  945. constexpr float k_swing_end = 0.5F;
  946. bool const in_swing = (leg_phase < k_swing_end);
  947. if (in_swing) {
  948. float const t = leg_phase / k_swing_end;
  949. float const ease = t * t * (3.0F - 2.0F * t);
  950. stride_offset = (-0.5F + ease) * full_stride;
  951. forward_bias = 1.0F;
  952. if (is_moving) {
  953. lift = std::sin(t * k_pi) * lift_height;
  954. }
  955. } else {
  956. float const t = (leg_phase - k_swing_end) / (1.0F - k_swing_end);
  957. float const ease = t * t * (3.0F - 2.0F * t);
  958. stride_offset = (0.5F - ease) * full_stride;
  959. forward_bias = -1.0F;
  960. lift = 0.0F;
  961. }
  962. }
  963. QVector3D const hip =
  964. barrel_center + QVector3D(lateral_sign * d.body_width * 0.48F,
  965. -d.body_height * 0.40F, base_forward);
  966. QVector3D const foot_target(
  967. hip.x(), lift,
  968. hip.z() + ((is_moving || is_fighting) ? stride_offset : 0.0F));
  969. ElephantLegPose pose = solve_elephant_leg_ik(hip, foot_target, upper_len,
  970. lower_len, lateral_sign);
  971. float const upper_radius = d.leg_radius * (is_front ? 1.05F : 1.10F);
  972. float const lower_radius = d.leg_radius * (is_front ? 0.80F : 0.85F);
  973. draw_cylinder(out, elephant_ctx.model, pose.hip, pose.knee, upper_radius,
  974. skin_gradient(v.skin_color, 0.45F,
  975. forward_bias > 0.0F ? 0.1F : -0.1F,
  976. skin_seed_a),
  977. 1.0F, 6);
  978. {
  979. QMatrix4x4 knee_joint = elephant_ctx.model;
  980. knee_joint.translate(pose.knee);
  981. knee_joint.scale(lower_radius * 1.15F);
  982. out.mesh(get_unit_sphere(), knee_joint, darken(v.skin_color, 0.92F),
  983. nullptr, 1.0F, 6);
  984. }
  985. draw_cylinder(out, elephant_ctx.model, pose.knee, pose.foot, lower_radius,
  986. skin_gradient(v.skin_color, 0.40F, 0.0F, skin_seed_b), 1.0F,
  987. 6);
  988. {
  989. QVector3D const ankle =
  990. pose.foot + QVector3D(0.0F, d.foot_radius * 0.15F, 0.0F);
  991. QMatrix4x4 ankle_joint = elephant_ctx.model;
  992. ankle_joint.translate(ankle);
  993. ankle_joint.scale(lower_radius * 1.10F);
  994. out.mesh(get_unit_sphere(), ankle_joint, darken(v.skin_color, 0.90F),
  995. nullptr, 1.0F, 6);
  996. }
  997. {
  998. QMatrix4x4 foot_pad = elephant_ctx.model;
  999. foot_pad.translate(pose.foot +
  1000. QVector3D(0.0F, -d.foot_radius * 0.18F, 0.0F));
  1001. foot_pad.scale(d.foot_radius * 1.10F, d.foot_radius * 0.70F,
  1002. d.foot_radius * 1.20F);
  1003. out.mesh(get_unit_sphere(), foot_pad, darken(v.skin_color, 0.80F),
  1004. nullptr, 1.0F, 8);
  1005. }
  1006. constexpr int k_toenails = 4;
  1007. for (int t = 0; t < k_toenails; ++t) {
  1008. float const toe_angle =
  1009. (static_cast<float>(t) / static_cast<float>(k_toenails - 1) - 0.5F) *
  1010. k_pi * 0.6F;
  1011. QVector3D const nail_pos =
  1012. pose.foot + QVector3D(std::sin(toe_angle) * d.foot_radius * 0.8F,
  1013. -d.foot_radius * 0.35F,
  1014. std::cos(toe_angle) * d.foot_radius * 0.9F);
  1015. {
  1016. QMatrix4x4 nail = elephant_ctx.model;
  1017. nail.translate(nail_pos);
  1018. nail.scale(d.foot_radius * 0.18F, d.foot_radius * 0.25F,
  1019. d.foot_radius * 0.22F);
  1020. out.mesh(get_unit_sphere(), nail, v.toenail_color, nullptr, 1.0F, 8);
  1021. }
  1022. }
  1023. };
  1024. draw_leg_phase(0);
  1025. draw_leg_phase(1);
  1026. draw_leg_phase(2);
  1027. draw_leg_phase(3);
  1028. QVector3D const tail_base =
  1029. rump_center +
  1030. QVector3D(0.0F, d.body_height * 0.15F, -d.body_length * 0.32F);
  1031. float const tail_sway = is_moving ? std::sin(phase * 4.0F * k_pi) * 0.08F
  1032. : std::sin(anim.time * 0.7F) * 0.04F;
  1033. constexpr int k_tail_segments = 8;
  1034. QVector3D prev_tail = tail_base;
  1035. for (int i = 1; i <= k_tail_segments; ++i) {
  1036. float const t = static_cast<float>(i) / static_cast<float>(k_tail_segments);
  1037. QVector3D const curr_tail =
  1038. tail_base + QVector3D(tail_sway * t, -d.tail_length * t * 0.85F,
  1039. -d.tail_length * t * 0.35F);
  1040. float const seg_radius = d.leg_radius * 0.25F * (1.0F - t * 0.6F);
  1041. draw_cylinder(out, elephant_ctx.model, prev_tail, curr_tail, seg_radius,
  1042. darken(v.skin_color, 0.85F), 1.0F, 6);
  1043. prev_tail = curr_tail;
  1044. }
  1045. {
  1046. QMatrix4x4 tail_tuft = elephant_ctx.model;
  1047. tail_tuft.translate(prev_tail);
  1048. tail_tuft.scale(d.leg_radius * 0.20F, d.leg_radius * 0.35F,
  1049. d.leg_radius * 0.15F);
  1050. out.mesh(get_unit_sphere(), tail_tuft, darken(v.skin_color, 0.70F), nullptr,
  1051. 1.0F);
  1052. }
  1053. ElephantBodyFrames body_frames;
  1054. QVector3D const forward(0.0F, 0.0F, 1.0F);
  1055. QVector3D const up(0.0F, 1.0F, 0.0F);
  1056. QVector3D const right(1.0F, 0.0F, 0.0F);
  1057. body_frames.head.origin = head_center;
  1058. body_frames.head.right = right;
  1059. body_frames.head.up = up;
  1060. body_frames.head.forward = forward;
  1061. body_frames.back_center.origin = howdah.howdah_center;
  1062. body_frames.back_center.right = right;
  1063. body_frames.back_center.up = up;
  1064. body_frames.back_center.forward = forward;
  1065. body_frames.howdah.origin = howdah.seat_position;
  1066. body_frames.howdah.right = right;
  1067. body_frames.howdah.up = up;
  1068. body_frames.howdah.forward = forward;
  1069. draw_howdah(elephant_ctx, anim, profile, howdah, phase, bob, body_frames,
  1070. out);
  1071. }
  1072. void ElephantRendererBase::render_simplified(
  1073. const DrawContext &ctx, const AnimationInputs &anim,
  1074. ElephantProfile &profile, const HowdahAttachmentFrame *shared_howdah,
  1075. const ElephantMotionSample *shared_motion, ISubmitter &out) const {
  1076. const ElephantDimensions &d = profile.dims;
  1077. const ElephantVariant &v = profile.variant;
  1078. const ElephantGait &g = profile.gait;
  1079. ElephantMotionSample const motion =
  1080. shared_motion ? *shared_motion : evaluate_elephant_motion(profile, anim);
  1081. float const phase = motion.phase;
  1082. float const bob = motion.bob;
  1083. const bool is_moving = motion.is_moving;
  1084. bool const is_fighting =
  1085. anim.is_attacking || (anim.combat_phase != CombatAnimPhase::Idle);
  1086. HowdahAttachmentFrame howdah =
  1087. shared_howdah ? *shared_howdah : compute_howdah_frame(profile);
  1088. if (!shared_howdah) {
  1089. apply_howdah_vertical_offset(howdah, bob);
  1090. }
  1091. DrawContext elephant_ctx = ctx;
  1092. elephant_ctx.model = ctx.model;
  1093. elephant_ctx.model.translate(howdah.ground_offset);
  1094. QVector3D const barrel_center(0.0F, d.barrel_center_y + bob, 0.0F);
  1095. {
  1096. QMatrix4x4 body = elephant_ctx.model;
  1097. body.translate(barrel_center);
  1098. body.scale(d.body_width * 1.0F, d.body_height * 0.90F,
  1099. d.body_length * 0.75F);
  1100. out.mesh(get_unit_sphere(), body, v.skin_color, nullptr, 1.0F, 6);
  1101. }
  1102. QVector3D const neck_base =
  1103. barrel_center +
  1104. QVector3D(0.0F, d.body_height * 0.20F, d.body_length * 0.45F);
  1105. QVector3D const head_center =
  1106. neck_base + QVector3D(0.0F, d.neck_length * 0.50F, d.head_length * 0.60F);
  1107. draw_cylinder(out, elephant_ctx.model, neck_base, head_center,
  1108. d.neck_width * 0.85F, v.skin_color, 1.0F);
  1109. {
  1110. QMatrix4x4 head = elephant_ctx.model;
  1111. head.translate(head_center);
  1112. head.scale(d.head_width * 0.85F, d.head_height * 0.80F,
  1113. d.head_length * 0.70F);
  1114. out.mesh(get_unit_sphere(), head, v.skin_color, nullptr, 1.0F);
  1115. }
  1116. QVector3D const trunk_end =
  1117. head_center +
  1118. QVector3D(0.0F, -d.trunk_length * 0.50F, d.trunk_length * 0.40F);
  1119. draw_cone(out, elephant_ctx.model, trunk_end, head_center,
  1120. d.trunk_base_radius * 0.8F, darken(v.skin_color, 0.90F), 1.0F);
  1121. auto draw_simple_leg = [&](float lateral_sign, float forward_bias,
  1122. float phase_offset) {
  1123. float const leg_phase =
  1124. is_fighting ? std::fmod(anim.time / 1.15F + phase_offset, 1.0F)
  1125. : std::fmod(phase + phase_offset, 1.0F);
  1126. float stride = 0.0F;
  1127. float lift = 0.0F;
  1128. if (is_fighting) {
  1129. bool const is_front = (forward_bias > 0.0F);
  1130. float const local = leg_phase;
  1131. float intensity = 0.70F;
  1132. switch (anim.combat_phase) {
  1133. case CombatAnimPhase::WindUp:
  1134. intensity = 0.85F;
  1135. break;
  1136. case CombatAnimPhase::Strike:
  1137. case CombatAnimPhase::Impact:
  1138. intensity = 1.0F;
  1139. break;
  1140. case CombatAnimPhase::Recover:
  1141. intensity = 0.80F;
  1142. break;
  1143. default:
  1144. break;
  1145. }
  1146. float const base_stomp = d.leg_length * 0.58F;
  1147. float const stomp_height =
  1148. base_stomp * (0.7F + 0.3F * intensity) * (is_front ? 1.0F : 0.80F);
  1149. float const stomp_stride = g.stride_swing * 0.32F * intensity;
  1150. float const impact_sink = d.foot_radius * (0.20F + 0.10F * intensity) *
  1151. (is_front ? 1.0F : 0.85F);
  1152. if (local < 0.45F) {
  1153. float const u = local / 0.45F;
  1154. float const ease = 1.0F - std::cos(u * k_pi * 0.5F);
  1155. lift = ease * stomp_height;
  1156. stride = stomp_stride * ease * 0.35F;
  1157. } else if (local < 0.65F) {
  1158. lift = stomp_height;
  1159. stride = stomp_stride * 0.35F;
  1160. } else if (local < 0.78F) {
  1161. float const u = (local - 0.65F) / 0.13F;
  1162. float const slam = (1.0F - u);
  1163. float const slam_pow = slam * slam * slam * slam;
  1164. lift = slam_pow * stomp_height - impact_sink * (u * u);
  1165. stride = stomp_stride * (0.35F + u * 0.65F);
  1166. } else {
  1167. float const u = (local - 0.78F) / 0.22F;
  1168. float const recover = 1.0F - (u * u);
  1169. lift = -impact_sink * recover;
  1170. stride = stomp_stride * (1.0F - u * 0.25F);
  1171. }
  1172. } else if (is_moving) {
  1173. float const angle = leg_phase * 2.0F * k_pi;
  1174. stride = std::sin(angle) * g.stride_swing * 0.6F;
  1175. float const lift_raw = std::sin(angle);
  1176. lift = lift_raw > 0.0F ? lift_raw * g.stride_lift * 0.8F : 0.0F;
  1177. }
  1178. QVector3D const hip =
  1179. barrel_center + QVector3D(lateral_sign * d.body_width * 0.40F,
  1180. -d.body_height * 0.30F,
  1181. forward_bias + stride);
  1182. QVector3D const foot =
  1183. hip + QVector3D(0.0F, -d.leg_length * 0.85F + lift, stride * 0.3F);
  1184. draw_cylinder(out, elephant_ctx.model, hip, foot, d.leg_radius * 0.85F,
  1185. darken(v.skin_color, 0.88F), 1.0F, 6);
  1186. {
  1187. QMatrix4x4 foot_pad = elephant_ctx.model;
  1188. foot_pad.translate(foot + QVector3D(0.0F, -d.foot_radius * 0.18F, 0.0F));
  1189. foot_pad.scale(d.foot_radius * 1.00F, d.foot_radius * 0.65F,
  1190. d.foot_radius * 1.10F);
  1191. out.mesh(get_unit_sphere(), foot_pad, darken(v.skin_color, 0.75F),
  1192. nullptr, 1.0F, 8);
  1193. }
  1194. };
  1195. float const front_forward = d.body_length * 0.35F;
  1196. float const rear_forward = -d.body_length * 0.35F;
  1197. draw_simple_leg(1.0F, front_forward, g.front_leg_phase);
  1198. draw_simple_leg(-1.0F, front_forward, g.front_leg_phase + 0.50F);
  1199. draw_simple_leg(1.0F, rear_forward, g.rear_leg_phase);
  1200. draw_simple_leg(-1.0F, rear_forward, g.rear_leg_phase + 0.50F);
  1201. }
  1202. void ElephantRendererBase::render_minimal(
  1203. const DrawContext &ctx, ElephantProfile &profile,
  1204. const ElephantMotionSample *shared_motion, ISubmitter &out) const {
  1205. const ElephantDimensions &d = profile.dims;
  1206. const ElephantVariant &v = profile.variant;
  1207. float const bob = shared_motion ? shared_motion->bob : 0.0F;
  1208. HowdahAttachmentFrame howdah = compute_howdah_frame(profile);
  1209. apply_howdah_vertical_offset(howdah, bob);
  1210. DrawContext elephant_ctx = ctx;
  1211. elephant_ctx.model = ctx.model;
  1212. elephant_ctx.model.translate(howdah.ground_offset);
  1213. QVector3D const center(0.0F, d.barrel_center_y + bob, 0.0F);
  1214. QMatrix4x4 body = elephant_ctx.model;
  1215. body.translate(center);
  1216. body.scale(d.body_width * 1.2F, d.body_height + d.neck_length * 0.3F,
  1217. d.body_length + d.head_length * 0.3F);
  1218. out.mesh(get_unit_sphere(), body, v.skin_color, nullptr, 1.0F, 6);
  1219. for (int i = 0; i < 4; ++i) {
  1220. float const x_sign = (i % 2 == 0) ? 1.0F : -1.0F;
  1221. float const z_offset =
  1222. (i < 2) ? d.body_length * 0.30F : -d.body_length * 0.30F;
  1223. QVector3D const top = center + QVector3D(x_sign * d.body_width * 0.38F,
  1224. -d.body_height * 0.25F, z_offset);
  1225. QVector3D const bottom = top + QVector3D(0.0F, -d.leg_length * 0.70F, 0.0F);
  1226. draw_cylinder(out, elephant_ctx.model, top, bottom, d.leg_radius * 0.70F,
  1227. darken(v.skin_color, 0.80F), 1.0F, 6);
  1228. }
  1229. }
  1230. namespace {
  1231. auto resolve_renderer_for_submitter(ISubmitter &out) -> Renderer * {
  1232. if (auto *renderer = dynamic_cast<Renderer *>(&out)) {
  1233. return renderer;
  1234. }
  1235. if (auto *batch = dynamic_cast<BatchingSubmitter *>(&out)) {
  1236. return dynamic_cast<Renderer *>(batch->fallback_submitter());
  1237. }
  1238. return nullptr;
  1239. }
  1240. } // namespace
  1241. void ElephantRendererBase::render(const DrawContext &ctx,
  1242. const AnimationInputs &anim,
  1243. ElephantProfile &profile,
  1244. const HowdahAttachmentFrame *shared_howdah,
  1245. const ElephantMotionSample *shared_motion,
  1246. ISubmitter &out, HorseLOD lod) const {
  1247. HorseLOD effective_lod = lod;
  1248. if (ctx.force_horse_lod) {
  1249. effective_lod = ctx.forced_horse_lod;
  1250. }
  1251. bool use_cache = ctx.allow_template_cache && !ctx.renderer_id.empty();
  1252. std::uint32_t seed = 0U;
  1253. if (ctx.has_seed_override) {
  1254. seed = ctx.seed_override;
  1255. } else if (ctx.entity != nullptr) {
  1256. seed = static_cast<std::uint32_t>(
  1257. reinterpret_cast<std::uintptr_t>(ctx.entity) & 0xFFFFFFFFU);
  1258. }
  1259. std::uint8_t variant_key = ctx.has_variant_override
  1260. ? ctx.variant_override
  1261. : resolve_variant_key_from_seed(seed);
  1262. AnimKey anim_key = make_anim_key(anim, 0.0F, 0);
  1263. auto *unit_comp =
  1264. ctx.entity ? ctx.entity->get_component<Engine::Core::UnitComponent>()
  1265. : nullptr;
  1266. std::uint32_t owner_id = (unit_comp != nullptr)
  1267. ? static_cast<std::uint32_t>(unit_comp->owner_id)
  1268. : 0U;
  1269. TemplateKey key;
  1270. key.renderer_id = ctx.renderer_id;
  1271. key.owner_id = owner_id;
  1272. key.lod = static_cast<std::uint8_t>(effective_lod);
  1273. key.mount_lod = 0;
  1274. key.variant = variant_key;
  1275. key.attack_variant = anim_key.attack_variant;
  1276. key.state = anim_key.state;
  1277. key.combat_phase = anim_key.combat_phase;
  1278. key.frame = anim_key.frame;
  1279. const TemplateCache::DenseDomainHandle dense_domain =
  1280. TemplateCache::instance().get_dense_domain_handle(
  1281. key.renderer_id, key.owner_id, key.lod, key.mount_lod);
  1282. const std::size_t dense_slot =
  1283. TemplateCache::dense_slot_index(key.variant, anim_key);
  1284. auto build_template = [&]() -> PoseTemplate {
  1285. thread_local TemplateRecorder recorder;
  1286. recorder.reset(192);
  1287. recorder.set_current_shader(nullptr);
  1288. if (auto *outer = dynamic_cast<Renderer *>(&out)) {
  1289. recorder.set_current_shader(outer->get_current_shader());
  1290. }
  1291. DrawContext build_ctx = ctx;
  1292. build_ctx.model = QMatrix4x4();
  1293. build_ctx.camera = nullptr;
  1294. build_ctx.allow_template_cache = false;
  1295. build_ctx.force_horse_lod = true;
  1296. build_ctx.forced_horse_lod = effective_lod;
  1297. QVector3D fabric_base = profile.variant.howdah_fabric_color;
  1298. QVector3D metal_base = profile.variant.howdah_metal_color;
  1299. std::uint32_t variant_seed = resolve_variant_seed(unit_comp, variant_key);
  1300. ElephantProfile variant_profile = get_or_create_cached_elephant_profile(
  1301. variant_seed, fabric_base, metal_base);
  1302. AnimationInputs build_anim = make_animation_inputs(anim_key);
  1303. switch (effective_lod) {
  1304. case HorseLOD::Full:
  1305. render_full(build_ctx, build_anim, variant_profile, nullptr, nullptr,
  1306. recorder);
  1307. break;
  1308. case HorseLOD::Reduced:
  1309. render_simplified(build_ctx, build_anim, variant_profile, nullptr,
  1310. nullptr, recorder);
  1311. break;
  1312. case HorseLOD::Minimal:
  1313. render_minimal(build_ctx, variant_profile, nullptr, recorder);
  1314. break;
  1315. case HorseLOD::Billboard:
  1316. break;
  1317. }
  1318. PoseTemplate built;
  1319. built.commands = recorder.take_commands();
  1320. return built;
  1321. };
  1322. if (ctx.template_prewarm) {
  1323. if (use_cache && effective_lod != HorseLOD::Billboard) {
  1324. (void)TemplateCache::instance().get_or_build_dense(
  1325. dense_domain, dense_slot, key, build_template);
  1326. }
  1327. return;
  1328. }
  1329. ++s_elephantRenderStats.elephants_total;
  1330. if (effective_lod == HorseLOD::Billboard) {
  1331. ++s_elephantRenderStats.elephants_skipped_lod;
  1332. return;
  1333. }
  1334. if (use_cache) {
  1335. const PoseTemplate *tpl = TemplateCache::instance().get_or_build_dense(
  1336. dense_domain, dense_slot, key, build_template);
  1337. if (tpl != nullptr && !tpl->commands.empty()) {
  1338. Renderer *renderer = resolve_renderer_for_submitter(out);
  1339. Shader *last_shader = nullptr;
  1340. for (const auto &cmd : tpl->commands) {
  1341. if (renderer != nullptr && cmd.shader != last_shader) {
  1342. renderer->set_current_shader(cmd.shader);
  1343. last_shader = cmd.shader;
  1344. }
  1345. QMatrix4x4 world_model =
  1346. Render::Geom::multiply_affine(ctx.model, cmd.local_model);
  1347. out.mesh(cmd.mesh, world_model, cmd.color, cmd.texture, cmd.alpha,
  1348. cmd.material_id);
  1349. }
  1350. if (renderer != nullptr) {
  1351. renderer->set_current_shader(nullptr);
  1352. }
  1353. ++s_elephantRenderStats.elephants_rendered;
  1354. switch (effective_lod) {
  1355. case HorseLOD::Full:
  1356. ++s_elephantRenderStats.lod_full;
  1357. break;
  1358. case HorseLOD::Reduced:
  1359. ++s_elephantRenderStats.lod_reduced;
  1360. break;
  1361. case HorseLOD::Minimal:
  1362. ++s_elephantRenderStats.lod_minimal;
  1363. break;
  1364. case HorseLOD::Billboard:
  1365. break;
  1366. }
  1367. return;
  1368. }
  1369. }
  1370. ++s_elephantRenderStats.elephants_rendered;
  1371. switch (effective_lod) {
  1372. case HorseLOD::Full:
  1373. ++s_elephantRenderStats.lod_full;
  1374. render_full(ctx, anim, profile, shared_howdah, shared_motion, out);
  1375. break;
  1376. case HorseLOD::Reduced:
  1377. ++s_elephantRenderStats.lod_reduced;
  1378. render_simplified(ctx, anim, profile, shared_howdah, shared_motion, out);
  1379. break;
  1380. case HorseLOD::Minimal:
  1381. ++s_elephantRenderStats.lod_minimal;
  1382. render_minimal(ctx, profile, shared_motion, out);
  1383. break;
  1384. case HorseLOD::Billboard:
  1385. break;
  1386. }
  1387. }
  1388. void ElephantRendererBase::render(const DrawContext &ctx,
  1389. const AnimationInputs &anim,
  1390. ElephantProfile &profile,
  1391. const HowdahAttachmentFrame *shared_howdah,
  1392. const ElephantMotionSample *shared_motion,
  1393. ISubmitter &out) const {
  1394. render(ctx, anim, profile, shared_howdah, shared_motion, out, HorseLOD::Full);
  1395. }
  1396. } // namespace Render::GL