TransformConstraint.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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 mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY;
  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. mixRotate = data.mixRotate;
  49. mixX = data.mixX;
  50. mixY = data.mixY;
  51. mixScaleX = data.mixScaleX;
  52. mixScaleY = data.mixScaleY;
  53. mixShearY = data.mixShearY;
  54. bones = new ExposedList<Bone>();
  55. foreach (BoneData boneData in data.bones)
  56. bones.Add(skeleton.FindBone(boneData.name));
  57. target = skeleton.FindBone(data.target.name);
  58. }
  59. /// <summary>Copy constructor.</summary>
  60. public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) {
  61. if (constraint == null) throw new ArgumentNullException("constraint cannot be null.");
  62. if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
  63. data = constraint.data;
  64. bones = new ExposedList<Bone>(constraint.Bones.Count);
  65. foreach (Bone bone in constraint.Bones)
  66. bones.Add(skeleton.Bones.Items[bone.data.index]);
  67. target = skeleton.Bones.Items[constraint.target.data.index];
  68. mixRotate = constraint.mixRotate;
  69. mixX = constraint.mixX;
  70. mixY = constraint.mixY;
  71. mixScaleX = constraint.mixScaleX;
  72. mixScaleY = constraint.mixScaleY;
  73. mixShearY = constraint.mixShearY;
  74. }
  75. public void Update () {
  76. if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleX == 0 && mixShearY == 0) return;
  77. if (data.local) {
  78. if (data.relative)
  79. ApplyRelativeLocal();
  80. else
  81. ApplyAbsoluteLocal();
  82. } else {
  83. if (data.relative)
  84. ApplyRelativeWorld();
  85. else
  86. ApplyAbsoluteWorld();
  87. }
  88. }
  89. void ApplyAbsoluteWorld () {
  90. float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
  91. mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
  92. bool translate = mixX != 0 || mixY != 0;
  93. Bone target = this.target;
  94. float ta = target.a, tb = target.b, tc = target.c, td = target.d;
  95. float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
  96. float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
  97. var bones = this.bones.Items;
  98. for (int i = 0, n = this.bones.Count; i < n; i++) {
  99. Bone bone = bones[i];
  100. if (mixRotate != 0) {
  101. float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
  102. float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation;
  103. if (r > MathUtils.PI)
  104. r -= MathUtils.PI2;
  105. else if (r < -MathUtils.PI) //
  106. r += MathUtils.PI2;
  107. r *= mixRotate;
  108. float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
  109. bone.a = cos * a - sin * c;
  110. bone.b = cos * b - sin * d;
  111. bone.c = sin * a + cos * c;
  112. bone.d = sin * b + cos * d;
  113. }
  114. if (translate) {
  115. float tx, ty; //Vector2 temp = this.temp;
  116. target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
  117. bone.worldX += (tx - bone.worldX) * mixX;
  118. bone.worldY += (ty - bone.worldY) * mixY;
  119. }
  120. if (mixScaleX != 0) {
  121. float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
  122. if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * mixScaleX) / s;
  123. bone.a *= s;
  124. bone.c *= s;
  125. }
  126. if (mixScaleY != 0) {
  127. float s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
  128. if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * mixScaleY) / s;
  129. bone.b *= s;
  130. bone.d *= s;
  131. }
  132. if (mixShearY > 0) {
  133. float b = bone.b, d = bone.d;
  134. float by = MathUtils.Atan2(d, b);
  135. float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a));
  136. if (r > MathUtils.PI)
  137. r -= MathUtils.PI2;
  138. else if (r < -MathUtils.PI) //
  139. r += MathUtils.PI2;
  140. r = by + (r + offsetShearY) * mixShearY;
  141. float s = (float)Math.Sqrt(b * b + d * d);
  142. bone.b = MathUtils.Cos(r) * s;
  143. bone.d = MathUtils.Sin(r) * s;
  144. }
  145. bone.UpdateAppliedTransform();
  146. }
  147. }
  148. void ApplyRelativeWorld () {
  149. float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
  150. mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
  151. bool translate = mixX != 0 || mixY != 0;
  152. Bone target = this.target;
  153. float ta = target.a, tb = target.b, tc = target.c, td = target.d;
  154. float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
  155. float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
  156. var bones = this.bones.Items;
  157. for (int i = 0, n = this.bones.Count; i < n; i++) {
  158. Bone bone = bones[i];
  159. if (mixRotate != 0) {
  160. float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
  161. float r = MathUtils.Atan2(tc, ta) + offsetRotation;
  162. if (r > MathUtils.PI)
  163. r -= MathUtils.PI2;
  164. else if (r < -MathUtils.PI) //
  165. r += MathUtils.PI2;
  166. r *= mixRotate;
  167. float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
  168. bone.a = cos * a - sin * c;
  169. bone.b = cos * b - sin * d;
  170. bone.c = sin * a + cos * c;
  171. bone.d = sin * b + cos * d;
  172. }
  173. if (translate) {
  174. float tx, ty; //Vector2 temp = this.temp;
  175. target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
  176. bone.worldX += tx * mixX;
  177. bone.worldY += ty * mixY;
  178. }
  179. if (mixScaleX != 0) {
  180. float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * mixScaleX + 1;
  181. bone.a *= s;
  182. bone.c *= s;
  183. }
  184. if (mixScaleY != 0) {
  185. float s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * mixScaleY + 1;
  186. bone.b *= s;
  187. bone.d *= s;
  188. }
  189. if (mixShearY > 0) {
  190. float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta);
  191. if (r > MathUtils.PI)
  192. r -= MathUtils.PI2;
  193. else if (r < -MathUtils.PI) //
  194. r += MathUtils.PI2;
  195. float b = bone.b, d = bone.d;
  196. r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY;
  197. float s = (float)Math.Sqrt(b * b + d * d);
  198. bone.b = MathUtils.Cos(r) * s;
  199. bone.d = MathUtils.Sin(r) * s;
  200. }
  201. bone.UpdateAppliedTransform();
  202. }
  203. }
  204. void ApplyAbsoluteLocal () {
  205. float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
  206. mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
  207. Bone target = this.target;
  208. var bones = this.bones.Items;
  209. for (int i = 0, n = this.bones.Count; i < n; i++) {
  210. Bone bone = bones[i];
  211. float rotation = bone.arotation;
  212. if (mixRotate != 0) {
  213. float r = target.arotation - rotation + data.offsetRotation;
  214. r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
  215. rotation += r * mixRotate;
  216. }
  217. float x = bone.ax, y = bone.ay;
  218. x += (target.ax - x + data.offsetX) * mixX;
  219. y += (target.ay - y + data.offsetY) * mixY;
  220. float scaleX = bone.ascaleX, scaleY = bone.ascaleY;
  221. if (mixScaleX != 0 && scaleX != 0)
  222. scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * mixScaleX) / scaleX;
  223. if (mixScaleY != 0 && scaleY != 0)
  224. scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * mixScaleY) / scaleY;
  225. float shearY = bone.ashearY;
  226. if (mixShearY != 0) {
  227. float r = target.ashearY - shearY + data.offsetShearY;
  228. r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
  229. shearY += r * mixShearY;
  230. }
  231. bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
  232. }
  233. }
  234. void ApplyRelativeLocal () {
  235. float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
  236. mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
  237. Bone target = this.target;
  238. var bones = this.bones.Items;
  239. for (int i = 0, n = this.bones.Count; i < n; i++) {
  240. Bone bone = bones[i];
  241. float rotation = bone.arotation + (target.arotation + data.offsetRotation) * mixRotate;
  242. float x = bone.ax + (target.ax + data.offsetX) * mixX;
  243. float y = bone.ay + (target.ay + data.offsetY) * mixY;
  244. float scaleX = bone.ascaleX * (((target.ascaleX - 1 + data.offsetScaleX) * mixScaleX) + 1);
  245. float scaleY = bone.ascaleY * (((target.ascaleY - 1 + data.offsetScaleY) * mixScaleY) + 1);
  246. float shearY = bone.ashearY + (target.ashearY + data.offsetShearY) * mixShearY;
  247. bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
  248. }
  249. }
  250. /// <summary>The bones that will be modified by this transform constraint.</summary>
  251. public ExposedList<Bone> Bones { get { return bones; } }
  252. /// <summary>The target bone whose world transform will be copied to the constrained bones.</summary>
  253. public Bone Target { get { return target; } set { target = value; } }
  254. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.</summary>
  255. public float MixRotate { get { return mixRotate; } set { mixRotate = value; } }
  256. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation X.</summary>
  257. public float MixX { get { return mixX; } set { mixX = value; } }
  258. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
  259. public float MixY { get { return mixY; } set { mixY = value; } }
  260. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale X.</summary>
  261. public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } }
  262. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y.</summary>
  263. public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } }
  264. /// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y.</summary>
  265. public float MixShearY { get { return mixShearY; } set { mixShearY = value; } }
  266. public bool Active { get { return active; } }
  267. /// <summary>The transform constraint's setup pose data.</summary>
  268. public TransformConstraintData Data { get { return data; } }
  269. override public string ToString () {
  270. return data.name;
  271. }
  272. }
  273. }