rig.cpp 71 KB

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