PhysicsConstraint.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated July 28, 2023. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2023, Esoteric Software LLC
  6. *
  7. * Integration of the Spine Runtimes into software or otherwise creating
  8. * derivative works of the Spine Runtimes is permitted under the terms and
  9. * conditions of Section 2 of the Spine Editor License Agreement:
  10. * http://esotericsoftware.com/spine-editor-license
  11. *
  12. * Otherwise, it is permitted to integrate the Spine Runtimes into software or
  13. * otherwise create derivative works of the Spine Runtimes (collectively,
  14. * "Products"), provided that each user of the Products must obtain their own
  15. * Spine Editor license and redistribution of the Products in any form must
  16. * include this license and copyright notice.
  17. *
  18. * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
  27. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. using System;
  30. namespace Spine {
  31. using Physics = Skeleton.Physics;
  32. /// <summary>
  33. /// Stores the current pose for a physics constraint. A physics constraint applies physics to bones.
  34. /// <para>
  35. /// See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide.</para>
  36. /// </summary>
  37. public class PhysicsConstraint : IUpdatable {
  38. internal readonly PhysicsConstraintData data;
  39. public Bone bone;
  40. internal float inertia, strength, damping, massInverse, wind, gravity, mix;
  41. bool reset = true;
  42. float ux, uy, cx, cy, tx, ty;
  43. float xOffset, xVelocity;
  44. float yOffset, yVelocity;
  45. float rotateOffset, rotateVelocity;
  46. float scaleOffset, scaleVelocity;
  47. internal bool active;
  48. readonly Skeleton skeleton;
  49. float remaining, lastTime;
  50. public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) {
  51. if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
  52. if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
  53. this.data = data;
  54. this.skeleton = skeleton;
  55. bone = skeleton.bones.Items[data.bone.index];
  56. inertia = data.inertia;
  57. strength = data.strength;
  58. damping = data.damping;
  59. massInverse = data.massInverse;
  60. wind = data.wind;
  61. gravity = data.gravity;
  62. mix = data.mix;
  63. }
  64. /// <summary>Copy constructor.</summary>
  65. public PhysicsConstraint (PhysicsConstraint constraint, Skeleton skeleton)
  66. : this(constraint.data, skeleton) {
  67. inertia = constraint.inertia;
  68. strength = constraint.strength;
  69. damping = constraint.damping;
  70. massInverse = constraint.massInverse;
  71. wind = constraint.wind;
  72. gravity = constraint.gravity;
  73. mix = constraint.mix;
  74. }
  75. public void Reset () {
  76. remaining = 0;
  77. lastTime = skeleton.time;
  78. reset = true;
  79. xOffset = 0;
  80. xVelocity = 0;
  81. yOffset = 0;
  82. yVelocity = 0;
  83. rotateOffset = 0;
  84. rotateVelocity = 0;
  85. scaleOffset = 0;
  86. scaleVelocity = 0;
  87. }
  88. public void SetToSetupPose () {
  89. PhysicsConstraintData data = this.data;
  90. inertia = data.inertia;
  91. strength = data.strength;
  92. damping = data.damping;
  93. massInverse = data.massInverse;
  94. wind = data.wind;
  95. gravity = data.gravity;
  96. mix = data.mix;
  97. }
  98. /// <summary>
  99. /// Translates the physics constraint so next <see cref="Update(Physics)"/> forces are applied as if the bone moved an additional
  100. /// amount in world space.
  101. /// </summary>
  102. public void Translate (float x, float y) {
  103. ux -= x;
  104. uy -= y;
  105. cx -= x;
  106. cy -= y;
  107. }
  108. /// <summary>
  109. /// Rotates the physics constraint so next <see cref="Update(Physics)"/> forces are applied as if the bone rotated around the
  110. /// specified point in world space.
  111. /// </summary>
  112. public void Rotate (float x, float y, float degrees) {
  113. float r = degrees * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
  114. float dx = cx - x, dy = cy - y;
  115. Translate(dx * cos - dy * sin - dx, dx * sin + dy * cos - dy);
  116. }
  117. /// <summary>Applies the constraint to the constrained bones.</summary>
  118. public void Update (Physics physics) {
  119. float mix = this.mix;
  120. if (mix == 0) return;
  121. bool x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0;
  122. Bone bone = this.bone;
  123. float l = bone.data.length;
  124. switch (physics) {
  125. case Physics.None:
  126. return;
  127. case Physics.Reset:
  128. Reset();
  129. goto case Physics.Update; // Fall through.
  130. case Physics.Update:
  131. Skeleton skeleton = this.skeleton;
  132. float delta = Math.Max(skeleton.time - lastTime, 0);
  133. remaining += delta;
  134. lastTime = skeleton.time;
  135. float bx = bone.worldX, by = bone.worldY;
  136. if (reset) {
  137. reset = false;
  138. ux = bx;
  139. uy = by;
  140. } else {
  141. float a = remaining, i = inertia, t = data.step, f = skeleton.data.referenceScale, d = -1;
  142. float qx = data.limit * delta, qy = qx * Math.Abs(skeleton.ScaleY);
  143. qx *= Math.Abs(skeleton.ScaleX);
  144. if (x || y) {
  145. if (x) {
  146. float u = (ux - bx) * i;
  147. xOffset += u > qx ? qx : u < -qx ? -qx : u;
  148. ux = bx;
  149. }
  150. if (y) {
  151. float u = (uy - by) * i;
  152. yOffset += u > qy ? qy : u < -qy ? -qy : u;
  153. uy = by;
  154. }
  155. if (a >= t) {
  156. d = (float)Math.Pow(damping, 60 * t);
  157. float m = massInverse * t, e = strength, w = wind * f, g = (Bone.yDown ? -gravity : gravity) * f;
  158. do {
  159. if (x) {
  160. xVelocity += (w - xOffset * e) * m;
  161. xOffset += xVelocity * t;
  162. xVelocity *= d;
  163. }
  164. if (y) {
  165. yVelocity -= (g + yOffset * e) * m;
  166. yOffset += yVelocity * t;
  167. yVelocity *= d;
  168. }
  169. a -= t;
  170. } while (a >= t);
  171. }
  172. if (x) bone.worldX += xOffset * mix * data.x;
  173. if (y) bone.worldY += yOffset * mix * data.y;
  174. }
  175. if (rotateOrShearX || scaleX) {
  176. float ca = (float)Math.Atan2(bone.c, bone.a), c, s, mr = 0;
  177. float dx = cx - bone.worldX, dy = cy - bone.worldY;
  178. if (dx > qx)
  179. dx = qx;
  180. else if (dx < -qx)
  181. dx = -qx;
  182. if (dy > qy)
  183. dy = qy;
  184. else if (dy < -qy)
  185. dy = -qy;
  186. if (rotateOrShearX) {
  187. mr = (data.rotate + data.shearX) * mix;
  188. float r = (float)Math.Atan2(dy + ty, dx + tx) - ca - rotateOffset * mr;
  189. rotateOffset += (r - (float)Math.Ceiling(r * MathUtils.InvPI2 - 0.5f) * MathUtils.PI2) * i;
  190. r = rotateOffset * mr + ca;
  191. c = (float)Math.Cos(r);
  192. s = (float)Math.Sin(r);
  193. if (scaleX) {
  194. r = l * bone.WorldScaleX;
  195. if (r > 0) scaleOffset += (dx * c + dy * s) * i / r;
  196. }
  197. } else {
  198. c = (float)Math.Cos(ca);
  199. s = (float)Math.Sin(ca);
  200. float r = l * bone.WorldScaleX;
  201. if (r > 0) scaleOffset += (dx * c + dy * s) * i / r;
  202. }
  203. a = remaining;
  204. if (a >= t) {
  205. if (d == -1) d = (float)Math.Pow(damping, 60 * t);
  206. float m = massInverse * t, e = strength, w = wind, g = (Bone.yDown ? -gravity : gravity), h = l / f;
  207. while (true) {
  208. a -= t;
  209. if (scaleX) {
  210. scaleVelocity += (w * c - g * s - scaleOffset * e) * m;
  211. scaleOffset += scaleVelocity * t;
  212. scaleVelocity *= d;
  213. }
  214. if (rotateOrShearX) {
  215. rotateVelocity -= ((w * s + g * c) * h + rotateOffset * e) * m;
  216. rotateOffset += rotateVelocity * t;
  217. rotateVelocity *= d;
  218. if (a < t) break;
  219. float r = rotateOffset * mr + ca;
  220. c = (float)Math.Cos(r);
  221. s = (float)Math.Sin(r);
  222. } else if (a < t) //
  223. break;
  224. }
  225. }
  226. }
  227. remaining = a;
  228. }
  229. cx = bone.worldX;
  230. cy = bone.worldY;
  231. break;
  232. case Physics.Pose:
  233. if (x) bone.worldX += xOffset * mix * data.x;
  234. if (y) bone.worldY += yOffset * mix * data.y;
  235. break;
  236. }
  237. if (rotateOrShearX) {
  238. float o = rotateOffset * mix, s, c, a;
  239. if (data.shearX > 0) {
  240. float r = 0;
  241. if (data.rotate > 0) {
  242. r = o * data.rotate;
  243. s = (float)Math.Sin(r);
  244. c = (float)Math.Cos(r);
  245. a = bone.b;
  246. bone.b = c * a - s * bone.d;
  247. bone.d = s * a + c * bone.d;
  248. }
  249. r += o * data.shearX;
  250. s = (float)Math.Sin(r);
  251. c = (float)Math.Cos(r);
  252. a = bone.a;
  253. bone.a = c * a - s * bone.c;
  254. bone.c = s * a + c * bone.c;
  255. } else {
  256. o *= data.rotate;
  257. s = (float)Math.Sin(o);
  258. c = (float)Math.Cos(o);
  259. a = bone.a;
  260. bone.a = c * a - s * bone.c;
  261. bone.c = s * a + c * bone.c;
  262. a = bone.b;
  263. bone.b = c * a - s * bone.d;
  264. bone.d = s * a + c * bone.d;
  265. }
  266. }
  267. if (scaleX) {
  268. float s = 1 + scaleOffset * mix * data.scaleX;
  269. bone.a *= s;
  270. bone.c *= s;
  271. }
  272. if (physics != Physics.Pose) {
  273. tx = l * bone.a;
  274. ty = l * bone.c;
  275. }
  276. bone.UpdateAppliedTransform();
  277. }
  278. /// <summary>The bone constrained by this physics constraint.</summary>
  279. public Bone Bone { get { return bone; } set { bone = value; } }
  280. public float Inertia { get { return inertia; } set { inertia = value; } }
  281. public float Strength { get { return strength; } set { strength = value; } }
  282. public float Damping { get { return damping; } set { damping = value; } }
  283. public float MassInverse { get { return massInverse; } set { massInverse = value; } }
  284. public float Wind { get { return wind; } set { wind = value; } }
  285. public float Gravity { get { return gravity; } set { gravity = value; } }
  286. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained poses.</summary>
  287. public float Mix { get { return mix; } set { mix = value; } }
  288. public bool Active { get { return active; } }
  289. /// <summary>The physics constraint's setup pose data.</summary>
  290. public PhysicsConstraintData getData () {
  291. return data;
  292. }
  293. /// <summary>The physics constraint's setup pose data.</summary>
  294. public PhysicsConstraintData Data { get { return data; } }
  295. override public string ToString () {
  296. return data.name;
  297. }
  298. }
  299. }