rig.cpp 71 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883
  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 "horse_animation_controller.h"
  9. #include <QMatrix4x4>
  10. #include <QVector3D>
  11. #include <algorithm>
  12. #include <cmath>
  13. #include <cstdint>
  14. #include <mutex>
  15. #include <numbers>
  16. #include <qmatrix4x4.h>
  17. #include <qvectornd.h>
  18. #include <unordered_map>
  19. namespace Render::GL {
  20. static HorseRenderStats s_horseRenderStats;
  21. auto get_horse_render_stats() -> const HorseRenderStats & {
  22. return s_horseRenderStats;
  23. }
  24. void reset_horse_render_stats() { s_horseRenderStats.reset(); }
  25. using Render::Geom::clamp01;
  26. using Render::Geom::cone_from_to;
  27. using Render::Geom::cylinder_between;
  28. using Render::Geom::lerp;
  29. using Render::Geom::smoothstep;
  30. namespace {
  31. struct CachedHorseProfileEntry {
  32. HorseProfile profile;
  33. QVector3D leather_base;
  34. QVector3D cloth_base;
  35. uint32_t frame_number{0};
  36. };
  37. using HorseProfileCacheKey = uint64_t;
  38. static std::unordered_map<HorseProfileCacheKey, CachedHorseProfileEntry>
  39. s_horse_profile_cache;
  40. static std::mutex s_horse_profile_cache_mutex;
  41. static uint32_t s_horse_cache_frame = 0;
  42. constexpr uint32_t k_horse_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 make_horse_profile_cache_key(
  47. uint32_t seed, const QVector3D &leather_base,
  48. const QVector3D &cloth_base) -> HorseProfileCacheKey {
  49. auto color_to_5bit = [](float c) -> uint32_t {
  50. return static_cast<uint32_t>(std::clamp(c, 0.0F, 1.0F) *
  51. k_color_hash_multiplier);
  52. };
  53. uint32_t color_hash = color_to_5bit(leather_base.x());
  54. color_hash |= color_to_5bit(leather_base.y()) << 5;
  55. color_hash |= color_to_5bit(leather_base.z()) << 10;
  56. color_hash |= color_to_5bit(cloth_base.x()) << 15;
  57. color_hash |= color_to_5bit(cloth_base.y()) << 20;
  58. color_hash |= color_to_5bit(cloth_base.z()) << 25;
  59. return (static_cast<uint64_t>(seed) << 32) |
  60. static_cast<uint64_t>(color_hash);
  61. }
  62. constexpr float k_pi = std::numbers::pi_v<float>;
  63. constexpr int k_hash_shift_16 = 16;
  64. constexpr int k_hash_shift_15 = 15;
  65. constexpr uint32_t k_hash_mult_1 = 0x7Feb352dU;
  66. constexpr uint32_t k_hash_mult_2 = 0x846ca68bU;
  67. constexpr uint32_t k_hash_mask_24bit = 0xFFFFFF;
  68. constexpr float k_hash_divisor = 16777216.0F;
  69. constexpr float k_rgb_max = 255.0F;
  70. constexpr int k_rgb_shift_red = 16;
  71. constexpr int k_rgb_shift_green = 8;
  72. inline auto hash01(uint32_t x) -> float {
  73. x ^= x >> k_hash_shift_16;
  74. x *= k_hash_mult_1;
  75. x ^= x >> k_hash_shift_15;
  76. x *= k_hash_mult_2;
  77. x ^= x >> k_hash_shift_16;
  78. return (x & k_hash_mask_24bit) / k_hash_divisor;
  79. }
  80. inline auto rand_between(uint32_t seed, uint32_t salt, float min_val,
  81. float max_val) -> float {
  82. const float t = hash01(seed ^ salt);
  83. return min_val + (max_val - min_val) * t;
  84. }
  85. inline auto saturate(float x) -> float {
  86. return std::min(1.0F, std::max(0.0F, x));
  87. }
  88. inline auto rotate_around_y(const QVector3D &v, float angle) -> QVector3D {
  89. float const s = std::sin(angle);
  90. float const c = std::cos(angle);
  91. return {v.x() * c + v.z() * s, v.y(), -v.x() * s + v.z() * c};
  92. }
  93. inline auto rotate_around_z(const QVector3D &v, float angle) -> QVector3D {
  94. float const s = std::sin(angle);
  95. float const c = std::cos(angle);
  96. return {v.x() * c - v.y() * s, v.x() * s + v.y() * c, v.z()};
  97. }
  98. inline auto darken(const QVector3D &c, float k) -> QVector3D { return c * k; }
  99. inline auto lighten(const QVector3D &c, float k) -> QVector3D {
  100. return {saturate(c.x() * k), saturate(c.y() * k), saturate(c.z() * k)};
  101. }
  102. constexpr float k_coat_highlight_base = 0.55F;
  103. constexpr float k_coat_vertical_factor = 0.35F;
  104. constexpr float k_coat_longitudinal_factor = 0.20F;
  105. constexpr float k_coat_seed_factor = 0.08F;
  106. constexpr float k_coat_bright_factor = 1.08F;
  107. constexpr float k_coat_shadow_factor = 0.86F;
  108. inline auto coat_gradient(const QVector3D &coat, float vertical_factor,
  109. float longitudinal_factor, float seed) -> QVector3D {
  110. float const highlight = saturate(
  111. k_coat_highlight_base + vertical_factor * k_coat_vertical_factor -
  112. longitudinal_factor * k_coat_longitudinal_factor +
  113. seed * k_coat_seed_factor);
  114. QVector3D const bright = lighten(coat, k_coat_bright_factor);
  115. QVector3D const shadow = darken(coat, k_coat_shadow_factor);
  116. return shadow * (1.0F - highlight) + bright * highlight;
  117. }
  118. inline auto lerp3(const QVector3D &a, const QVector3D &b,
  119. float t) -> QVector3D {
  120. return {a.x() + (b.x() - a.x()) * t, a.y() + (b.y() - a.y()) * t,
  121. a.z() + (b.z() - a.z()) * t};
  122. }
  123. inline auto scaled_sphere(const QMatrix4x4 &model, const QVector3D &center,
  124. const QVector3D &scale) -> QMatrix4x4 {
  125. QMatrix4x4 m = model;
  126. m.translate(center);
  127. m.scale(scale);
  128. return m;
  129. }
  130. inline void draw_cylinder(ISubmitter &out, const QMatrix4x4 &model,
  131. const QVector3D &a, const QVector3D &b, float radius,
  132. const QVector3D &color, float alpha = 1.0F,
  133. int material_id = 0) {
  134. out.mesh(get_unit_cylinder(), cylinder_between(model, a, b, radius), color,
  135. nullptr, alpha, material_id);
  136. }
  137. inline void draw_cone(ISubmitter &out, const QMatrix4x4 &model,
  138. const QVector3D &tip, const QVector3D &base, float radius,
  139. const QVector3D &color, float alpha = 1.0F,
  140. int material_id = 0) {
  141. out.mesh(get_unit_cone(), cone_from_to(model, tip, base, radius), color,
  142. nullptr, alpha, material_id);
  143. }
  144. inline void draw_rounded_segment(ISubmitter &out, const QMatrix4x4 &model,
  145. const QVector3D &start, const QVector3D &end,
  146. float start_radius, float end_radius,
  147. const QVector3D &start_color,
  148. const QVector3D &end_color, float alpha = 1.0F,
  149. int material_id = 0) {
  150. float const mid_radius = 0.5F * (start_radius + end_radius);
  151. QVector3D const tint = lerp(start_color, end_color, 0.5F);
  152. out.mesh(get_unit_cylinder(), cylinder_between(model, start, end, mid_radius),
  153. tint, nullptr, alpha, material_id);
  154. out.mesh(get_unit_sphere(),
  155. Render::Geom::sphere_at(model, start, start_radius), start_color,
  156. nullptr, alpha, material_id);
  157. out.mesh(get_unit_sphere(), Render::Geom::sphere_at(model, end, end_radius),
  158. end_color, nullptr, alpha, material_id);
  159. }
  160. inline auto bezier(const QVector3D &p0, const QVector3D &p1,
  161. const QVector3D &p2, float t) -> QVector3D {
  162. float const u = 1.0F - t;
  163. return p0 * (u * u) + p1 * (2.0F * u * t) + p2 * (t * t);
  164. }
  165. inline auto color_hash(const QVector3D &c) -> uint32_t {
  166. auto const r = uint32_t(saturate(c.x()) * k_rgb_max);
  167. auto const g = uint32_t(saturate(c.y()) * k_rgb_max);
  168. auto const b = uint32_t(saturate(c.z()) * k_rgb_max);
  169. uint32_t v = (r << k_rgb_shift_red) | (g << k_rgb_shift_green) | b;
  170. v ^= v >> k_hash_shift_16;
  171. v *= k_hash_mult_1;
  172. v ^= v >> k_hash_shift_15;
  173. v *= k_hash_mult_2;
  174. v ^= v >> k_hash_shift_16;
  175. return v;
  176. }
  177. } // namespace
  178. namespace HorseDimensionRange {
  179. constexpr float kBodyLengthMin = 0.92F;
  180. constexpr float kBodyLengthMax = 1.08F;
  181. constexpr float kBodyWidthMin = 0.20F;
  182. constexpr float kBodyWidthMax = 0.28F;
  183. constexpr float kBodyHeightMin = 0.42F;
  184. constexpr float kBodyHeightMax = 0.52F;
  185. constexpr float kNeckLengthMin = 0.48F;
  186. constexpr float kNeckLengthMax = 0.58F;
  187. constexpr float kNeckRiseMin = 0.30F;
  188. constexpr float kNeckRiseMax = 0.38F;
  189. constexpr float kHeadLengthMin = 0.34F;
  190. constexpr float kHeadLengthMax = 0.42F;
  191. constexpr float kHeadWidthMin = 0.16F;
  192. constexpr float kHeadWidthMax = 0.20F;
  193. constexpr float kHeadHeightMin = 0.22F;
  194. constexpr float kHeadHeightMax = 0.28F;
  195. constexpr float kMuzzleLengthMin = 0.16F;
  196. constexpr float kMuzzleLengthMax = 0.20F;
  197. constexpr float kLegLengthMin = 1.05F;
  198. constexpr float kLegLengthMax = 1.18F;
  199. constexpr float kHoofHeightMin = 0.095F;
  200. constexpr float kHoofHeightMax = 0.115F;
  201. constexpr float kTailLengthMin = 0.55F;
  202. constexpr float kTailLengthMax = 0.72F;
  203. constexpr float kSaddleThicknessMin = 0.035F;
  204. constexpr float kSaddleThicknessMax = 0.045F;
  205. constexpr float kSeatForwardOffsetMin = 0.010F;
  206. constexpr float kSeatForwardOffsetMax = 0.035F;
  207. constexpr float kStirrupOutScaleMin = 0.75F;
  208. constexpr float kStirrupOutScaleMax = 0.88F;
  209. constexpr float kStirrupDropMin = 0.28F;
  210. constexpr float kStirrupDropMax = 0.32F;
  211. constexpr float kIdleBobAmplitudeMin = 0.004F;
  212. constexpr float kIdleBobAmplitudeMax = 0.007F;
  213. constexpr float kMoveBobAmplitudeMin = 0.024F;
  214. constexpr float kMoveBobAmplitudeMax = 0.032F;
  215. constexpr float kLegSegmentRatioUpper = 0.59F;
  216. constexpr float kLegSegmentRatioMiddle = 0.30F;
  217. constexpr float kLegSegmentRatioLower = 0.12F;
  218. constexpr float kShoulderBarrelOffsetScale = 0.05F;
  219. constexpr float kShoulderBarrelOffsetBase = 0.05F;
  220. constexpr float kSaddleHeightBodyScale = 0.55F;
  221. constexpr uint32_t kSaltBodyLength = 0x12U;
  222. constexpr uint32_t kSaltBodyWidth = 0x34U;
  223. constexpr uint32_t kSaltBodyHeight = 0x56U;
  224. constexpr uint32_t kSaltNeckLength = 0x9AU;
  225. constexpr uint32_t kSaltNeckRise = 0xBCU;
  226. constexpr uint32_t kSaltHeadLength = 0xDEU;
  227. constexpr uint32_t kSaltHeadWidth = 0xF1U;
  228. constexpr uint32_t kSaltHeadHeight = 0x1357U;
  229. constexpr uint32_t kSaltMuzzleLength = 0x2468U;
  230. constexpr uint32_t kSaltLegLength = 0x369CU;
  231. constexpr uint32_t kSaltHoofHeight = 0x48AEU;
  232. constexpr uint32_t kSaltTailLength = 0x5ABCU;
  233. constexpr uint32_t kSaltSaddleThickness = 0x6CDEU;
  234. constexpr uint32_t kSaltSeatForwardOffset = 0x7531U;
  235. constexpr uint32_t kSaltStirrupOut = 0x8642U;
  236. constexpr uint32_t kSaltStirrupDrop = 0x9753U;
  237. constexpr uint32_t kSaltIdleBob = 0xA864U;
  238. constexpr uint32_t kSaltMoveBob = 0xB975U;
  239. } // namespace HorseDimensionRange
  240. auto make_horse_dimensions(uint32_t seed) -> HorseDimensions {
  241. using namespace HorseDimensionRange;
  242. HorseDimensions d{};
  243. d.body_length =
  244. rand_between(seed, kSaltBodyLength, kBodyLengthMin, kBodyLengthMax);
  245. d.body_width =
  246. rand_between(seed, kSaltBodyWidth, kBodyWidthMin, kBodyWidthMax);
  247. d.body_height =
  248. rand_between(seed, kSaltBodyHeight, kBodyHeightMin, kBodyHeightMax);
  249. d.neck_length =
  250. rand_between(seed, kSaltNeckLength, kNeckLengthMin, kNeckLengthMax);
  251. d.neck_rise = rand_between(seed, kSaltNeckRise, kNeckRiseMin, kNeckRiseMax);
  252. d.head_length =
  253. rand_between(seed, kSaltHeadLength, kHeadLengthMin, kHeadLengthMax);
  254. d.head_width =
  255. rand_between(seed, kSaltHeadWidth, kHeadWidthMin, kHeadWidthMax);
  256. d.head_height =
  257. rand_between(seed, kSaltHeadHeight, kHeadHeightMin, kHeadHeightMax);
  258. d.muzzle_length =
  259. rand_between(seed, kSaltMuzzleLength, kMuzzleLengthMin, kMuzzleLengthMax);
  260. d.leg_length =
  261. rand_between(seed, kSaltLegLength, kLegLengthMin, kLegLengthMax);
  262. d.hoof_height =
  263. rand_between(seed, kSaltHoofHeight, kHoofHeightMin, kHoofHeightMax);
  264. d.tail_length =
  265. rand_between(seed, kSaltTailLength, kTailLengthMin, kTailLengthMax);
  266. d.saddle_thickness = rand_between(seed, kSaltSaddleThickness,
  267. kSaddleThicknessMin, kSaddleThicknessMax);
  268. d.seat_forward_offset =
  269. rand_between(seed, kSaltSeatForwardOffset, kSeatForwardOffsetMin,
  270. kSeatForwardOffsetMax);
  271. d.stirrup_out =
  272. d.body_width * rand_between(seed, kSaltStirrupOut, kStirrupOutScaleMin,
  273. kStirrupOutScaleMax);
  274. d.stirrup_drop =
  275. rand_between(seed, kSaltStirrupDrop, kStirrupDropMin, kStirrupDropMax);
  276. d.idle_bob_amplitude = rand_between(seed, kSaltIdleBob, kIdleBobAmplitudeMin,
  277. kIdleBobAmplitudeMax);
  278. d.move_bob_amplitude = rand_between(seed, kSaltMoveBob, kMoveBobAmplitudeMin,
  279. kMoveBobAmplitudeMax);
  280. float const avg_leg_segment_ratio =
  281. kLegSegmentRatioUpper + kLegSegmentRatioMiddle + kLegSegmentRatioLower;
  282. float const leg_down_distance =
  283. d.leg_length * avg_leg_segment_ratio + d.hoof_height;
  284. float const shoulder_to_barrel_offset =
  285. d.body_height * kShoulderBarrelOffsetScale + kShoulderBarrelOffsetBase;
  286. d.barrel_center_y = leg_down_distance - shoulder_to_barrel_offset;
  287. d.saddle_height = d.barrel_center_y + d.body_height * kSaddleHeightBodyScale +
  288. d.saddle_thickness;
  289. return d;
  290. }
  291. namespace HorseVariantConstants {
  292. constexpr float kGrayCoatThreshold = 0.18F;
  293. constexpr float kBayCoatThreshold = 0.38F;
  294. constexpr float kChestnutCoatThreshold = 0.65F;
  295. constexpr float kBlackCoatThreshold = 0.85F;
  296. constexpr float kGrayCoatR = 0.70F;
  297. constexpr float kGrayCoatG = 0.68F;
  298. constexpr float kGrayCoatB = 0.63F;
  299. constexpr float kBayCoatR = 0.40F;
  300. constexpr float kBayCoatG = 0.30F;
  301. constexpr float kBayCoatB = 0.22F;
  302. constexpr float kChestnutCoatR = 0.28F;
  303. constexpr float kChestnutCoatG = 0.22F;
  304. constexpr float kChestnutCoatB = 0.19F;
  305. constexpr float kBlackCoatR = 0.18F;
  306. constexpr float kBlackCoatG = 0.15F;
  307. constexpr float kBlackCoatB = 0.13F;
  308. constexpr float kDunCoatR = 0.48F;
  309. constexpr float kDunCoatG = 0.42F;
  310. constexpr float kDunCoatB = 0.39F;
  311. constexpr float kBlazeChanceThreshold = 0.82F;
  312. constexpr float kBlazeColorR = 0.92F;
  313. constexpr float kBlazeColorG = 0.92F;
  314. constexpr float kBlazeColorB = 0.90F;
  315. constexpr float kBlazeBlendFactor = 0.25F;
  316. constexpr float kManeBlendMin = 0.55F;
  317. constexpr float kManeBlendMax = 0.85F;
  318. constexpr float kManeBaseR = 0.10F;
  319. constexpr float kManeBaseG = 0.09F;
  320. constexpr float kManeBaseB = 0.08F;
  321. constexpr float kTailBlendFactor = 0.35F;
  322. constexpr float kMuzzleBlendFactor = 0.65F;
  323. constexpr float kMuzzleBaseR = 0.18F;
  324. constexpr float kMuzzleBaseG = 0.14F;
  325. constexpr float kMuzzleBaseB = 0.12F;
  326. constexpr float kHoofDarkR = 0.16F;
  327. constexpr float kHoofDarkG = 0.14F;
  328. constexpr float kHoofDarkB = 0.12F;
  329. constexpr float kHoofLightR = 0.40F;
  330. constexpr float kHoofLightG = 0.35F;
  331. constexpr float kHoofLightB = 0.32F;
  332. constexpr float kHoofBlendMin = 0.15F;
  333. constexpr float kHoofBlendMax = 0.65F;
  334. constexpr float kLeatherToneMin = 0.78F;
  335. constexpr float kLeatherToneMax = 0.96F;
  336. constexpr float kTackToneMin = 0.58F;
  337. constexpr float kTackToneMax = 0.78F;
  338. constexpr float kSpecialTackThreshold = 0.90F;
  339. constexpr float kSpecialTackR = 0.18F;
  340. constexpr float kSpecialTackG = 0.19F;
  341. constexpr float kSpecialTackB = 0.22F;
  342. constexpr float kSpecialTackBlend = 0.25F;
  343. constexpr float kBlanketTintMin = 0.92F;
  344. constexpr float kBlanketTintMax = 1.05F;
  345. constexpr float kEarInnerBaseR = 0.45F;
  346. constexpr float kEarInnerBaseG = 0.35F;
  347. constexpr float kEarInnerBaseB = 0.32F;
  348. constexpr float kEarInnerBlendFactor = 0.30F;
  349. constexpr uint32_t kSaltCoatHue = 0x23456U;
  350. constexpr uint32_t kSaltBlazeChance = 0x1122U;
  351. constexpr uint32_t kSaltManeBlend = 0x3344U;
  352. constexpr uint32_t kSaltHoofBlend = 0x5566U;
  353. constexpr uint32_t kSaltLeatherTone = 0x7788U;
  354. constexpr uint32_t kSaltTackTone = 0x88AAU;
  355. constexpr uint32_t kSaltBlanketTint = 0x99B0U;
  356. } // namespace HorseVariantConstants
  357. namespace HorseGaitConstants {
  358. constexpr float kCycleTimeMin = 0.60F;
  359. constexpr float kCycleTimeMax = 0.72F;
  360. constexpr float kFrontLegPhaseMin = 0.08F;
  361. constexpr float kFrontLegPhaseMax = 0.16F;
  362. constexpr float kDiagonalLeadMin = 0.44F;
  363. constexpr float kDiagonalLeadMax = 0.54F;
  364. constexpr float kStrideSwingMin = 0.26F;
  365. constexpr float kStrideSwingMax = 0.32F;
  366. constexpr float kStrideLiftMin = 0.10F;
  367. constexpr float kStrideLiftMax = 0.14F;
  368. constexpr uint32_t kSaltCycleTime = 0xAA12U;
  369. constexpr uint32_t kSaltFrontLegPhase = 0xBB34U;
  370. constexpr uint32_t kSaltDiagonalLead = 0xCC56U;
  371. constexpr uint32_t kSaltStrideSwing = 0xDD78U;
  372. constexpr uint32_t kSaltStrideLift = 0xEE9AU;
  373. } // namespace HorseGaitConstants
  374. auto make_horse_variant(uint32_t seed, const QVector3D &leather_base,
  375. const QVector3D &cloth_base) -> HorseVariant {
  376. using namespace HorseVariantConstants;
  377. HorseVariant v;
  378. float const coat_hue = hash01(seed ^ kSaltCoatHue);
  379. if (coat_hue < kGrayCoatThreshold) {
  380. v.coat_color = QVector3D(kGrayCoatR, kGrayCoatG, kGrayCoatB);
  381. } else if (coat_hue < kBayCoatThreshold) {
  382. v.coat_color = QVector3D(kBayCoatR, kBayCoatG, kBayCoatB);
  383. } else if (coat_hue < kChestnutCoatThreshold) {
  384. v.coat_color = QVector3D(kChestnutCoatR, kChestnutCoatG, kChestnutCoatB);
  385. } else if (coat_hue < kBlackCoatThreshold) {
  386. v.coat_color = QVector3D(kBlackCoatR, kBlackCoatG, kBlackCoatB);
  387. } else {
  388. v.coat_color = QVector3D(kDunCoatR, kDunCoatG, kDunCoatB);
  389. }
  390. float const blaze_chance = hash01(seed ^ kSaltBlazeChance);
  391. if (blaze_chance > kBlazeChanceThreshold) {
  392. v.coat_color =
  393. lerp(v.coat_color, QVector3D(kBlazeColorR, kBlazeColorG, kBlazeColorB),
  394. kBlazeBlendFactor);
  395. }
  396. v.mane_color =
  397. lerp(v.coat_color, QVector3D(kManeBaseR, kManeBaseG, kManeBaseB),
  398. rand_between(seed, kSaltManeBlend, kManeBlendMin, kManeBlendMax));
  399. v.tail_color = lerp(v.mane_color, v.coat_color, kTailBlendFactor);
  400. v.muzzle_color =
  401. lerp(v.coat_color, QVector3D(kMuzzleBaseR, kMuzzleBaseG, kMuzzleBaseB),
  402. kMuzzleBlendFactor);
  403. v.hoof_color =
  404. lerp(QVector3D(kHoofDarkR, kHoofDarkG, kHoofDarkB),
  405. QVector3D(kHoofLightR, kHoofLightG, kHoofLightB),
  406. rand_between(seed, kSaltHoofBlend, kHoofBlendMin, kHoofBlendMax));
  407. float const leather_tone =
  408. rand_between(seed, kSaltLeatherTone, kLeatherToneMin, kLeatherToneMax);
  409. float const tack_tone =
  410. rand_between(seed, kSaltTackTone, kTackToneMin, kTackToneMax);
  411. QVector3D const leather_tint = leather_base * leather_tone;
  412. QVector3D tack_tint = leather_base * tack_tone;
  413. if (blaze_chance > kSpecialTackThreshold) {
  414. tack_tint =
  415. lerp(tack_tint, QVector3D(kSpecialTackR, kSpecialTackG, kSpecialTackB),
  416. kSpecialTackBlend);
  417. }
  418. v.saddle_color = leather_tint;
  419. v.tack_color = tack_tint;
  420. v.blanket_color = cloth_base * rand_between(seed, kSaltBlanketTint,
  421. kBlanketTintMin, kBlanketTintMax);
  422. return v;
  423. }
  424. auto make_horse_profile(uint32_t seed, const QVector3D &leather_base,
  425. const QVector3D &cloth_base) -> HorseProfile {
  426. using namespace HorseGaitConstants;
  427. HorseProfile profile;
  428. profile.dims = make_horse_dimensions(seed);
  429. profile.variant = make_horse_variant(seed, leather_base, cloth_base);
  430. profile.gait.cycle_time =
  431. rand_between(seed, kSaltCycleTime, kCycleTimeMin, kCycleTimeMax);
  432. profile.gait.front_leg_phase = rand_between(
  433. seed, kSaltFrontLegPhase, kFrontLegPhaseMin, kFrontLegPhaseMax);
  434. float const diagonal_lead =
  435. rand_between(seed, kSaltDiagonalLead, kDiagonalLeadMin, kDiagonalLeadMax);
  436. profile.gait.rear_leg_phase =
  437. std::fmod(profile.gait.front_leg_phase + diagonal_lead, 1.0F);
  438. profile.gait.stride_swing =
  439. rand_between(seed, kSaltStrideSwing, kStrideSwingMin, kStrideSwingMax);
  440. profile.gait.stride_lift =
  441. rand_between(seed, kSaltStrideLift, kStrideLiftMin, kStrideLiftMax);
  442. return profile;
  443. }
  444. auto get_or_create_cached_horse_profile(
  445. uint32_t seed, const QVector3D &leather_base,
  446. const QVector3D &cloth_base) -> HorseProfile {
  447. std::lock_guard<std::mutex> lock(s_horse_profile_cache_mutex);
  448. HorseProfileCacheKey cache_key =
  449. make_horse_profile_cache_key(seed, leather_base, cloth_base);
  450. auto cache_it = s_horse_profile_cache.find(cache_key);
  451. if (cache_it != s_horse_profile_cache.end()) {
  452. CachedHorseProfileEntry &entry = cache_it->second;
  453. if ((entry.leather_base - leather_base).lengthSquared() <
  454. k_color_comparison_tolerance &&
  455. (entry.cloth_base - cloth_base).lengthSquared() <
  456. k_color_comparison_tolerance) {
  457. entry.frame_number = s_horse_cache_frame;
  458. ++s_horseRenderStats.profiles_cached;
  459. return entry.profile;
  460. }
  461. }
  462. ++s_horseRenderStats.profiles_computed;
  463. HorseProfile profile = make_horse_profile(seed, leather_base, cloth_base);
  464. CachedHorseProfileEntry &new_entry = s_horse_profile_cache[cache_key];
  465. new_entry.profile = profile;
  466. new_entry.leather_base = leather_base;
  467. new_entry.cloth_base = cloth_base;
  468. new_entry.frame_number = s_horse_cache_frame;
  469. return profile;
  470. }
  471. void advance_horse_profile_cache_frame() {
  472. std::lock_guard<std::mutex> lock(s_horse_profile_cache_mutex);
  473. ++s_horse_cache_frame;
  474. if ((s_horse_cache_frame & k_cache_cleanup_interval_mask) == 0) {
  475. auto it = s_horse_profile_cache.begin();
  476. while (it != s_horse_profile_cache.end()) {
  477. if (s_horse_cache_frame - it->second.frame_number >
  478. k_horse_profile_cache_max_age) {
  479. it = s_horse_profile_cache.erase(it);
  480. } else {
  481. ++it;
  482. }
  483. }
  484. }
  485. }
  486. auto MountedAttachmentFrame::stirrup_attach(bool is_left) const
  487. -> const QVector3D & {
  488. return is_left ? stirrup_attach_left : stirrup_attach_right;
  489. }
  490. auto MountedAttachmentFrame::stirrup_bottom(bool is_left) const
  491. -> const QVector3D & {
  492. return is_left ? stirrup_bottom_left : stirrup_bottom_right;
  493. }
  494. namespace MountFrameConstants {
  495. constexpr float kSaddleThicknessOffset = 0.35F;
  496. constexpr float kSaddleBodyLengthOffset = 0.05F;
  497. constexpr float kSaddleSeatForwardScale = 0.25F;
  498. constexpr float kSeatPositionHeightScale = 0.32F;
  499. constexpr float kStirrupWidthScale = 0.92F;
  500. constexpr float kStirrupThicknessOffset = 0.10F;
  501. constexpr float kStirrupForwardScale = 0.28F;
  502. constexpr float kNeckTopBodyHeightScale = 0.65F;
  503. constexpr float kNeckTopBodyLengthScale = 0.25F;
  504. constexpr float kHeadCenterHeightScale = 0.10F;
  505. constexpr float kHeadCenterLengthScale = 0.40F;
  506. constexpr float kMuzzleHeightOffset = 0.18F;
  507. constexpr float kMuzzleLengthOffset = 0.58F;
  508. constexpr float kBridleHeightOffset = 0.05F;
  509. constexpr float kBridleLengthOffset = 0.20F;
  510. constexpr float kBitWidthOffset = 0.55F;
  511. constexpr float kBitHeightOffset = 0.08F;
  512. constexpr float kBitLengthOffset = 0.10F;
  513. } // namespace MountFrameConstants
  514. auto compute_mount_frame(const HorseProfile &profile)
  515. -> MountedAttachmentFrame {
  516. using namespace MountFrameConstants;
  517. const HorseDimensions &d = profile.dims;
  518. MountedAttachmentFrame frame{};
  519. frame.seat_forward = QVector3D(0.0F, 0.0F, 1.0F);
  520. frame.seat_right = QVector3D(1.0F, 0.0F, 0.0F);
  521. frame.seat_up = QVector3D(0.0F, 1.0F, 0.0F);
  522. frame.ground_offset = QVector3D(0.0F, -d.barrel_center_y, 0.0F);
  523. frame.saddle_center = QVector3D(
  524. 0.0F, d.saddle_height - d.saddle_thickness * kSaddleThicknessOffset,
  525. -d.body_length * kSaddleBodyLengthOffset +
  526. d.seat_forward_offset * kSaddleSeatForwardScale);
  527. frame.seat_position =
  528. frame.saddle_center +
  529. QVector3D(0.0F, d.saddle_thickness * kSeatPositionHeightScale, 0.0F);
  530. frame.stirrup_attach_left =
  531. frame.saddle_center +
  532. QVector3D(-d.body_width * kStirrupWidthScale,
  533. -d.saddle_thickness * kStirrupThicknessOffset,
  534. d.seat_forward_offset * kStirrupForwardScale);
  535. frame.stirrup_attach_right =
  536. frame.saddle_center +
  537. QVector3D(d.body_width * kStirrupWidthScale,
  538. -d.saddle_thickness * kStirrupThicknessOffset,
  539. d.seat_forward_offset * kStirrupForwardScale);
  540. frame.stirrup_bottom_left =
  541. frame.stirrup_attach_left + QVector3D(0.0F, -d.stirrup_drop, 0.0F);
  542. frame.stirrup_bottom_right =
  543. frame.stirrup_attach_right + QVector3D(0.0F, -d.stirrup_drop, 0.0F);
  544. QVector3D const neck_top(
  545. 0.0F,
  546. d.barrel_center_y + d.body_height * kNeckTopBodyHeightScale + d.neck_rise,
  547. d.body_length * kNeckTopBodyLengthScale);
  548. QVector3D const head_center =
  549. neck_top + QVector3D(0.0F, d.head_height * kHeadCenterHeightScale,
  550. d.head_length * kHeadCenterLengthScale);
  551. QVector3D const muzzle_center =
  552. head_center + QVector3D(0.0F, -d.head_height * kMuzzleHeightOffset,
  553. d.head_length * kMuzzleLengthOffset);
  554. frame.bridle_base =
  555. muzzle_center + QVector3D(0.0F, -d.head_height * kBridleHeightOffset,
  556. d.muzzle_length * kBridleLengthOffset);
  557. frame.rein_bit_left =
  558. muzzle_center + QVector3D(d.head_width * kBitWidthOffset,
  559. -d.head_height * kBitHeightOffset,
  560. d.muzzle_length * kBitLengthOffset);
  561. frame.rein_bit_right =
  562. muzzle_center + QVector3D(-d.head_width * kBitWidthOffset,
  563. -d.head_height * kBitHeightOffset,
  564. d.muzzle_length * kBitLengthOffset);
  565. return frame;
  566. }
  567. namespace ReinConstants {
  568. constexpr uint32_t kSlackSeedSalt = 0x707U;
  569. constexpr float kBaseSlackScale = 0.08F;
  570. constexpr float kBaseSlackOffset = 0.02F;
  571. constexpr float kTargetTensionBonus = 0.25F;
  572. constexpr float kAttackTensionBonus = 0.35F;
  573. constexpr float kMinSlack = 0.01F;
  574. constexpr float kHandleRightOffset = 0.08F;
  575. constexpr float kHandleForwardBase = 0.18F;
  576. constexpr float kHandleForwardTensionScale = 0.18F;
  577. constexpr float kHandleUpBase = -0.10F;
  578. constexpr float kHandleUpSlackScale = -0.30F;
  579. constexpr float kHandleUpTensionScale = 0.04F;
  580. constexpr float kDirLengthThreshold = 1e-4F;
  581. constexpr float kReinBaseLength = 0.85F;
  582. constexpr float kSlackLengthScale = 0.12F;
  583. } // namespace ReinConstants
  584. auto compute_rein_state(uint32_t horse_seed,
  585. const HumanoidAnimationContext &rider_ctx)
  586. -> ReinState {
  587. using namespace ReinConstants;
  588. float const base_slack =
  589. hash01(horse_seed ^ kSlackSeedSalt) * kBaseSlackScale + kBaseSlackOffset;
  590. float rein_tension = rider_ctx.locomotion_normalized_speed();
  591. if (rider_ctx.gait.has_target) {
  592. rein_tension += kTargetTensionBonus;
  593. }
  594. if (rider_ctx.is_attacking()) {
  595. rein_tension += kAttackTensionBonus;
  596. }
  597. rein_tension = std::clamp(rein_tension, 0.0F, 1.0F);
  598. float const rein_slack =
  599. std::max(kMinSlack, base_slack * (1.0F - rein_tension));
  600. return ReinState{rein_slack, rein_tension};
  601. }
  602. auto compute_rein_handle(const MountedAttachmentFrame &mount, bool is_left,
  603. float slack, float tension) -> QVector3D {
  604. using namespace ReinConstants;
  605. float const clamped_slack = std::clamp(slack, 0.0F, 1.0F);
  606. float const clamped_tension = std::clamp(tension, 0.0F, 1.0F);
  607. QVector3D const &bit = is_left ? mount.rein_bit_left : mount.rein_bit_right;
  608. QVector3D desired = mount.seat_position;
  609. desired +=
  610. (is_left ? -mount.seat_right : mount.seat_right) * kHandleRightOffset;
  611. desired +=
  612. -mount.seat_forward *
  613. (kHandleForwardBase + clamped_tension * kHandleForwardTensionScale);
  614. desired +=
  615. mount.seat_up * (kHandleUpBase + clamped_slack * kHandleUpSlackScale +
  616. clamped_tension * kHandleUpTensionScale);
  617. QVector3D dir = desired - bit;
  618. if (dir.lengthSquared() < kDirLengthThreshold) {
  619. dir = -mount.seat_forward;
  620. }
  621. dir.normalize();
  622. float const rein_length = kReinBaseLength + clamped_slack * kSlackLengthScale;
  623. return bit + dir * rein_length;
  624. }
  625. auto evaluate_horse_motion(HorseProfile &profile, const AnimationInputs &anim,
  626. const HumanoidAnimationContext &rider_ctx)
  627. -> HorseMotionSample {
  628. HorseMotionSample sample{};
  629. HorseAnimationController controller(profile, anim, rider_ctx);
  630. sample.rider_intensity = rider_ctx.locomotion_normalized_speed();
  631. bool const rider_has_motion =
  632. rider_ctx.is_walking() || rider_ctx.is_running();
  633. sample.is_moving = rider_has_motion || anim.is_moving;
  634. constexpr float kIdleSpeedMax = 0.5F;
  635. constexpr float kWalkSpeedMax = 3.0F;
  636. constexpr float kTrotSpeedMax = 5.5F;
  637. constexpr float kCanterSpeedMax = 8.0F;
  638. if (sample.is_moving) {
  639. float const speed = rider_ctx.locomotion_speed();
  640. if (speed < kIdleSpeedMax && !anim.is_moving) {
  641. controller.idle(1.0F);
  642. } else if (speed < kWalkSpeedMax) {
  643. controller.set_gait(GaitType::WALK);
  644. } else if (speed < kTrotSpeedMax) {
  645. controller.set_gait(GaitType::TROT);
  646. } else if (speed < kCanterSpeedMax) {
  647. controller.set_gait(GaitType::CANTER);
  648. } else {
  649. controller.set_gait(GaitType::GALLOP);
  650. }
  651. } else {
  652. controller.idle(1.0F);
  653. }
  654. controller.update_gait_parameters();
  655. sample.phase = controller.get_current_phase();
  656. sample.bob = controller.get_current_bob();
  657. return sample;
  658. }
  659. void apply_mount_vertical_offset(MountedAttachmentFrame &frame, float bob) {
  660. QVector3D const offset(0.0F, bob, 0.0F);
  661. frame.saddle_center += offset;
  662. frame.seat_position += offset;
  663. frame.stirrup_attach_left += offset;
  664. frame.stirrup_attach_right += offset;
  665. frame.stirrup_bottom_left += offset;
  666. frame.stirrup_bottom_right += offset;
  667. frame.rein_bit_left += offset;
  668. frame.rein_bit_right += offset;
  669. frame.bridle_base += offset;
  670. }
  671. void HorseRendererBase::render_full(
  672. const DrawContext &ctx, const AnimationInputs &anim,
  673. const HumanoidAnimationContext &rider_ctx, HorseProfile &profile,
  674. const MountedAttachmentFrame *shared_mount, const ReinState *shared_reins,
  675. const HorseMotionSample *shared_motion, ISubmitter &out) const {
  676. const HorseDimensions &d = profile.dims;
  677. const HorseVariant &v = profile.variant;
  678. HorseMotionSample const motion =
  679. shared_motion ? *shared_motion
  680. : evaluate_horse_motion(profile, anim, rider_ctx);
  681. const HorseGait &g = profile.gait;
  682. float const phase = motion.phase;
  683. float const bob = motion.bob;
  684. const bool is_moving = motion.is_moving;
  685. const float rider_intensity = motion.rider_intensity;
  686. MountedAttachmentFrame mount =
  687. shared_mount ? *shared_mount : compute_mount_frame(profile);
  688. if (!shared_mount) {
  689. apply_mount_vertical_offset(mount, bob);
  690. }
  691. uint32_t horse_seed = 0U;
  692. if (ctx.entity != nullptr) {
  693. horse_seed = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(ctx.entity) &
  694. 0xFFFFFFFFU);
  695. }
  696. DrawContext horse_ctx = ctx;
  697. horse_ctx.model = ctx.model;
  698. horse_ctx.model.translate(mount.ground_offset);
  699. float const sway_intensity =
  700. is_moving ? (1.0F - rider_intensity * 0.5F) : 0.3F;
  701. float const body_sway =
  702. is_moving ? std::sin(phase * 2.0F * k_pi) * 0.012F * sway_intensity
  703. : std::sin(anim.time * 0.4F) * 0.005F;
  704. float const pitch_intensity = rider_intensity * 0.7F + 0.1F;
  705. float const body_pitch = is_moving ? std::sin((phase + 0.25F) * 2.0F * k_pi) *
  706. 0.008F * pitch_intensity
  707. : std::sin(anim.time * 0.25F) * 0.003F;
  708. float const nod_base = is_moving ? std::sin((phase + 0.25F) * 2.0F * k_pi) *
  709. (0.025F + rider_intensity * 0.02F)
  710. : std::sin(anim.time * 1.5F) * 0.008F;
  711. float const nod_secondary = std::sin(anim.time * 0.8F) * 0.004F;
  712. float const head_nod = nod_base + nod_secondary;
  713. float const head_lateral = body_sway * 0.6F;
  714. float const spine_flex =
  715. is_moving ? std::sin(phase * 2.0F * k_pi) * 0.006F * rider_intensity
  716. : 0.0F;
  717. uint32_t const vhash = color_hash(v.coat_color);
  718. float const sock_chance_fl = hash01(vhash ^ 0x101U);
  719. float const sock_chance_fr = hash01(vhash ^ 0x202U);
  720. float const sock_chance_rl = hash01(vhash ^ 0x303U);
  721. float const sock_chance_rr = hash01(vhash ^ 0x404U);
  722. bool const has_blaze = hash01(vhash ^ 0x505U) > 0.82F;
  723. ReinState const rein_state =
  724. shared_reins ? *shared_reins : compute_rein_state(horse_seed, rider_ctx);
  725. float const rein_slack = rein_state.slack;
  726. float const rein_tension = rein_state.tension;
  727. const float coat_seed_a = hash01(vhash ^ 0x701U);
  728. const float coat_seed_b = hash01(vhash ^ 0x702U);
  729. const float coat_seed_c = hash01(vhash ^ 0x703U);
  730. const float coat_seed_d = hash01(vhash ^ 0x704U);
  731. QVector3D const barrel_center(body_sway, d.barrel_center_y + bob + body_pitch,
  732. spine_flex);
  733. float const ground_offset = -d.barrel_center_y - bob;
  734. QVector3D const chest_center =
  735. barrel_center +
  736. QVector3D(0.0F, d.body_height * 0.12F, d.body_length * 0.34F);
  737. QVector3D const rump_center =
  738. barrel_center +
  739. QVector3D(0.0F, d.body_height * 0.08F, -d.body_length * 0.36F);
  740. QVector3D const belly_center =
  741. barrel_center +
  742. QVector3D(0.0F, -d.body_height * 0.35F, -d.body_length * 0.05F);
  743. {
  744. QMatrix4x4 chest = horse_ctx.model;
  745. chest.translate(chest_center);
  746. chest.scale(d.body_width * 1.12F, d.body_height * 0.95F,
  747. d.body_length * 0.36F);
  748. QVector3D const chest_color =
  749. coat_gradient(v.coat_color, 0.75F, 0.20F, coat_seed_a);
  750. out.mesh(get_unit_sphere(), chest, chest_color, nullptr, 1.0F, 6);
  751. }
  752. {
  753. QMatrix4x4 withers = horse_ctx.model;
  754. withers.translate(chest_center + QVector3D(0.0F, d.body_height * 0.55F,
  755. -d.body_length * 0.03F));
  756. withers.scale(d.body_width * 0.75F, d.body_height * 0.35F,
  757. d.body_length * 0.18F);
  758. QVector3D const wither_color =
  759. coat_gradient(v.coat_color, 0.88F, 0.35F, coat_seed_b);
  760. out.mesh(get_unit_sphere(), withers, wither_color, nullptr, 1.0F, 6);
  761. }
  762. {
  763. QMatrix4x4 belly = horse_ctx.model;
  764. belly.translate(belly_center);
  765. belly.scale(d.body_width * 0.98F, d.body_height * 0.64F,
  766. d.body_length * 0.40F);
  767. QVector3D const belly_color =
  768. coat_gradient(v.coat_color, 0.25F, -0.10F, coat_seed_c);
  769. out.mesh(get_unit_sphere(), belly, belly_color, nullptr, 1.0F, 6);
  770. }
  771. {
  772. QMatrix4x4 rump = horse_ctx.model;
  773. rump.translate(rump_center);
  774. rump.scale(d.body_width * 1.22F, d.body_height * 1.05F,
  775. d.body_length * 0.38F);
  776. QVector3D const rump_color =
  777. coat_gradient(v.coat_color, 0.62F, -0.28F, coat_seed_a * 0.7F);
  778. out.mesh(get_unit_sphere(), rump, rump_color, nullptr, 1.0F, 6);
  779. }
  780. QVector3D withers_peak = chest_center + QVector3D(0.0F, d.body_height * 0.65F,
  781. -d.body_length * 0.04F);
  782. QVector3D croup_peak = rump_center + QVector3D(0.0F, d.body_height * 0.50F,
  783. -d.body_length * 0.16F);
  784. {
  785. QMatrix4x4 spine = horse_ctx.model;
  786. spine.translate(lerp(withers_peak, croup_peak, 0.42F));
  787. spine.scale(QVector3D(d.body_width * 0.55F, d.body_height * 0.16F,
  788. d.body_length * 0.58F));
  789. QVector3D const spine_color =
  790. coat_gradient(v.coat_color, 0.74F, -0.06F, coat_seed_d * 0.92F);
  791. out.mesh(get_unit_sphere(), spine, spine_color, nullptr, 1.0F, 6);
  792. }
  793. {
  794. QMatrix4x4 sternum = horse_ctx.model;
  795. sternum.translate(barrel_center + QVector3D(0.0F, -d.body_height * 0.42F,
  796. d.body_length * 0.30F));
  797. sternum.scale(QVector3D(d.body_width * 0.55F, d.body_height * 0.18F,
  798. d.body_length * 0.14F));
  799. out.mesh(get_unit_sphere(), sternum,
  800. coat_gradient(v.coat_color, 0.18F, 0.18F, coat_seed_a * 0.4F),
  801. nullptr, 1.0F, 6);
  802. }
  803. QVector3D const neck_base =
  804. chest_center + QVector3D(head_lateral * 0.3F, d.body_height * 0.42F,
  805. d.body_length * 0.08F);
  806. QVector3D const neck_top =
  807. neck_base + QVector3D(head_lateral * 0.8F, d.neck_rise + head_nod * 0.4F,
  808. d.neck_length);
  809. float const neck_radius = d.body_width * 0.48F;
  810. QVector3D const neck_mid =
  811. lerp(neck_base, neck_top, 0.55F) + QVector3D(head_lateral * 0.5F,
  812. d.body_height * 0.03F,
  813. d.body_length * 0.02F);
  814. QVector3D const neck_color_base =
  815. coat_gradient(v.coat_color, 0.78F, 0.12F, coat_seed_c * 0.6F);
  816. out.mesh(get_unit_cylinder(),
  817. cylinder_between(horse_ctx.model, neck_base, neck_mid,
  818. neck_radius * 1.00F),
  819. neck_color_base, nullptr, 1.0F);
  820. out.mesh(get_unit_cylinder(),
  821. cylinder_between(horse_ctx.model, neck_mid, neck_top,
  822. neck_radius * 0.86F),
  823. lighten(neck_color_base, 1.03F), nullptr, 1.0F);
  824. {
  825. QVector3D const jugular_start =
  826. lerp(neck_base, neck_top, 0.42F) + QVector3D(d.body_width * 0.18F,
  827. -d.body_height * 0.06F,
  828. d.body_length * 0.04F);
  829. QVector3D const jugular_end =
  830. jugular_start +
  831. QVector3D(0.0F, -d.body_height * 0.24F, d.body_length * 0.06F);
  832. draw_cylinder(out, horse_ctx.model, jugular_start, jugular_end,
  833. neck_radius * 0.18F, lighten(neck_color_base, 1.08F), 0.85F,
  834. 6);
  835. }
  836. const int mane_sections = 8;
  837. QVector3D const mane_color =
  838. lerp3(v.mane_color, QVector3D(0.12F, 0.09F, 0.08F), 0.35F);
  839. for (int i = 0; i < mane_sections; ++i) {
  840. float const t =
  841. static_cast<float>(i) / static_cast<float>(mane_sections - 1);
  842. QVector3D const spine = lerp(neck_base, neck_top, t) +
  843. QVector3D(0.0F, d.body_height * 0.12F, 0.0F);
  844. float const length = lerp(0.14F, 0.08F, t) * d.body_height * 1.4F;
  845. QVector3D const tip =
  846. spine + QVector3D(0.0F, length * 1.2F, 0.02F * length);
  847. draw_cone(out, horse_ctx.model, tip, spine,
  848. d.body_width * lerp(0.25F, 0.12F, t), mane_color, 1.0F, 7);
  849. }
  850. QVector3D const head_center =
  851. neck_top + QVector3D(head_lateral,
  852. d.head_height * (0.10F - head_nod * 0.20F),
  853. d.head_length * 0.40F + head_nod * 0.03F);
  854. {
  855. QMatrix4x4 skull = horse_ctx.model;
  856. skull.translate(head_center + QVector3D(0.0F, d.head_height * 0.10F,
  857. -d.head_length * 0.10F));
  858. skull.scale(d.head_width * 0.95F, d.head_height * 0.90F,
  859. d.head_length * 0.80F);
  860. QVector3D const skull_color =
  861. coat_gradient(v.coat_color, 0.82F, 0.30F, coat_seed_d * 0.8F);
  862. out.mesh(get_unit_sphere(), skull, skull_color, nullptr, 1.0F);
  863. }
  864. for (int i = 0; i < 2; ++i) {
  865. float const side = (i == 0) ? 1.0F : -1.0F;
  866. QMatrix4x4 cheek = horse_ctx.model;
  867. cheek.translate(head_center + QVector3D(side * d.head_width * 0.55F,
  868. -d.head_height * 0.15F, 0.0F));
  869. cheek.scale(d.head_width * 0.45F, d.head_height * 0.50F,
  870. d.head_length * 0.60F);
  871. QVector3D const cheek_color =
  872. coat_gradient(v.coat_color, 0.70F, 0.18F, coat_seed_a * 0.9F);
  873. out.mesh(get_unit_sphere(), cheek, cheek_color, nullptr, 1.0F, 6);
  874. }
  875. QVector3D const muzzle_center =
  876. head_center +
  877. QVector3D(0.0F, -d.head_height * 0.18F, d.head_length * 0.58F);
  878. {
  879. QMatrix4x4 muzzle = horse_ctx.model;
  880. muzzle.translate(muzzle_center +
  881. QVector3D(0.0F, -d.head_height * 0.05F, 0.0F));
  882. muzzle.scale(d.head_width * 0.68F, d.head_height * 0.60F,
  883. d.muzzle_length * 1.05F);
  884. out.mesh(get_unit_sphere(), muzzle, v.muzzle_color, nullptr, 1.0F);
  885. }
  886. {
  887. QVector3D const nostril_base =
  888. muzzle_center +
  889. QVector3D(0.0F, -d.head_height * 0.02F, d.muzzle_length * 0.60F);
  890. QVector3D const left_base =
  891. nostril_base + QVector3D(d.head_width * 0.26F, 0.0F, 0.0F);
  892. QVector3D const right_base =
  893. nostril_base + QVector3D(-d.head_width * 0.26F, 0.0F, 0.0F);
  894. QVector3D const inward =
  895. QVector3D(0.0F, -d.head_height * 0.02F, d.muzzle_length * -0.30F);
  896. out.mesh(get_unit_cone(),
  897. cone_from_to(horse_ctx.model, left_base + inward, left_base,
  898. d.head_width * 0.11F),
  899. darken(v.muzzle_color, 0.6F), nullptr, 1.0F);
  900. out.mesh(get_unit_cone(),
  901. cone_from_to(horse_ctx.model, right_base + inward, right_base,
  902. d.head_width * 0.11F),
  903. darken(v.muzzle_color, 0.6F), nullptr, 1.0F);
  904. }
  905. float const ear_flick_l = std::sin(anim.time * 1.7F + 1.3F) * 0.25F;
  906. float const ear_flick_r = std::sin(anim.time * 1.9F + 2.1F) * -0.22F;
  907. float const ear_forward_l = std::sin(anim.time * 0.8F) * 0.15F;
  908. float const ear_forward_r = std::sin(anim.time * 0.9F + 0.5F) * 0.12F;
  909. QVector3D const ear_base_left =
  910. head_center + QVector3D(d.head_width * 0.42F, d.head_height * 0.48F,
  911. -d.head_length * 0.15F);
  912. QVector3D const ear_tip_left =
  913. ear_base_left +
  914. rotate_around_y(QVector3D(d.head_width * 0.12F, d.head_height * 0.55F,
  915. -d.head_length * 0.08F + ear_forward_l),
  916. ear_flick_l);
  917. QVector3D const ear_base_right =
  918. head_center + QVector3D(-d.head_width * 0.42F, d.head_height * 0.48F,
  919. -d.head_length * 0.15F);
  920. QVector3D const ear_tip_right =
  921. ear_base_right +
  922. rotate_around_y(QVector3D(-d.head_width * 0.12F, d.head_height * 0.55F,
  923. -d.head_length * 0.08F + ear_forward_r),
  924. ear_flick_r);
  925. float const ear_base_radius = d.head_width * 0.14F;
  926. float const ear_tip_radius = d.head_width * 0.06F;
  927. out.mesh(get_unit_cone(),
  928. cone_from_to(horse_ctx.model, ear_tip_left, ear_base_left,
  929. ear_base_radius),
  930. v.mane_color, nullptr, 1.0F);
  931. out.mesh(get_unit_cone(),
  932. cone_from_to(horse_ctx.model, ear_tip_right, ear_base_right,
  933. ear_base_radius),
  934. v.mane_color, nullptr, 1.0F);
  935. QVector3D const ear_inner_base(HorseVariantConstants::kEarInnerBaseR,
  936. HorseVariantConstants::kEarInnerBaseG,
  937. HorseVariantConstants::kEarInnerBaseB);
  938. QVector3D const ear_inner_color =
  939. lerp(v.mane_color, ear_inner_base,
  940. HorseVariantConstants::kEarInnerBlendFactor);
  941. QVector3D const ear_mid_left = lerp(ear_base_left, ear_tip_left, 0.4F);
  942. QVector3D const ear_mid_right = lerp(ear_base_right, ear_tip_right, 0.4F);
  943. out.mesh(
  944. get_unit_cone(),
  945. cone_from_to(horse_ctx.model, ear_tip_left, ear_mid_left, ear_tip_radius),
  946. ear_inner_color, nullptr, 1.0F);
  947. out.mesh(get_unit_cone(),
  948. cone_from_to(horse_ctx.model, ear_tip_right, ear_mid_right,
  949. ear_tip_radius),
  950. ear_inner_color, nullptr, 1.0F);
  951. QVector3D const eye_left =
  952. head_center + QVector3D(d.head_width * 0.48F, d.head_height * 0.10F,
  953. d.head_length * 0.05F);
  954. QVector3D const eye_right =
  955. head_center + QVector3D(-d.head_width * 0.48F, d.head_height * 0.10F,
  956. d.head_length * 0.05F);
  957. QVector3D eye_base_color(0.10F, 0.10F, 0.10F);
  958. auto draw_eye = [&](const QVector3D &pos) {
  959. {
  960. QMatrix4x4 eye = horse_ctx.model;
  961. eye.translate(pos);
  962. eye.scale(d.head_width * 0.14F);
  963. out.mesh(get_unit_sphere(), eye, eye_base_color, nullptr, 1.0F, 6);
  964. }
  965. {
  966. QMatrix4x4 pupil = horse_ctx.model;
  967. pupil.translate(pos + QVector3D(0.0F, 0.0F, d.head_width * 0.04F));
  968. pupil.scale(d.head_width * 0.05F);
  969. out.mesh(get_unit_sphere(), pupil, QVector3D(0.03F, 0.03F, 0.03F),
  970. nullptr, 1.0F, 6);
  971. }
  972. {
  973. QMatrix4x4 spec = horse_ctx.model;
  974. spec.translate(pos + QVector3D(d.head_width * 0.03F, d.head_width * 0.03F,
  975. d.head_width * 0.03F));
  976. spec.scale(d.head_width * 0.02F);
  977. out.mesh(get_unit_sphere(), spec, QVector3D(0.95F, 0.95F, 0.95F), nullptr,
  978. 1.0F, 6);
  979. }
  980. };
  981. draw_eye(eye_left);
  982. draw_eye(eye_right);
  983. if (has_blaze) {
  984. QMatrix4x4 blaze = horse_ctx.model;
  985. blaze.translate(head_center + QVector3D(0.0F, d.head_height * 0.15F,
  986. d.head_length * 0.10F));
  987. blaze.scale(d.head_width * 0.22F, d.head_height * 0.32F,
  988. d.head_length * 0.10F);
  989. out.mesh(get_unit_sphere(), blaze, QVector3D(0.92F, 0.92F, 0.90F), nullptr,
  990. 1.0F, 6);
  991. }
  992. QVector3D bridle_base =
  993. muzzle_center +
  994. QVector3D(0.0F, -d.head_height * 0.05F, d.muzzle_length * 0.20F);
  995. mount.bridle_base = bridle_base;
  996. QVector3D const cheek_anchor_left =
  997. head_center + QVector3D(d.head_width * 0.55F, d.head_height * 0.05F,
  998. -d.head_length * 0.05F);
  999. QVector3D const cheek_anchor_right =
  1000. head_center + QVector3D(-d.head_width * 0.55F, d.head_height * 0.05F,
  1001. -d.head_length * 0.05F);
  1002. QVector3D const brow = head_center + QVector3D(0.0F, d.head_height * 0.38F,
  1003. -d.head_length * 0.28F);
  1004. QVector3D const tack_color = lighten(v.tack_color, 0.9F);
  1005. draw_cylinder(out, horse_ctx.model, bridle_base, cheek_anchor_left,
  1006. d.head_width * 0.07F, tack_color, 1.0F, 10);
  1007. draw_cylinder(out, horse_ctx.model, bridle_base, cheek_anchor_right,
  1008. d.head_width * 0.07F, tack_color, 1.0F, 10);
  1009. draw_cylinder(out, horse_ctx.model, cheek_anchor_left, brow,
  1010. d.head_width * 0.05F, tack_color, 1.0F, 10);
  1011. draw_cylinder(out, horse_ctx.model, cheek_anchor_right, brow,
  1012. d.head_width * 0.05F, tack_color, 1.0F, 10);
  1013. QVector3D const mane_root =
  1014. neck_top + QVector3D(0.0F, d.head_height * 0.25F, -d.head_length * 0.15F);
  1015. constexpr int k_mane_segments = 16;
  1016. constexpr float k_mane_segment_divisor =
  1017. static_cast<float>(k_mane_segments - 1);
  1018. for (int i = 0; i < k_mane_segments; ++i) {
  1019. float const t = static_cast<float>(i) / k_mane_segment_divisor;
  1020. QVector3D seg_start = lerp(mane_root, neck_base, t);
  1021. seg_start.setY(seg_start.y() + (0.10F - t * 0.06F));
  1022. float const sway =
  1023. (is_moving ? std::sin((phase + t * 0.18F) * 2.0F * k_pi) *
  1024. (0.035F + rider_intensity * 0.030F)
  1025. : std::sin((anim.time * 0.7F + t * 2.5F)) * 0.025F);
  1026. QVector3D const seg_end =
  1027. seg_start + QVector3D(sway, 0.10F - t * 0.06F, -0.07F - t * 0.04F);
  1028. float const mane_thickness = d.head_width * (0.14F * (1.0F - t * 0.35F));
  1029. out.mesh(
  1030. get_unit_cylinder(),
  1031. cylinder_between(horse_ctx.model, seg_start, seg_end, mane_thickness),
  1032. v.mane_color * (0.96F + t * 0.06F), nullptr, 1.0F, 7);
  1033. }
  1034. {
  1035. QVector3D const forelock_base =
  1036. head_center +
  1037. QVector3D(0.0F, d.head_height * 0.35F, -d.head_length * 0.12F);
  1038. for (int i = 0; i < 5; ++i) {
  1039. float const offset = (i - 2) * d.head_width * 0.08F;
  1040. float const wave = std::sin(anim.time * 0.9F + i * 0.5F) * 0.01F;
  1041. QVector3D const strand_base =
  1042. forelock_base + QVector3D(offset, 0.0F, 0.0F);
  1043. QVector3D const strand_tip =
  1044. strand_base + QVector3D(offset * 0.3F + wave, -d.head_height * 0.35F,
  1045. d.head_length * 0.15F);
  1046. draw_cone(out, horse_ctx.model, strand_tip, strand_base,
  1047. d.head_width * 0.12F, v.mane_color * (0.92F + 0.04F * (i % 3)),
  1048. 0.94F, 7);
  1049. }
  1050. }
  1051. QVector3D const tail_base =
  1052. rump_center +
  1053. QVector3D(0.0F, d.body_height * 0.36F, -d.body_length * 0.34F);
  1054. QVector3D const tail_ctrl =
  1055. tail_base +
  1056. QVector3D(0.0F, -d.tail_length * 0.25F, -d.tail_length * 0.22F);
  1057. QVector3D const tail_end = tail_base + QVector3D(0.0F, -d.tail_length * 1.1F,
  1058. -d.tail_length * 0.55F);
  1059. QVector3D const tail_color = lerp3(v.tail_color, v.mane_color, 0.35F);
  1060. QVector3D prev_tail = tail_base;
  1061. constexpr int k_tail_segments = 14;
  1062. for (int i = 1; i <= k_tail_segments; ++i) {
  1063. float const t = static_cast<float>(i) / static_cast<float>(k_tail_segments);
  1064. QVector3D p = bezier(tail_base, tail_ctrl, tail_end, t);
  1065. float const primary_swing =
  1066. (is_moving ? std::sin((phase + t * 0.15F) * 2.0F * k_pi)
  1067. : std::sin((phase * 0.5F + t * 0.4F) * 2.0F * k_pi)) *
  1068. (0.035F + rider_intensity * 0.030F + 0.025F * (1.0F - t));
  1069. float const secondary_swing =
  1070. std::sin(anim.time * 1.2F + t * 3.5F) * 0.015F * (1.0F - t * 0.5F);
  1071. p.setX(p.x() + primary_swing + secondary_swing);
  1072. float const radius = d.body_width * (0.22F - 0.014F * i);
  1073. draw_cylinder(out, horse_ctx.model, prev_tail, p, radius, tail_color, 1.0F,
  1074. 7);
  1075. prev_tail = p;
  1076. }
  1077. {
  1078. QMatrix4x4 tail_knot = horse_ctx.model;
  1079. tail_knot.translate(tail_base + QVector3D(0.0F, -d.body_height * 0.04F,
  1080. -d.body_length * 0.01F));
  1081. tail_knot.scale(QVector3D(d.body_width * 0.28F, d.body_width * 0.22F,
  1082. d.body_width * 0.24F));
  1083. out.mesh(get_unit_sphere(), tail_knot, lighten(tail_color, 0.92F), nullptr,
  1084. 1.0F, 7);
  1085. }
  1086. for (int i = 0; i < 5; ++i) {
  1087. float const spread = (i - 2) * d.body_width * 0.12F;
  1088. float const swing_offset = std::sin(anim.time * 0.8F + i * 0.7F) * 0.02F;
  1089. QVector3D const fan_base =
  1090. tail_end + QVector3D(spread * 0.1F + swing_offset,
  1091. -d.body_width * 0.04F, -d.tail_length * 0.06F);
  1092. QVector3D const fan_tip =
  1093. fan_base + QVector3D(spread + swing_offset * 2.0F,
  1094. -d.tail_length * 0.38F, -d.tail_length * 0.18F);
  1095. draw_cone(out, horse_ctx.model, fan_tip, fan_base, d.body_width * 0.20F,
  1096. tail_color * (0.94F + 0.03F * (i % 3)), 0.85F, 7);
  1097. }
  1098. auto render_hoof = [&](const QVector3D &hoof_top, float hoof_height,
  1099. float half_width, float half_depth,
  1100. const QVector3D &hoof_color, bool is_rear) {
  1101. QVector3D const hoof_center =
  1102. hoof_top + QVector3D(0.0F, -hoof_height * 0.5F, 0.0F);
  1103. QVector3D const wall_tint = lighten(hoof_color, is_rear ? 1.02F : 1.05F);
  1104. QMatrix4x4 hoof_block = horse_ctx.model;
  1105. hoof_block.translate(hoof_center);
  1106. hoof_block.scale(QVector3D(half_width, hoof_height * 0.5F, half_depth));
  1107. out.mesh(get_unit_cylinder(), hoof_block, wall_tint, nullptr, 1.0F, 8);
  1108. QMatrix4x4 sole = horse_ctx.model;
  1109. sole.translate(hoof_center + QVector3D(0.0F, -hoof_height * 0.45F, 0.0F));
  1110. sole.scale(
  1111. QVector3D(half_width * 0.92F, hoof_height * 0.08F, half_depth * 0.95F));
  1112. out.mesh(get_unit_cylinder(), sole, darken(hoof_color, 0.72F), nullptr,
  1113. 1.0F, 8);
  1114. QMatrix4x4 toe = horse_ctx.model;
  1115. toe.translate(hoof_center + QVector3D(0.0F, -hoof_height * 0.10F,
  1116. is_rear ? -half_depth * 0.35F
  1117. : half_depth * 0.30F));
  1118. toe.scale(
  1119. QVector3D(half_width * 0.85F, hoof_height * 0.20F, half_depth * 0.70F));
  1120. out.mesh(get_unit_sphere(), toe, lighten(hoof_color, 1.10F), nullptr, 1.0F,
  1121. 8);
  1122. QMatrix4x4 coronet = horse_ctx.model;
  1123. coronet.translate(hoof_top + QVector3D(0.0F, -hoof_height * 0.10F, 0.0F));
  1124. coronet.scale(
  1125. QVector3D(half_width * 0.95F, half_width * 0.60F, half_depth * 1.05F));
  1126. out.mesh(get_unit_sphere(), coronet, lighten(hoof_color, 1.16F), nullptr,
  1127. 1.0F, 8);
  1128. };
  1129. constexpr float k_swing_phase_end = 0.5F;
  1130. constexpr float k_impact_settle_duration = 0.15F;
  1131. constexpr float k_impact_settle_intensity = 0.08F;
  1132. auto draw_leg = [&](const QVector3D &anchor, float lateralSign,
  1133. float forwardBias, float phase_offset, float sockChance) {
  1134. float const leg_phase = std::fmod(phase + phase_offset, 1.0F);
  1135. float stride = 0.0F;
  1136. float lift = 0.0F;
  1137. float const weight_shift_time = anim.time * 0.15F;
  1138. bool const is_rear = (forwardBias < 0.0F);
  1139. float weight_shift = 0.0F;
  1140. if (!is_moving && is_rear) {
  1141. float const shift_cycle =
  1142. std::sin(weight_shift_time + lateralSign * k_pi * 0.5F);
  1143. weight_shift = shift_cycle * 0.03F;
  1144. }
  1145. if (is_moving) {
  1146. bool const is_galloping = (g.stride_swing > 0.7F);
  1147. float const swing_progress =
  1148. leg_phase < k_swing_phase_end ? leg_phase / k_swing_phase_end : 0.0F;
  1149. float const stance_progress =
  1150. leg_phase >= k_swing_phase_end
  1151. ? (leg_phase - k_swing_phase_end) / (1.0F - k_swing_phase_end)
  1152. : 0.0F;
  1153. float const swing_ease =
  1154. swing_progress * swing_progress * (3.0F - 2.0F * swing_progress);
  1155. float const stance_ease =
  1156. stance_progress * stance_progress * (3.0F - 2.0F * stance_progress);
  1157. float stride_mult = 0.85F;
  1158. float lift_mult = 1.0F;
  1159. if (is_galloping) {
  1160. float const suspension_phase =
  1161. (leg_phase > 0.35F && leg_phase < 0.45F) ? 1.0F : 0.0F;
  1162. stride_mult = 0.88F + swing_ease * 0.08F;
  1163. lift_mult = 1.0F + suspension_phase * 0.12F;
  1164. float const reach_factor = is_rear ? 0.90F : 1.08F;
  1165. stride_mult *= reach_factor;
  1166. }
  1167. float const rest_stride = forwardBias - g.stride_swing * 0.25F;
  1168. float const peak_stride =
  1169. g.stride_swing * stride_mult + forwardBias - g.stride_swing * 0.25F;
  1170. stride = leg_phase < k_swing_phase_end
  1171. ? rest_stride + swing_ease * (peak_stride - rest_stride)
  1172. : peak_stride - stance_ease * (peak_stride - rest_stride);
  1173. float const lift_curve = std::sin(swing_progress * k_pi);
  1174. float const impact_settle =
  1175. stance_progress < k_impact_settle_duration
  1176. ? std::sin(stance_progress / k_impact_settle_duration * k_pi) *
  1177. k_impact_settle_intensity
  1178. : 0.0F;
  1179. lift = leg_phase < k_swing_phase_end
  1180. ? lift_curve * g.stride_lift * lift_mult
  1181. : -impact_settle * g.stride_lift;
  1182. } else {
  1183. float const idle = std::sin(leg_phase * 2.0F * k_pi);
  1184. float const secondary =
  1185. std::sin(anim.time * 0.7F + lateralSign * 1.2F) * 0.015F;
  1186. stride = idle * g.stride_swing * 0.08F + forwardBias + secondary +
  1187. weight_shift;
  1188. lift = idle * d.idle_bob_amplitude * 2.5F;
  1189. }
  1190. if (!is_rear) {
  1191. stride =
  1192. std::clamp(stride, -d.body_length * 0.08F, d.body_length * 0.35F);
  1193. }
  1194. bool const tighten_legs = is_moving;
  1195. float const shoulder_out = d.body_width * (tighten_legs ? 0.42F : 0.56F) *
  1196. (is_rear ? 0.96F : 1.0F);
  1197. float const shoulder_height = (is_rear ? 0.02F : 0.05F);
  1198. float const stance_pull =
  1199. is_rear ? -d.body_length * 0.04F : d.body_length * 0.05F;
  1200. float const stance_stagger =
  1201. lateralSign *
  1202. (is_rear ? -d.body_length * 0.020F : d.body_length * 0.030F);
  1203. QVector3D shoulder =
  1204. anchor + QVector3D(lateralSign * shoulder_out,
  1205. shoulder_height + lift * 0.04F,
  1206. stride + stance_pull + stance_stagger);
  1207. float const gallop_angle = leg_phase * 2.0F * k_pi;
  1208. bool const is_galloping = (g.stride_swing > 0.7F);
  1209. float hip_swing_mult = is_galloping ? 1.1F : 1.0F;
  1210. float const hip_swing =
  1211. is_moving ? std::sin(gallop_angle) * hip_swing_mult : 0.0F;
  1212. float const lift_factor =
  1213. is_moving
  1214. ? std::max(0.0F,
  1215. std::sin(gallop_angle + (is_rear ? 0.35F : -0.25F)))
  1216. : 0.0F;
  1217. float const hip_flex =
  1218. is_galloping ? (is_rear ? -0.08F : 0.06F) : (is_rear ? -0.06F : 0.05F);
  1219. shoulder.setZ(shoulder.z() + hip_swing * hip_flex);
  1220. if (tighten_legs) {
  1221. shoulder.setX(shoulder.x() - lateralSign * lift_factor * 0.04F);
  1222. }
  1223. float const upper_length = d.leg_length * (is_rear ? 0.48F : 0.46F);
  1224. float const lower_length = d.leg_length * (is_rear ? 0.43F : 0.49F);
  1225. float const pastern_length = d.leg_length * (is_rear ? 0.12F : 0.14F);
  1226. float const stance_phase = smoothstep(0.0F, 0.3F, leg_phase);
  1227. float const swing_phase = smoothstep(0.3F, 0.7F, leg_phase);
  1228. float const extend_phase = smoothstep(0.7F, 1.0F, leg_phase);
  1229. float knee_flex_base =
  1230. (swing_phase * (1.0F - extend_phase) * (is_rear ? 0.85F : 1.10F));
  1231. float knee_flex_gallop =
  1232. is_galloping ? knee_flex_base * 1.08F : knee_flex_base;
  1233. float const knee_flex = is_moving ? knee_flex_gallop : 0.35F;
  1234. float cannon_flex_base = smoothstep(0.35F, 0.65F, leg_phase) *
  1235. (1.0F - extend_phase) * (is_rear ? 0.70F : 0.85F);
  1236. float cannon_flex_gallop =
  1237. is_galloping ? cannon_flex_base * 1.05F : cannon_flex_base;
  1238. float const cannon_flex = is_moving ? cannon_flex_gallop : 0.35F;
  1239. float const fetlock_compress =
  1240. is_moving ? std::max(stance_phase * 0.4F,
  1241. (1.0F - swing_phase) * extend_phase * 0.6F)
  1242. : 0.2F;
  1243. float const backward_bias = is_rear ? -0.42F : -0.18F;
  1244. float const hip_drive =
  1245. (is_rear ? -1.0F : 1.0F) * hip_swing * (is_rear ? 0.20F : 0.35F);
  1246. float const upper_vertical =
  1247. -0.90F - lift_factor * 0.08F - knee_flex * (is_rear ? 0.25F : 0.40F);
  1248. QVector3D upper_dir(lateralSign * (tighten_legs ? -0.05F : -0.02F),
  1249. upper_vertical, backward_bias + hip_drive);
  1250. if (upper_dir.lengthSquared() < 1e-6F) {
  1251. upper_dir = QVector3D(0.0F, -1.0F, backward_bias);
  1252. }
  1253. upper_dir.normalize();
  1254. QVector3D knee = shoulder + upper_dir * upper_length;
  1255. float const knee_out = d.body_width * (is_rear ? 0.08F : 0.06F);
  1256. knee.setX(knee.x() + lateralSign * knee_out);
  1257. float const joint_drive =
  1258. is_moving
  1259. ? clamp01(std::sin(gallop_angle + (is_rear ? 0.50F : -0.35F)) *
  1260. 0.55F +
  1261. 0.45F)
  1262. : 0.35F;
  1263. float const lower_forward =
  1264. (is_rear ? 0.44F : 0.20F) +
  1265. (is_rear ? 0.30F : 0.18F) * (joint_drive - 0.5F) - cannon_flex * 0.35F;
  1266. float const lower_vertical = -0.95F + cannon_flex * 0.15F;
  1267. QVector3D lower_dir(lateralSign * (tighten_legs ? -0.02F : -0.01F),
  1268. lower_vertical, lower_forward);
  1269. if (lower_dir.lengthSquared() < 1e-6F) {
  1270. lower_dir = QVector3D(0.0F, -1.0F, lower_forward);
  1271. }
  1272. lower_dir.normalize();
  1273. QVector3D cannon = knee + lower_dir * lower_length;
  1274. float const pastern_bias = is_rear ? -0.30F : 0.08F;
  1275. float const pastern_dyn =
  1276. (is_rear ? -0.10F : 0.05F) * (joint_drive - 0.5F) +
  1277. fetlock_compress * 0.25F;
  1278. QVector3D pastern_dir(0.0F, -1.0F, pastern_bias + pastern_dyn);
  1279. if (pastern_dir.lengthSquared() < 1e-6F) {
  1280. pastern_dir = QVector3D(0.0F, -1.0F, pastern_bias);
  1281. }
  1282. pastern_dir.normalize();
  1283. QVector3D fetlock = cannon + pastern_dir * pastern_length;
  1284. QVector3D hoof_top = fetlock;
  1285. if (is_moving) {
  1286. float hoof_lift_amount = 0.0F;
  1287. if (leg_phase < k_swing_phase_end && lift > 0.0F) {
  1288. hoof_lift_amount = lift * 0.35F;
  1289. }
  1290. hoof_top.setY(hoof_top.y() + hoof_lift_amount);
  1291. fetlock = hoof_top;
  1292. }
  1293. float const shoulder_r = d.body_width * (is_rear ? 0.38F : 0.35F);
  1294. float const upper_r = shoulder_r * (is_rear ? 0.92F : 0.88F);
  1295. float const knee_r = upper_r * 0.85F;
  1296. float const cannon_r = knee_r * 0.72F;
  1297. float const pastern_r = cannon_r * 0.78F;
  1298. QVector3D const thigh_color = coat_gradient(
  1299. v.coat_color, is_rear ? 0.48F : 0.58F, is_rear ? -0.22F : 0.18F,
  1300. coat_seed_a + lateralSign * 0.07F);
  1301. draw_cylinder(out, horse_ctx.model, shoulder, knee,
  1302. (shoulder_r + upper_r) * 0.5F, thigh_color, 1.0F, 6);
  1303. {
  1304. QMatrix4x4 knee_joint = horse_ctx.model;
  1305. knee_joint.translate(knee);
  1306. float const joint_scale = knee_r * 1.15F;
  1307. knee_joint.scale(joint_scale, joint_scale * 0.9F, joint_scale);
  1308. QVector3D const joint_color = darken(thigh_color, 0.95F);
  1309. out.mesh(get_unit_sphere(), knee_joint, joint_color, nullptr, 1.0F, 6);
  1310. }
  1311. QVector3D const shin_color = darken(thigh_color, is_rear ? 0.90F : 0.92F);
  1312. draw_cylinder(out, horse_ctx.model, knee, cannon,
  1313. (knee_r + cannon_r) * 0.45F, shin_color, 1.0F, 6);
  1314. {
  1315. QVector3D const tendon_offset =
  1316. QVector3D(0.0F, 0.0F, is_rear ? cannon_r * 0.4F : -cannon_r * 0.4F);
  1317. QVector3D const tendon_start = knee + tendon_offset;
  1318. QVector3D const tendon_end = cannon + tendon_offset;
  1319. draw_cylinder(out, horse_ctx.model, tendon_start, tendon_end,
  1320. cannon_r * 0.35F, darken(shin_color, 0.88F), 1.0F, 6);
  1321. }
  1322. QVector3D const hoof_joint_color =
  1323. darken(shin_color, is_rear ? 0.92F : 0.94F);
  1324. float const sock =
  1325. sockChance > 0.78F ? 1.0F : (sockChance > 0.58F ? 0.55F : 0.0F);
  1326. QVector3D const distal_color =
  1327. (sock > 0.0F) ? lighten(v.coat_color, 1.18F) : v.coat_color * 0.92F;
  1328. float const t_sock = smoothstep(0.0F, 1.0F, sock);
  1329. QVector3D const pastern_color =
  1330. lerp(hoof_joint_color, distal_color, t_sock * 0.8F);
  1331. {
  1332. QMatrix4x4 fetlock_joint = horse_ctx.model;
  1333. fetlock_joint.translate(cannon);
  1334. float const fetlock_scale = cannon_r * 1.1F;
  1335. fetlock_joint.scale(fetlock_scale, fetlock_scale * 0.85F, fetlock_scale);
  1336. out.mesh(get_unit_sphere(), fetlock_joint,
  1337. lerp(hoof_joint_color, pastern_color, 0.3F), nullptr, 1.0F, 6);
  1338. }
  1339. draw_cylinder(out, horse_ctx.model, cannon, fetlock,
  1340. (cannon_r * 0.85F + pastern_r) * 0.5F,
  1341. lerp(hoof_joint_color, pastern_color, 0.5F), 1.0F, 6);
  1342. QVector3D const fetlock_color = lerp(pastern_color, distal_color, 0.25F);
  1343. QVector3D const hoof_color = v.hoof_color;
  1344. float const hoof_width = pastern_r * (is_rear ? 1.70F : 1.60F);
  1345. float const hoof_depth = hoof_width * (is_rear ? 0.95F : 1.10F);
  1346. render_hoof(hoof_top, d.hoof_height, hoof_width, hoof_depth, hoof_color,
  1347. is_rear);
  1348. if (sock > 0.0F) {
  1349. QVector3D const feather_tip = lerp(fetlock, hoof_top, 0.35F) +
  1350. QVector3D(0.0F, -pastern_r * 0.65F, 0.0F);
  1351. draw_cone(out, horse_ctx.model, feather_tip, fetlock, pastern_r * 0.90F,
  1352. lerp(distal_color, v.coat_color, 0.25F), 0.85F, 6);
  1353. }
  1354. };
  1355. QVector3D const front_anchor =
  1356. barrel_center +
  1357. QVector3D(0.0F, d.body_height * 0.05F, d.body_length * 0.32F);
  1358. QVector3D const rear_anchor =
  1359. barrel_center +
  1360. QVector3D(0.0F, d.body_height * 0.02F, -d.body_length * 0.30F);
  1361. float const front_forward_bias = d.body_length * 0.16F;
  1362. float const front_bias_offset = d.body_length * 0.035F;
  1363. draw_leg(front_anchor, 1.0F, front_forward_bias + front_bias_offset,
  1364. g.front_leg_phase, sock_chance_fl);
  1365. draw_leg(front_anchor, -1.0F, front_forward_bias - front_bias_offset,
  1366. g.front_leg_phase + 0.50F, sock_chance_fr);
  1367. float const rear_forward_bias = -d.body_length * 0.16F;
  1368. float const rear_bias_offset = d.body_length * 0.032F;
  1369. draw_leg(rear_anchor, 1.0F, rear_forward_bias - rear_bias_offset,
  1370. g.rear_leg_phase + 0.50F, sock_chance_rl);
  1371. draw_leg(rear_anchor, -1.0F, rear_forward_bias + rear_bias_offset,
  1372. g.rear_leg_phase, sock_chance_rr);
  1373. QVector3D const bit_left =
  1374. muzzle_center + QVector3D(d.head_width * 0.55F, -d.head_height * 0.08F,
  1375. d.muzzle_length * 0.10F);
  1376. QVector3D const bit_right =
  1377. muzzle_center + QVector3D(-d.head_width * 0.55F, -d.head_height * 0.08F,
  1378. d.muzzle_length * 0.10F);
  1379. mount.rein_bit_left = bit_left;
  1380. mount.rein_bit_right = bit_right;
  1381. HorseBodyFrames body_frames;
  1382. QVector3D const forward(0.0F, 0.0F, 1.0F);
  1383. QVector3D const up(0.0F, 1.0F, 0.0F);
  1384. QVector3D const right(1.0F, 0.0F, 0.0F);
  1385. body_frames.head.origin = head_center;
  1386. body_frames.head.right = right;
  1387. body_frames.head.up = up;
  1388. body_frames.head.forward = forward;
  1389. body_frames.neck_base.origin = neck_base;
  1390. body_frames.neck_base.right = right;
  1391. body_frames.neck_base.up = up;
  1392. body_frames.neck_base.forward = forward;
  1393. QVector3D const withers_pos =
  1394. chest_center +
  1395. QVector3D(0.0F, d.body_height * 0.55F, -d.body_length * 0.06F);
  1396. body_frames.withers.origin = withers_pos;
  1397. body_frames.withers.right = right;
  1398. body_frames.withers.up = up;
  1399. body_frames.withers.forward = forward;
  1400. body_frames.back_center.origin = mount.saddle_center;
  1401. body_frames.back_center.right = right;
  1402. body_frames.back_center.up = up;
  1403. body_frames.back_center.forward = forward;
  1404. QVector3D const croup_pos =
  1405. rump_center +
  1406. QVector3D(0.0F, d.body_height * 0.46F, -d.body_length * 0.18F);
  1407. body_frames.croup.origin = croup_pos;
  1408. body_frames.croup.right = right;
  1409. body_frames.croup.up = up;
  1410. body_frames.croup.forward = forward;
  1411. body_frames.chest.origin = chest_center;
  1412. body_frames.chest.right = right;
  1413. body_frames.chest.up = up;
  1414. body_frames.chest.forward = forward;
  1415. body_frames.barrel.origin = barrel_center;
  1416. body_frames.barrel.right = right;
  1417. body_frames.barrel.up = up;
  1418. body_frames.barrel.forward = forward;
  1419. body_frames.rump.origin = rump_center;
  1420. body_frames.rump.right = right;
  1421. body_frames.rump.up = up;
  1422. body_frames.rump.forward = forward;
  1423. QVector3D const tail_base_pos =
  1424. rump_center + QVector3D(0.0F, d.body_height * 0.20F, -100.05F);
  1425. body_frames.tail_base.origin = tail_base_pos;
  1426. body_frames.tail_base.right = right;
  1427. body_frames.tail_base.up = up;
  1428. body_frames.tail_base.forward = forward;
  1429. body_frames.muzzle.origin = muzzle_center;
  1430. body_frames.muzzle.right = right;
  1431. body_frames.muzzle.up = up;
  1432. body_frames.muzzle.forward = forward;
  1433. draw_attachments(horse_ctx, anim, rider_ctx, profile, mount, phase, bob,
  1434. rein_slack, body_frames, out);
  1435. }
  1436. void HorseRendererBase::render_simplified(
  1437. const DrawContext &ctx, const AnimationInputs &anim,
  1438. const HumanoidAnimationContext &rider_ctx, HorseProfile &profile,
  1439. const MountedAttachmentFrame *shared_mount,
  1440. const HorseMotionSample *shared_motion, ISubmitter &out) const {
  1441. const HorseDimensions &d = profile.dims;
  1442. const HorseVariant &v = profile.variant;
  1443. const HorseGait &g = profile.gait;
  1444. HorseMotionSample const motion =
  1445. shared_motion ? *shared_motion
  1446. : evaluate_horse_motion(profile, anim, rider_ctx);
  1447. float const phase = motion.phase;
  1448. float const bob = motion.bob;
  1449. const bool is_moving = motion.is_moving;
  1450. MountedAttachmentFrame mount =
  1451. shared_mount ? *shared_mount : compute_mount_frame(profile);
  1452. if (!shared_mount) {
  1453. apply_mount_vertical_offset(mount, bob);
  1454. }
  1455. DrawContext horse_ctx = ctx;
  1456. horse_ctx.model = ctx.model;
  1457. horse_ctx.model.translate(mount.ground_offset);
  1458. QVector3D const barrel_center(0.0F, d.barrel_center_y + bob, 0.0F);
  1459. {
  1460. QMatrix4x4 body = horse_ctx.model;
  1461. body.translate(barrel_center);
  1462. body.scale(d.body_width * 1.0F, d.body_height * 0.85F,
  1463. d.body_length * 0.80F);
  1464. out.mesh(get_unit_sphere(), body, v.coat_color, nullptr, 1.0F, 6);
  1465. }
  1466. QVector3D const neck_base =
  1467. barrel_center +
  1468. QVector3D(0.0F, d.body_height * 0.35F, d.body_length * 0.35F);
  1469. QVector3D const neck_top =
  1470. neck_base + QVector3D(0.0F, d.neck_rise, d.neck_length);
  1471. draw_cylinder(out, horse_ctx.model, neck_base, neck_top, d.body_width * 0.40F,
  1472. v.coat_color, 1.0F);
  1473. QVector3D const head_center =
  1474. neck_top + QVector3D(0.0F, d.head_height * 0.10F, d.head_length * 0.40F);
  1475. {
  1476. QMatrix4x4 head = horse_ctx.model;
  1477. head.translate(head_center);
  1478. head.scale(d.head_width * 0.90F, d.head_height * 0.85F,
  1479. d.head_length * 0.75F);
  1480. out.mesh(get_unit_sphere(), head, v.coat_color, nullptr, 1.0F);
  1481. }
  1482. QVector3D const front_anchor =
  1483. barrel_center +
  1484. QVector3D(0.0F, d.body_height * 0.05F, d.body_length * 0.30F);
  1485. QVector3D const rear_anchor =
  1486. barrel_center +
  1487. QVector3D(0.0F, d.body_height * 0.02F, -d.body_length * 0.28F);
  1488. auto draw_simple_leg = [&](const QVector3D &anchor, float lateralSign,
  1489. float forwardBias, float phase_offset) {
  1490. float const leg_phase = std::fmod(phase + phase_offset, 1.0F);
  1491. float stride = 0.0F;
  1492. float lift = 0.0F;
  1493. if (is_moving) {
  1494. float const angle = leg_phase * 2.0F * k_pi;
  1495. stride = std::sin(angle) * g.stride_swing * 0.6F + forwardBias;
  1496. float const lift_raw = std::sin(angle);
  1497. lift = lift_raw > 0.0F ? lift_raw * g.stride_lift * 0.8F : 0.0F;
  1498. }
  1499. float const shoulder_out = d.body_width * 0.45F;
  1500. QVector3D shoulder =
  1501. anchor + QVector3D(lateralSign * shoulder_out, lift * 0.05F, stride);
  1502. float const leg_length = d.leg_length * 0.85F;
  1503. QVector3D const foot = shoulder + QVector3D(0.0F, -leg_length + lift, 0.0F);
  1504. draw_cylinder(out, horse_ctx.model, shoulder, foot, d.body_width * 0.22F,
  1505. v.coat_color * 0.85F, 1.0F, 6);
  1506. QMatrix4x4 hoof = horse_ctx.model;
  1507. hoof.translate(foot);
  1508. hoof.scale(d.body_width * 0.28F, d.hoof_height, d.body_width * 0.30F);
  1509. out.mesh(get_unit_cylinder(), hoof, v.hoof_color, nullptr, 1.0F, 8);
  1510. };
  1511. draw_simple_leg(front_anchor, 1.0F, d.body_length * 0.15F, g.front_leg_phase);
  1512. draw_simple_leg(front_anchor, -1.0F, d.body_length * 0.15F,
  1513. g.front_leg_phase + 0.48F);
  1514. draw_simple_leg(rear_anchor, 1.0F, -d.body_length * 0.15F, g.rear_leg_phase);
  1515. draw_simple_leg(rear_anchor, -1.0F, -d.body_length * 0.15F,
  1516. g.rear_leg_phase + 0.52F);
  1517. }
  1518. void HorseRendererBase::render_minimal(const DrawContext &ctx,
  1519. HorseProfile &profile,
  1520. const HorseMotionSample *shared_motion,
  1521. ISubmitter &out) const {
  1522. const HorseDimensions &d = profile.dims;
  1523. const HorseVariant &v = profile.variant;
  1524. float const bob = shared_motion ? shared_motion->bob : 0.0F;
  1525. MountedAttachmentFrame mount = compute_mount_frame(profile);
  1526. apply_mount_vertical_offset(mount, bob);
  1527. DrawContext horse_ctx = ctx;
  1528. horse_ctx.model = ctx.model;
  1529. horse_ctx.model.translate(mount.ground_offset);
  1530. QVector3D const center(0.0F, d.barrel_center_y + bob, 0.0F);
  1531. QMatrix4x4 body = horse_ctx.model;
  1532. body.translate(center);
  1533. body.scale(d.body_width * 1.2F, d.body_height + d.neck_rise * 0.5F,
  1534. d.body_length + d.head_length * 0.5F);
  1535. out.mesh(get_unit_sphere(), body, v.coat_color, nullptr, 1.0F, 6);
  1536. for (int i = 0; i < 4; ++i) {
  1537. float const x_sign = (i % 2 == 0) ? 1.0F : -1.0F;
  1538. float const z_offset =
  1539. (i < 2) ? d.body_length * 0.25F : -d.body_length * 0.25F;
  1540. QVector3D const top = center + QVector3D(x_sign * d.body_width * 0.40F,
  1541. -d.body_height * 0.3F, z_offset);
  1542. QVector3D const bottom = top + QVector3D(0.0F, -d.leg_length * 0.60F, 0.0F);
  1543. draw_cylinder(out, horse_ctx.model, top, bottom, d.body_width * 0.15F,
  1544. v.coat_color * 0.75F, 1.0F, 6);
  1545. }
  1546. }
  1547. void HorseRendererBase::render(const DrawContext &ctx,
  1548. const AnimationInputs &anim,
  1549. const HumanoidAnimationContext &rider_ctx,
  1550. HorseProfile &profile,
  1551. const MountedAttachmentFrame *shared_mount,
  1552. const ReinState *shared_reins,
  1553. const HorseMotionSample *shared_motion,
  1554. ISubmitter &out, HorseLOD lod) const {
  1555. ++s_horseRenderStats.horses_total;
  1556. if (lod == HorseLOD::Billboard) {
  1557. ++s_horseRenderStats.horses_skipped_lod;
  1558. return;
  1559. }
  1560. ++s_horseRenderStats.horses_rendered;
  1561. switch (lod) {
  1562. case HorseLOD::Full:
  1563. ++s_horseRenderStats.lod_full;
  1564. render_full(ctx, anim, rider_ctx, profile, shared_mount, shared_reins,
  1565. shared_motion, out);
  1566. break;
  1567. case HorseLOD::Reduced:
  1568. ++s_horseRenderStats.lod_reduced;
  1569. render_simplified(ctx, anim, rider_ctx, profile, shared_mount,
  1570. shared_motion, out);
  1571. break;
  1572. case HorseLOD::Minimal:
  1573. ++s_horseRenderStats.lod_minimal;
  1574. render_minimal(ctx, profile, shared_motion, out);
  1575. break;
  1576. case HorseLOD::Billboard:
  1577. break;
  1578. }
  1579. }
  1580. void HorseRendererBase::render(
  1581. const DrawContext &ctx, const AnimationInputs &anim,
  1582. const HumanoidAnimationContext &rider_ctx, HorseProfile &profile,
  1583. const MountedAttachmentFrame *shared_mount, const ReinState *shared_reins,
  1584. const HorseMotionSample *shared_motion, ISubmitter &out) const {
  1585. render(ctx, anim, rider_ctx, profile, shared_mount, shared_reins,
  1586. shared_motion, out, HorseLOD::Full);
  1587. }
  1588. } // namespace Render::GL