TransformConstraint.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated January 1, 2020. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2020, 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
  13. * or 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
  27. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. using System;
  30. namespace Spine {
  31. /// <summary>
  32. /// <para>
  33. /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained
  34. /// bones to match that of the target bone.</para>
  35. /// <para>
  36. /// See <a href="http://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide.</para>
  37. /// </summary>
  38. public class TransformConstraint : IUpdatable {
  39. internal TransformConstraintData data;
  40. internal ExposedList<Bone> bones;
  41. internal Bone target;
  42. internal float rotateMix, translateMix, scaleMix, shearMix;
  43. internal bool active;
  44. public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
  45. if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
  46. if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
  47. this.data = data;
  48. rotateMix = data.rotateMix;
  49. translateMix = data.translateMix;
  50. scaleMix = data.scaleMix;
  51. shearMix = data.shearMix;
  52. bones = new ExposedList<Bone>();
  53. foreach (BoneData boneData in data.bones)
  54. bones.Add (skeleton.FindBone(boneData.name));
  55. target = skeleton.FindBone(data.target.name);
  56. }
  57. /// <summary>Copy constructor.</summary>
  58. public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) {
  59. if (constraint == null) throw new ArgumentNullException("constraint cannot be null.");
  60. if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
  61. data = constraint.data;
  62. bones = new ExposedList<Bone>(constraint.Bones.Count);
  63. foreach (Bone bone in constraint.Bones)
  64. bones.Add(skeleton.Bones.Items[bone.data.index]);
  65. target = skeleton.Bones.Items[constraint.target.data.index];
  66. rotateMix = constraint.rotateMix;
  67. translateMix = constraint.translateMix;
  68. scaleMix = constraint.scaleMix;
  69. shearMix = constraint.shearMix;
  70. }
  71. /// <summary>Applies the constraint to the constrained bones.</summary>
  72. public void Apply () {
  73. Update();
  74. }
  75. public void Update () {
  76. if (data.local) {
  77. if (data.relative)
  78. ApplyRelativeLocal();
  79. else
  80. ApplyAbsoluteLocal();
  81. } else {
  82. if (data.relative)
  83. ApplyRelativeWorld();
  84. else
  85. ApplyAbsoluteWorld();
  86. }
  87. }
  88. void ApplyAbsoluteWorld () {
  89. float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
  90. Bone target = this.target;
  91. float ta = target.a, tb = target.b, tc = target.c, td = target.d;
  92. float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
  93. float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
  94. var bones = this.bones;
  95. for (int i = 0, n = bones.Count; i < n; i++) {
  96. Bone bone = bones.Items[i];
  97. bool modified = false;
  98. if (rotateMix != 0) {
  99. float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
  100. float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation;
  101. if (r > MathUtils.PI)
  102. r -= MathUtils.PI2;
  103. else if (r < -MathUtils.PI) r += MathUtils.PI2;
  104. r *= rotateMix;
  105. float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
  106. bone.a = cos * a - sin * c;
  107. bone.b = cos * b - sin * d;
  108. bone.c = sin * a + cos * c;
  109. bone.d = sin * b + cos * d;
  110. modified = true;
  111. }
  112. if (translateMix != 0) {
  113. float tx, ty; //Vector2 temp = this.temp;
  114. target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
  115. bone.worldX += (tx - bone.worldX) * translateMix;
  116. bone.worldY += (ty - bone.worldY) * translateMix;
  117. modified = true;
  118. }
  119. if (scaleMix > 0) {
  120. float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
  121. if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s;
  122. bone.a *= s;
  123. bone.c *= s;
  124. s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
  125. if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s;
  126. bone.b *= s;
  127. bone.d *= s;
  128. modified = true;
  129. }
  130. if (shearMix > 0) {
  131. float b = bone.b, d = bone.d;
  132. float by = MathUtils.Atan2(d, b);
  133. float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a));
  134. if (r > MathUtils.PI)
  135. r -= MathUtils.PI2;
  136. else if (r < -MathUtils.PI) r += MathUtils.PI2;
  137. r = by + (r + offsetShearY) * shearMix;
  138. float s = (float)Math.Sqrt(b * b + d * d);
  139. bone.b = MathUtils.Cos(r) * s;
  140. bone.d = MathUtils.Sin(r) * s;
  141. modified = true;
  142. }
  143. if (modified) bone.appliedValid = false;
  144. }
  145. }
  146. void ApplyRelativeWorld () {
  147. float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
  148. Bone target = this.target;
  149. float ta = target.a, tb = target.b, tc = target.c, td = target.d;
  150. float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
  151. float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
  152. var bones = this.bones;
  153. for (int i = 0, n = bones.Count; i < n; i++) {
  154. Bone bone = bones.Items[i];
  155. bool modified = false;
  156. if (rotateMix != 0) {
  157. float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
  158. float r = MathUtils.Atan2(tc, ta) + offsetRotation;
  159. if (r > MathUtils.PI)
  160. r -= MathUtils.PI2;
  161. else if (r < -MathUtils.PI) r += MathUtils.PI2;
  162. r *= rotateMix;
  163. float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
  164. bone.a = cos * a - sin * c;
  165. bone.b = cos * b - sin * d;
  166. bone.c = sin * a + cos * c;
  167. bone.d = sin * b + cos * d;
  168. modified = true;
  169. }
  170. if (translateMix != 0) {
  171. float tx, ty; //Vector2 temp = this.temp;
  172. target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
  173. bone.worldX += tx * translateMix;
  174. bone.worldY += ty * translateMix;
  175. modified = true;
  176. }
  177. if (scaleMix > 0) {
  178. float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1;
  179. bone.a *= s;
  180. bone.c *= s;
  181. s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1;
  182. bone.b *= s;
  183. bone.d *= s;
  184. modified = true;
  185. }
  186. if (shearMix > 0) {
  187. float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta);
  188. if (r > MathUtils.PI)
  189. r -= MathUtils.PI2;
  190. else if (r < -MathUtils.PI) r += MathUtils.PI2;
  191. float b = bone.b, d = bone.d;
  192. r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix;
  193. float s = (float)Math.Sqrt(b * b + d * d);
  194. bone.b = MathUtils.Cos(r) * s;
  195. bone.d = MathUtils.Sin(r) * s;
  196. modified = true;
  197. }
  198. if (modified) bone.appliedValid = false;
  199. }
  200. }
  201. void ApplyAbsoluteLocal () {
  202. float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
  203. Bone target = this.target;
  204. if (!target.appliedValid) target.UpdateAppliedTransform();
  205. var bonesItems = this.bones.Items;
  206. for (int i = 0, n = this.bones.Count; i < n; i++) {
  207. Bone bone = bonesItems[i];
  208. if (!bone.appliedValid) bone.UpdateAppliedTransform();
  209. float rotation = bone.arotation;
  210. if (rotateMix != 0) {
  211. float r = target.arotation - rotation + data.offsetRotation;
  212. r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
  213. rotation += r * rotateMix;
  214. }
  215. float x = bone.ax, y = bone.ay;
  216. if (translateMix != 0) {
  217. x += (target.ax - x + data.offsetX) * translateMix;
  218. y += (target.ay - y + data.offsetY) * translateMix;
  219. }
  220. float scaleX = bone.ascaleX, scaleY = bone.ascaleY;
  221. if (scaleMix != 0) {
  222. if (scaleX != 0) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX;
  223. if (scaleY != 0) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY;
  224. }
  225. float shearY = bone.ashearY;
  226. if (shearMix != 0) {
  227. float r = target.ashearY - shearY + data.offsetShearY;
  228. r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
  229. shearY += r * shearMix;
  230. }
  231. bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
  232. }
  233. }
  234. void ApplyRelativeLocal () {
  235. float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
  236. Bone target = this.target;
  237. if (!target.appliedValid) target.UpdateAppliedTransform();
  238. var bonesItems = this.bones.Items;
  239. for (int i = 0, n = this.bones.Count; i < n; i++) {
  240. Bone bone = bonesItems[i];
  241. if (!bone.appliedValid) bone.UpdateAppliedTransform();
  242. float rotation = bone.arotation;
  243. if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix;
  244. float x = bone.ax, y = bone.ay;
  245. if (translateMix != 0) {
  246. x += (target.ax + data.offsetX) * translateMix;
  247. y += (target.ay + data.offsetY) * translateMix;
  248. }
  249. float scaleX = bone.ascaleX, scaleY = bone.ascaleY;
  250. if (scaleMix != 0) {
  251. scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1;
  252. scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1;
  253. }
  254. float shearY = bone.ashearY;
  255. if (shearMix != 0) shearY += (target.ashearY + data.offsetShearY) * shearMix;
  256. bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
  257. }
  258. }
  259. /// <summary>The bones that will be modified by this transform constraint.</summary>
  260. public ExposedList<Bone> Bones { get { return bones; } }
  261. /// <summary>The target bone whose world transform will be copied to the constrained bones.</summary>
  262. public Bone Target { get { return target; } set { target = value; } }
  263. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotations.</summary>
  264. public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
  265. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translations.</summary>
  266. public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
  267. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scales.</summary>
  268. public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
  269. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scales.</summary>
  270. public float ShearMix { get { return shearMix; } set { shearMix = value; } }
  271. public bool Active { get { return active; } }
  272. /// <summary>The transform constraint's setup pose data.</summary>
  273. public TransformConstraintData Data { get { return data; } }
  274. override public string ToString () {
  275. return data.name;
  276. }
  277. }
  278. }