rig.cpp 52 KB

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