Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

835 lines
25 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "tier1/convar.h"
  8. #include "jigglebones.h"
  9. #ifdef CLIENT_DLL
  10. #include "engine/ivdebugoverlay.h"
  11. #include "cdll_client_int.h"
  12. #endif // CLIENT_DLL
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. #ifdef CLIENT_DLL
  16. //-----------------------------------------------------------------------------
  17. ConVar cl_jiggle_bone_debug( "cl_jiggle_bone_debug", "0", FCVAR_CHEAT, "Display physics-based 'jiggle bone' debugging information" );
  18. ConVar cl_jiggle_bone_debug_yaw_constraints( "cl_jiggle_bone_debug_yaw_constraints", "0", FCVAR_CHEAT, "Display physics-based 'jiggle bone' debugging information" );
  19. ConVar cl_jiggle_bone_debug_pitch_constraints( "cl_jiggle_bone_debug_pitch_constraints", "0", FCVAR_CHEAT, "Display physics-based 'jiggle bone' debugging information" );
  20. #endif // CLIENT_DLL
  21. ConVar cl_jiggle_bone_framerate_cutoff( "cl_jiggle_bone_framerate_cutoff", "20", 0, "Skip jiggle bone simulation if framerate drops below this value (frames/second)" );
  22. //-----------------------------------------------------------------------------
  23. JiggleData * CJiggleBones::GetJiggleData( int bone, float currenttime, const Vector &initBasePos, const Vector &initTipPos )
  24. {
  25. FOR_EACH_LL( m_jiggleBoneState, it )
  26. {
  27. if ( m_jiggleBoneState[it].bone == bone )
  28. {
  29. return &m_jiggleBoneState[it];
  30. }
  31. }
  32. JiggleData data;
  33. data.Init( bone, currenttime, initBasePos, initTipPos );
  34. // Start out using jiggle bones for at least 16 frames.
  35. data.useGoalMatrixCount = 0;
  36. data.useJiggleBoneCount = 16;
  37. int idx = m_jiggleBoneState.AddToHead( data );
  38. if ( idx == m_jiggleBoneState.InvalidIndex() )
  39. return NULL;
  40. return &m_jiggleBoneState[idx];
  41. }
  42. //-----------------------------------------------------------------------------
  43. /**
  44. * Do spring physics calculations and update "jiggle bone" matrix
  45. * (Michael Booth, Turtle Rock Studios)
  46. */
  47. void CJiggleBones::BuildJiggleTransformations( int boneIndex, float currenttime, const mstudiojigglebone_t *jiggleInfo, const matrix3x4_t &goalMX, matrix3x4_t &boneMX )
  48. {
  49. Vector goalBasePosition;
  50. MatrixPosition( goalMX, goalBasePosition );
  51. Vector goalForward, goalUp, goalLeft;
  52. MatrixGetColumn( goalMX, 0, goalLeft );
  53. MatrixGetColumn( goalMX, 1, goalUp );
  54. MatrixGetColumn( goalMX, 2, goalForward );
  55. // compute goal tip position
  56. Vector goalTip = goalBasePosition + jiggleInfo->length * goalForward;
  57. JiggleData *data = GetJiggleData( boneIndex, currenttime, goalBasePosition, goalTip );
  58. if ( !data )
  59. {
  60. return;
  61. }
  62. // if frames have been skipped since our last update, we were likely
  63. // disabled and re-enabled, so re-init
  64. #if defined(CLIENT_DLL) || defined(GAME_DLL)
  65. float timeTolerance = 1.2f * gpGlobals->frametime;
  66. #else
  67. float timeTolerance = 0.5f;
  68. #endif
  69. if ( currenttime - data->lastUpdate > timeTolerance )
  70. {
  71. data->Init( boneIndex, currenttime, goalBasePosition, goalTip );
  72. }
  73. if ( data->lastLeft.IsZero() )
  74. {
  75. data->lastLeft = goalLeft;
  76. }
  77. // limit maximum deltaT to avoid simulation blowups
  78. // if framerate is too low, skip jigglebones altogether, since movement will be too
  79. // large between frames to simulate with a simple Euler integration
  80. float deltaT = currenttime - data->lastUpdate;
  81. const float thousandHZ = 0.001f;
  82. bool bMaxDeltaT = deltaT < thousandHZ;
  83. bool bUseGoalMatrix = cl_jiggle_bone_framerate_cutoff.GetFloat() <= 0.0f || deltaT > ( 1.0f / cl_jiggle_bone_framerate_cutoff.GetFloat() );
  84. if ( bUseGoalMatrix )
  85. {
  86. // We hit the jiggle bone framerate cutoff. Reset the useGoalMatrixCount so we
  87. // use the goal matrix at least 32 frames and don't flash back and forth.
  88. data->useGoalMatrixCount = 32;
  89. }
  90. else if ( data->useGoalMatrixCount > 0 )
  91. {
  92. // Below the cutoff, but still need to use the goal matrix a few more times.
  93. bUseGoalMatrix = true;
  94. data->useGoalMatrixCount--;
  95. }
  96. else
  97. {
  98. // Use real jiggle bones. Woot!
  99. data->useJiggleBoneCount = 32;
  100. }
  101. if ( data->useJiggleBoneCount > 0 )
  102. {
  103. // Make sure we draw at least runs of 32 frames with real jiggle bones.
  104. data->useJiggleBoneCount--;
  105. data->useGoalMatrixCount = 0;
  106. bUseGoalMatrix = false;
  107. }
  108. if ( bMaxDeltaT )
  109. {
  110. deltaT = thousandHZ;
  111. }
  112. else if ( bUseGoalMatrix )
  113. {
  114. // disable jigglebone - just use goal matrix
  115. boneMX = goalMX;
  116. return;
  117. }
  118. // we want lastUpdate here, so if jigglebones were skipped they get reinitialized if they turn back on
  119. data->lastUpdate = currenttime;
  120. //
  121. // Bone tip flex
  122. //
  123. if ( jiggleInfo->flags & ( JIGGLE_IS_FLEXIBLE | JIGGLE_IS_RIGID ) )
  124. {
  125. // apply gravity in global space
  126. data->tipAccel.z -= jiggleInfo->tipMass;
  127. if ( jiggleInfo->flags & JIGGLE_IS_FLEXIBLE )
  128. {
  129. // decompose into local coordinates
  130. Vector error = goalTip - data->tipPos;
  131. Vector localError;
  132. localError.x = DotProduct( goalLeft, error );
  133. localError.y = DotProduct( goalUp, error );
  134. localError.z = DotProduct( goalForward, error );
  135. Vector localVel;
  136. localVel.x = DotProduct( goalLeft, data->tipVel );
  137. localVel.y = DotProduct( goalUp, data->tipVel );
  138. // yaw spring
  139. float yawAccel = jiggleInfo->yawStiffness * localError.x - jiggleInfo->yawDamping * localVel.x;
  140. // pitch spring
  141. float pitchAccel = jiggleInfo->pitchStiffness * localError.y - jiggleInfo->pitchDamping * localVel.y;
  142. if ( jiggleInfo->flags & JIGGLE_HAS_LENGTH_CONSTRAINT )
  143. {
  144. // drive tip towards goal tip position
  145. data->tipAccel += yawAccel * goalLeft + pitchAccel * goalUp;
  146. }
  147. else
  148. {
  149. // allow flex along length of spring
  150. localVel.z = DotProduct( goalForward, data->tipVel );
  151. // along spring
  152. float alongAccel = jiggleInfo->alongStiffness * localError.z - jiggleInfo->alongDamping * localVel.z;
  153. // drive tip towards goal tip position
  154. data->tipAccel += yawAccel * goalLeft + pitchAccel * goalUp + alongAccel * goalForward;
  155. }
  156. }
  157. // simple euler integration
  158. data->tipVel += data->tipAccel * deltaT;
  159. data->tipPos += data->tipVel * deltaT;
  160. // clear this timestep's accumulated accelerations
  161. data->tipAccel = vec3_origin;
  162. //
  163. // Apply optional constraints
  164. //
  165. if ( jiggleInfo->flags & ( JIGGLE_HAS_YAW_CONSTRAINT | JIGGLE_HAS_PITCH_CONSTRAINT ) )
  166. {
  167. // find components of spring vector in local coordinate system
  168. Vector along = data->tipPos - goalBasePosition;
  169. Vector localAlong;
  170. localAlong.x = DotProduct( goalLeft, along );
  171. localAlong.y = DotProduct( goalUp, along );
  172. localAlong.z = DotProduct( goalForward, along );
  173. Vector localVel;
  174. localVel.x = DotProduct( goalLeft, data->tipVel );
  175. localVel.y = DotProduct( goalUp, data->tipVel );
  176. localVel.z = DotProduct( goalForward, data->tipVel );
  177. if ( jiggleInfo->flags & JIGGLE_HAS_YAW_CONSTRAINT )
  178. {
  179. // enforce yaw constraints in local XZ plane
  180. float yawError = atan2( localAlong.x, localAlong.z );
  181. bool isAtLimit = false;
  182. float yaw = 0.0f;
  183. if ( yawError < jiggleInfo->minYaw )
  184. {
  185. // at angular limit
  186. isAtLimit = true;
  187. yaw = jiggleInfo->minYaw;
  188. }
  189. else if ( yawError > jiggleInfo->maxYaw )
  190. {
  191. // at angular limit
  192. isAtLimit = true;
  193. yaw = jiggleInfo->maxYaw;
  194. }
  195. if ( isAtLimit )
  196. {
  197. float sy, cy;
  198. SinCos( yaw, &sy, &cy );
  199. // yaw matrix
  200. matrix3x4_t yawMatrix;
  201. yawMatrix[0][0] = cy;
  202. yawMatrix[1][0] = 0;
  203. yawMatrix[2][0] = -sy;
  204. yawMatrix[0][1] = 0;
  205. yawMatrix[1][1] = 1.0f;
  206. yawMatrix[2][1] = 0;
  207. yawMatrix[0][2] = sy;
  208. yawMatrix[1][2] = 0;
  209. yawMatrix[2][2] = cy;
  210. yawMatrix[0][3] = 0;
  211. yawMatrix[1][3] = 0;
  212. yawMatrix[2][3] = 0;
  213. // global coordinates of limit
  214. matrix3x4_t limitMatrix;
  215. ConcatTransforms( goalMX, yawMatrix, limitMatrix );
  216. Vector limitLeft( limitMatrix.m_flMatVal[0][0],
  217. limitMatrix.m_flMatVal[1][0],
  218. limitMatrix.m_flMatVal[2][0] );
  219. Vector limitUp( limitMatrix.m_flMatVal[0][1],
  220. limitMatrix.m_flMatVal[1][1],
  221. limitMatrix.m_flMatVal[2][1] );
  222. Vector limitForward( limitMatrix.m_flMatVal[0][2],
  223. limitMatrix.m_flMatVal[1][2],
  224. limitMatrix.m_flMatVal[2][2] );
  225. #ifdef CLIENT_DLL
  226. if ( cl_jiggle_bone_debug_yaw_constraints.GetBool() )
  227. {
  228. float dT = 0.01f;
  229. const float axisSize = 10.0f;
  230. if ( debugoverlay )
  231. {
  232. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + axisSize * limitLeft, 0, 255, 255, true, dT );
  233. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + axisSize * limitUp, 255, 255, 0, true, dT );
  234. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + axisSize * limitForward, 255, 0, 255, true, dT );
  235. }
  236. }
  237. #endif // CLIENT_DLL
  238. Vector limitAlong( DotProduct( limitLeft, along ),
  239. DotProduct( limitUp, along ),
  240. DotProduct( limitForward, along ) );
  241. // clip to limit plane
  242. data->tipPos = goalBasePosition + limitAlong.y * limitUp + limitAlong.z * limitForward;
  243. // removed friction and velocity clipping against constraint - was causing simulation blowups (MSB 12/9/2010)
  244. data->tipVel.Zero();
  245. // update along vectors for use by pitch constraint
  246. along = data->tipPos - goalBasePosition;
  247. localAlong.x = DotProduct( goalLeft, along );
  248. localAlong.y = DotProduct( goalUp, along );
  249. localAlong.z = DotProduct( goalForward, along );
  250. localVel.x = DotProduct( goalLeft, data->tipVel );
  251. localVel.y = DotProduct( goalUp, data->tipVel );
  252. localVel.z = DotProduct( goalForward, data->tipVel );
  253. }
  254. }
  255. if ( jiggleInfo->flags & JIGGLE_HAS_PITCH_CONSTRAINT )
  256. {
  257. // enforce pitch constraints in local YZ plane
  258. float pitchError = atan2( localAlong.y, localAlong.z );
  259. bool isAtLimit = false;
  260. float pitch = 0.0f;
  261. if ( pitchError < jiggleInfo->minPitch )
  262. {
  263. // at angular limit
  264. isAtLimit = true;
  265. pitch = jiggleInfo->minPitch;
  266. }
  267. else if ( pitchError > jiggleInfo->maxPitch )
  268. {
  269. // at angular limit
  270. isAtLimit = true;
  271. pitch = jiggleInfo->maxPitch;
  272. }
  273. if ( isAtLimit )
  274. {
  275. float sp, cp;
  276. SinCos( pitch, &sp, &cp );
  277. // pitch matrix
  278. matrix3x4_t pitchMatrix;
  279. pitchMatrix[0][0] = 1.0f;
  280. pitchMatrix[1][0] = 0;
  281. pitchMatrix[2][0] = 0;
  282. pitchMatrix[0][1] = 0;
  283. pitchMatrix[1][1] = cp;
  284. pitchMatrix[2][1] = -sp;
  285. pitchMatrix[0][2] = 0;
  286. pitchMatrix[1][2] = sp;
  287. pitchMatrix[2][2] = cp;
  288. pitchMatrix[0][3] = 0;
  289. pitchMatrix[1][3] = 0;
  290. pitchMatrix[2][3] = 0;
  291. // global coordinates of limit
  292. matrix3x4_t limitMatrix;
  293. ConcatTransforms( goalMX, pitchMatrix, limitMatrix );
  294. Vector limitLeft( limitMatrix.m_flMatVal[0][0],
  295. limitMatrix.m_flMatVal[1][0],
  296. limitMatrix.m_flMatVal[2][0] );
  297. Vector limitUp( limitMatrix.m_flMatVal[0][1],
  298. limitMatrix.m_flMatVal[1][1],
  299. limitMatrix.m_flMatVal[2][1] );
  300. Vector limitForward( limitMatrix.m_flMatVal[0][2],
  301. limitMatrix.m_flMatVal[1][2],
  302. limitMatrix.m_flMatVal[2][2] );
  303. #ifdef CLIENT_DLL
  304. if (cl_jiggle_bone_debug_pitch_constraints.GetBool())
  305. {
  306. float dT = 0.01f;
  307. const float axisSize = 10.0f;
  308. if ( debugoverlay )
  309. {
  310. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + axisSize * limitLeft, 0, 255, 255, true, dT );
  311. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + axisSize * limitUp, 255, 255, 0, true, dT );
  312. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + axisSize * limitForward, 255, 0, 255, true, dT );
  313. }
  314. }
  315. #endif // CLIENT_DLL
  316. Vector limitAlong( DotProduct( limitLeft, along ),
  317. DotProduct( limitUp, along ),
  318. DotProduct( limitForward, along ) );
  319. // clip to limit plane
  320. data->tipPos = goalBasePosition + limitAlong.x * limitLeft + limitAlong.z * limitForward;
  321. // removed friction and velocity clipping against constraint - was causing simulation blowups (MSB 12/9/2010)
  322. data->tipVel.Zero();
  323. }
  324. }
  325. }
  326. // needed for matrix assembly below
  327. Vector forward = data->tipPos - goalBasePosition;
  328. forward.NormalizeInPlace();
  329. if ( jiggleInfo->flags & JIGGLE_HAS_ANGLE_CONSTRAINT )
  330. {
  331. // enforce max angular error
  332. Vector error = goalTip - data->tipPos;
  333. float dot = DotProduct( forward, goalForward );
  334. float angleBetween = acos( dot );
  335. if ( dot < 0.0f )
  336. {
  337. angleBetween = 2.0f * M_PI - angleBetween;
  338. }
  339. if ( angleBetween > jiggleInfo->angleLimit )
  340. {
  341. // at angular limit
  342. float maxBetween = jiggleInfo->length * sin( jiggleInfo->angleLimit );
  343. Vector delta = goalTip - data->tipPos;
  344. delta.NormalizeInPlace();
  345. data->tipPos = goalTip - maxBetween * delta;
  346. forward = data->tipPos - goalBasePosition;
  347. forward.NormalizeInPlace();
  348. }
  349. }
  350. if ( jiggleInfo->flags & JIGGLE_HAS_LENGTH_CONSTRAINT )
  351. {
  352. // enforce spring length
  353. data->tipPos = goalBasePosition + jiggleInfo->length * forward;
  354. // zero velocity along forward bone axis
  355. data->tipVel -= DotProduct( data->tipVel, forward ) * forward;
  356. }
  357. //
  358. // Build bone matrix to align along current tip direction
  359. //
  360. Vector left = CrossProduct( goalUp, forward );
  361. left.NormalizeInPlace();
  362. if ( DotProduct( left, data->lastLeft ) < 0.0f )
  363. {
  364. // The bone has rotated so far its on the other side of the up vector
  365. // resulting in the cross product result flipping 180 degrees around the up
  366. // vector. Flip it back.
  367. left = -left;
  368. }
  369. data->lastLeft = left;
  370. #ifdef CLIENT_DLL
  371. if ( cl_jiggle_bone_debug.GetBool() )
  372. {
  373. if ( debugoverlay )
  374. {
  375. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + 10.0f * data->lastLeft, 255, 0, 255, true, 0.01f );
  376. }
  377. }
  378. #endif
  379. Vector up = CrossProduct( forward, left );
  380. boneMX[0][0] = left.x;
  381. boneMX[1][0] = left.y;
  382. boneMX[2][0] = left.z;
  383. boneMX[0][1] = up.x;
  384. boneMX[1][1] = up.y;
  385. boneMX[2][1] = up.z;
  386. boneMX[0][2] = forward.x;
  387. boneMX[1][2] = forward.y;
  388. boneMX[2][2] = forward.z;
  389. boneMX[0][3] = goalBasePosition.x;
  390. boneMX[1][3] = goalBasePosition.y;
  391. boneMX[2][3] = goalBasePosition.z;
  392. }
  393. //
  394. // Bone base flex
  395. //
  396. if ( jiggleInfo->flags & JIGGLE_HAS_BASE_SPRING )
  397. {
  398. // gravity
  399. data->baseAccel.z -= jiggleInfo->baseMass;
  400. // simple spring
  401. Vector error = goalBasePosition - data->basePos;
  402. data->baseAccel += jiggleInfo->baseStiffness * error - jiggleInfo->baseDamping * data->baseVel;
  403. data->baseVel += data->baseAccel * deltaT;
  404. data->basePos += data->baseVel * deltaT;
  405. // clear this timestep's accumulated accelerations
  406. data->baseAccel = vec3_origin;
  407. // constrain to limits
  408. error = data->basePos - goalBasePosition;
  409. Vector localError;
  410. localError.x = DotProduct( goalLeft, error );
  411. localError.y = DotProduct( goalUp, error );
  412. localError.z = DotProduct( goalForward, error );
  413. Vector localVel;
  414. localVel.x = DotProduct( goalLeft, data->baseVel );
  415. localVel.y = DotProduct( goalUp, data->baseVel );
  416. localVel.z = DotProduct( goalForward, data->baseVel );
  417. // horizontal constraint
  418. if ( localError.x < jiggleInfo->baseMinLeft )
  419. {
  420. localError.x = jiggleInfo->baseMinLeft;
  421. // friction
  422. data->baseAccel -= jiggleInfo->baseLeftFriction * (localVel.y * goalUp + localVel.z * goalForward);
  423. }
  424. else if ( localError.x > jiggleInfo->baseMaxLeft )
  425. {
  426. localError.x = jiggleInfo->baseMaxLeft;
  427. // friction
  428. data->baseAccel -= jiggleInfo->baseLeftFriction * (localVel.y * goalUp + localVel.z * goalForward);
  429. }
  430. if ( localError.y < jiggleInfo->baseMinUp )
  431. {
  432. localError.y = jiggleInfo->baseMinUp;
  433. // friction
  434. data->baseAccel -= jiggleInfo->baseUpFriction * (localVel.x * goalLeft + localVel.z * goalForward);
  435. }
  436. else if ( localError.y > jiggleInfo->baseMaxUp )
  437. {
  438. localError.y = jiggleInfo->baseMaxUp;
  439. // friction
  440. data->baseAccel -= jiggleInfo->baseUpFriction * (localVel.x * goalLeft + localVel.z * goalForward);
  441. }
  442. if ( localError.z < jiggleInfo->baseMinForward )
  443. {
  444. localError.z = jiggleInfo->baseMinForward;
  445. // friction
  446. data->baseAccel -= jiggleInfo->baseForwardFriction * (localVel.x * goalLeft + localVel.y * goalUp);
  447. }
  448. else if ( localError.z > jiggleInfo->baseMaxForward )
  449. {
  450. localError.z = jiggleInfo->baseMaxForward;
  451. // friction
  452. data->baseAccel -= jiggleInfo->baseForwardFriction * (localVel.x * goalLeft + localVel.y * goalUp);
  453. }
  454. data->basePos = goalBasePosition + localError.x * goalLeft + localError.y * goalUp + localError.z * goalForward;
  455. // fix up velocity
  456. data->baseVel = (data->basePos - data->baseLastPos) / deltaT;
  457. data->baseLastPos = data->basePos;
  458. if ( !( jiggleInfo->flags & ( JIGGLE_IS_FLEXIBLE | JIGGLE_IS_RIGID ) ) )
  459. {
  460. // no tip flex - use bone's goal orientation
  461. boneMX = goalMX;
  462. }
  463. // update bone position
  464. MatrixSetColumn( data->basePos, 3, boneMX );
  465. }
  466. else if ( jiggleInfo->flags & JIGGLE_IS_BOING )
  467. {
  468. // estimate velocity
  469. Vector vel = goalBasePosition - data->lastBoingPos;
  470. #ifdef CLIENT_DLL
  471. if ( cl_jiggle_bone_debug.GetBool() )
  472. {
  473. if ( debugoverlay )
  474. {
  475. debugoverlay->AddLineOverlay( data->lastBoingPos, goalBasePosition, 0, 128, ( gpGlobals->framecount & 0x1 ) ? 0 : 200, true, 999.9f );
  476. }
  477. }
  478. #endif
  479. data->lastBoingPos = goalBasePosition;
  480. float speed = vel.NormalizeInPlace();
  481. if ( speed < 0.00001f )
  482. {
  483. vel = Vector( 0, 0, 1.0f );
  484. speed = 0.0f;
  485. }
  486. else
  487. {
  488. speed /= deltaT;
  489. }
  490. data->boingTime += deltaT;
  491. // if velocity changed a lot, we impacted and should *boing*
  492. const float minSpeed = 5.0f; // 15.0f;
  493. const float minReBoingTime = 0.5f;
  494. if ( ( speed > minSpeed || data->boingSpeed > minSpeed ) && data->boingTime > minReBoingTime )
  495. {
  496. if ( fabs( data->boingSpeed - speed ) > jiggleInfo->boingImpactSpeed || DotProduct( vel, data->boingVelDir ) < jiggleInfo->boingImpactAngle )
  497. {
  498. data->boingTime = 0.0f;
  499. data->boingDir = -vel;
  500. #ifdef CLIENT_DLL
  501. if ( cl_jiggle_bone_debug.GetBool() )
  502. {
  503. if ( debugoverlay )
  504. {
  505. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + 5.0f * data->boingDir, 255, 255, 0, true, 999.9f );
  506. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + Vector( 0.1, 0, 0 ), 128, 128, 0, true, 999.9f );
  507. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + Vector( 0, 0.1, 0 ), 128, 128, 0, true, 999.9f );
  508. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + Vector( 0, 0, 0.1 ), 128, 128, 0, true, 999.9f );
  509. }
  510. }
  511. #endif
  512. }
  513. }
  514. data->boingVelDir = vel;
  515. data->boingSpeed = speed;
  516. float damping = 1.0f - ( jiggleInfo->boingDampingRate * data->boingTime );
  517. if ( damping < 0.01f )
  518. {
  519. // boing has entirely damped out
  520. boneMX = goalMX;
  521. }
  522. else
  523. {
  524. damping *= damping;
  525. damping *= damping;
  526. float flex = jiggleInfo->boingAmplitude * cos( jiggleInfo->boingFrequency * data->boingTime ) * damping;
  527. float squash = 1.0f + flex;
  528. float stretch = 1.0f - flex;
  529. boneMX[0][0] = goalLeft.x;
  530. boneMX[1][0] = goalLeft.y;
  531. boneMX[2][0] = goalLeft.z;
  532. boneMX[0][1] = goalUp.x;
  533. boneMX[1][1] = goalUp.y;
  534. boneMX[2][1] = goalUp.z;
  535. boneMX[0][2] = goalForward.x;
  536. boneMX[1][2] = goalForward.y;
  537. boneMX[2][2] = goalForward.z;
  538. boneMX[0][3] = 0.0f;
  539. boneMX[1][3] = 0.0f;
  540. boneMX[2][3] = 0.0f;
  541. // build transform into "boing space", where Z is along primary boing axis
  542. Vector boingSide;
  543. if ( fabs( data->boingDir.x ) < 0.9f )
  544. {
  545. boingSide = CrossProduct( data->boingDir, Vector( 1.0f, 0, 0 ) );
  546. }
  547. else
  548. {
  549. boingSide = CrossProduct( data->boingDir, Vector( 0, 0, 1.0f ) );
  550. }
  551. boingSide.NormalizeInPlace();
  552. Vector boingOtherSide = CrossProduct( data->boingDir, boingSide );
  553. matrix3x4_t xfrmToBoingCoordsMX;
  554. xfrmToBoingCoordsMX[0][0] = boingSide.x;
  555. xfrmToBoingCoordsMX[0][1] = boingSide.y;
  556. xfrmToBoingCoordsMX[0][2] = boingSide.z;
  557. xfrmToBoingCoordsMX[1][0] = boingOtherSide.x;
  558. xfrmToBoingCoordsMX[1][1] = boingOtherSide.y;
  559. xfrmToBoingCoordsMX[1][2] = boingOtherSide.z;
  560. xfrmToBoingCoordsMX[2][0] = data->boingDir.x;
  561. xfrmToBoingCoordsMX[2][1] = data->boingDir.y;
  562. xfrmToBoingCoordsMX[2][2] = data->boingDir.z;
  563. xfrmToBoingCoordsMX[0][3] = 0.0f;
  564. xfrmToBoingCoordsMX[1][3] = 0.0f;
  565. xfrmToBoingCoordsMX[2][3] = 0.0f;
  566. // build squash and stretch transform in "boing space"
  567. matrix3x4_t boingMX;
  568. boingMX[0][0] = squash;
  569. boingMX[1][0] = 0.0f;
  570. boingMX[2][0] = 0.0f;
  571. boingMX[0][1] = 0.0f;
  572. boingMX[1][1] = squash;
  573. boingMX[2][1] = 0.0f;
  574. boingMX[0][2] = 0.0f;
  575. boingMX[1][2] = 0.0f;
  576. boingMX[2][2] = stretch;
  577. boingMX[0][3] = 0.0f;
  578. boingMX[1][3] = 0.0f;
  579. boingMX[2][3] = 0.0f;
  580. // transform back from boing space (inverse is transpose since orthogonal)
  581. matrix3x4_t xfrmFromBoingCoordsMX;
  582. xfrmFromBoingCoordsMX[0][0] = xfrmToBoingCoordsMX[0][0];
  583. xfrmFromBoingCoordsMX[1][0] = xfrmToBoingCoordsMX[0][1];
  584. xfrmFromBoingCoordsMX[2][0] = xfrmToBoingCoordsMX[0][2];
  585. xfrmFromBoingCoordsMX[0][1] = xfrmToBoingCoordsMX[1][0];
  586. xfrmFromBoingCoordsMX[1][1] = xfrmToBoingCoordsMX[1][1];
  587. xfrmFromBoingCoordsMX[2][1] = xfrmToBoingCoordsMX[1][2];
  588. xfrmFromBoingCoordsMX[0][2] = xfrmToBoingCoordsMX[2][0];
  589. xfrmFromBoingCoordsMX[1][2] = xfrmToBoingCoordsMX[2][1];
  590. xfrmFromBoingCoordsMX[2][2] = xfrmToBoingCoordsMX[2][2];
  591. xfrmFromBoingCoordsMX[0][3] = 0.0f;
  592. xfrmFromBoingCoordsMX[1][3] = 0.0f;
  593. xfrmFromBoingCoordsMX[2][3] = 0.0f;
  594. // put it all together
  595. matrix3x4_t xfrmMX;
  596. MatrixMultiply( xfrmToBoingCoordsMX, boingMX, xfrmMX );
  597. MatrixMultiply( xfrmMX, xfrmFromBoingCoordsMX, xfrmMX );
  598. MatrixMultiply( boneMX, xfrmMX, boneMX );
  599. #ifdef CLIENT_DLL
  600. if ( cl_jiggle_bone_debug.GetBool() )
  601. {
  602. float dT = 0.01f;
  603. if ( debugoverlay )
  604. {
  605. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + 50.0f * data->boingDir, 255, 255, 0, true, dT );
  606. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + 50.0f * boingSide, 255, 0, 255, true, dT );
  607. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + 50.0f * boingOtherSide, 0, 255, 255, true, dT );
  608. }
  609. }
  610. #endif
  611. boneMX[0][3] = goalBasePosition.x;
  612. boneMX[1][3] = goalBasePosition.y;
  613. boneMX[2][3] = goalBasePosition.z;
  614. }
  615. }
  616. else if ( !( jiggleInfo->flags & ( JIGGLE_IS_FLEXIBLE | JIGGLE_IS_RIGID ) ) )
  617. {
  618. // no flex at all - just use goal matrix
  619. boneMX = goalMX;
  620. }
  621. #ifdef CLIENT_DLL
  622. // debug display for client only so server doesn't try to also draw it
  623. if ( cl_jiggle_bone_debug.GetBool() )
  624. {
  625. float dT = 0.01f;
  626. const float axisSize = 5.0f;
  627. if ( debugoverlay )
  628. {
  629. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + axisSize * goalLeft, 255, 0, 0, true, dT );
  630. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + axisSize * goalUp, 0, 255, 0, true, dT );
  631. debugoverlay->AddLineOverlay( goalBasePosition, goalBasePosition + axisSize * goalForward, 0, 0, 255, true, dT );
  632. }
  633. if ( cl_jiggle_bone_debug.GetInt() > 1 )
  634. {
  635. DevMsg( "Jiggle bone #%d, basePos( %3.2f, %3.2f, %3.2f ), tipPos( %3.2f, %3.2f, %3.2f ), left( %3.2f, %3.2f, %3.2f ), up( %3.2f, %3.2f, %3.2f ), forward( %3.2f, %3.2f, %3.2f )\n",
  636. data->bone,
  637. goalBasePosition.x, goalBasePosition.y, goalBasePosition.z,
  638. data->tipPos.x, data->tipPos.y, data->tipPos.z,
  639. goalLeft.x, goalLeft.y, goalLeft.z,
  640. goalUp.x, goalUp.y, goalUp.z,
  641. goalForward.x, goalForward.y, goalForward.z );
  642. }
  643. const float sz = 1.0f;
  644. if ( jiggleInfo->flags & ( JIGGLE_IS_FLEXIBLE | JIGGLE_IS_RIGID ) )
  645. {
  646. if ( debugoverlay )
  647. {
  648. debugoverlay->AddLineOverlay( goalBasePosition,
  649. data->tipPos, 255, 255, 0, true, dT );
  650. debugoverlay->AddLineOverlay( data->tipPos + Vector( -sz, 0, 0 ),
  651. data->tipPos + Vector( sz, 0, 0 ), 0, 255, 255, true, dT );
  652. debugoverlay->AddLineOverlay( data->tipPos + Vector( 0, -sz, 0 ),
  653. data->tipPos + Vector( 0, sz, 0 ), 0, 255, 255, true, dT );
  654. debugoverlay->AddLineOverlay( data->tipPos + Vector( 0, 0, -sz ),
  655. data->tipPos + Vector( 0, 0, sz ), 0, 255, 255, true, dT );
  656. }
  657. }
  658. if ( jiggleInfo->flags & JIGGLE_HAS_BASE_SPRING )
  659. {
  660. if ( debugoverlay )
  661. {
  662. debugoverlay->AddLineOverlay( data->basePos + Vector( -sz, 0, 0 ),
  663. data->basePos + Vector( sz, 0, 0 ), 255, 0, 255, true, dT );
  664. debugoverlay->AddLineOverlay( data->basePos + Vector( 0, -sz, 0 ),
  665. data->basePos + Vector( 0, sz, 0 ), 255, 0, 255, true, dT );
  666. debugoverlay->AddLineOverlay( data->basePos + Vector( 0, 0, -sz ),
  667. data->basePos + Vector( 0, 0, sz ), 255, 0, 255, true, dT );
  668. }
  669. }
  670. if ( jiggleInfo->flags & JIGGLE_IS_BOING )
  671. {
  672. if ( cl_jiggle_bone_debug.GetInt() > 2 )
  673. {
  674. DevMsg( " boingSpeed = %3.2f, boingVelDir( %3.2f, %3.2f, %3.2f )\n", data->boingVelDir.Length() / deltaT, data->boingVelDir.x, data->boingVelDir.y, data->boingVelDir.z );
  675. }
  676. }
  677. }
  678. #endif // CLIENT_DLL
  679. }