PageRenderTime 57ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/APE/org/cove/ape/SpringConstraintParticle.hx

http://taboo.googlecode.com/
Haxe | 455 lines | 251 code | 94 blank | 110 comment | 41 complexity | edc9c50da15f02a0e021818f8d57a35d MD5 | raw file
  1. /*
  2. Copyright (c) 2006, 2007 Alec Cove
  3. untyped __is__(Permission,hereby) granted, free of charge, to any person obtaining a copy of this
  4. software and associated documentation files (the "Software"), to deal in the Software
  5. without restriction, including without limitation the rights to use, copy, modify,
  6. merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. permit persons to whom the untyped __is__(Software,furnished) to do so, subject to the following
  8. conditions:
  9. The above copyright notice and this permission notice shall be included in all copies
  10. or substantial portions of the Software.
  11. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  12. INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  13. PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  14. HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  15. CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  16. OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  17. */
  18. /*
  19. TODO:
  20. - scale the post collision velocity by both the position *and* mass of each particle.
  21. currently only the position and average inverse untyped __is__(mass,used). as with the velocity,
  22. it might be a problem since the contact untyped __is__(point,not) available when the mass is
  23. needed.
  24. - review all p1 p2 getters (eg get mass). can it be stored instead of computed everytime?
  25. - consider if the API should let the user set the SCP's properties directly. elasticity,
  26. friction, mass, etc are all inherited from the attached particles
  27. - consider a more accurate velocity getter. should use a parameterized value
  28. to scale the velocity relative to the contact point. one untyped __is__(problem,the) velocity is
  29. needed before the contact untyped __is__(point,established).
  30. - untyped __is__(setCorners,a) duplicate from the updateCornerPositions method in the RectangleParticle class,
  31. it needs to be placed back in that class but use the untyped __as__(displacement,suggested) by Jim B. Its here
  32. because of the way RectangleParticle calculates the corners -- once on, they are calculated
  33. constantly. that should be fixed too.
  34. - getContactPointParam should probably belong to the rectangleparticle and circleparticle classes.
  35. also the functions respective to each, for better OOD
  36. - clean up resolveCollision with submethods
  37. */
  38. package org.cove.ape ;
  39. import flash.display.Sprite;
  40. import flash.display.DisplayObject;
  41. class SpringConstraintParticle extends RectangleParticle {
  42. public var rectScale(get_rectScale,set_rectScale):Float;
  43. public var rectHeight(get_rectHeight,set_rectHeight):Float;
  44. public var fixedEndLimit(get_fixedEndLimit,set_fixedEndLimit):Float;
  45. private var p1:AbstractParticle;
  46. private var p2:AbstractParticle;
  47. private var avgVelocity:Vector;
  48. private var lambda:Vector;
  49. private var parent:SpringConstraint;
  50. private var scaleToLength:Bool;
  51. private var rca:Vector;
  52. private var rcb:Vector;
  53. private var s:Float;
  54. private var _rectScale:Float;
  55. private var _rectHeight:Float;
  56. private var _fixedEndLimit:Float;
  57. public function new(
  58. p1:AbstractParticle,
  59. p2:AbstractParticle,
  60. p:SpringConstraint,
  61. rectHeight:Float,
  62. rectScale:Float,
  63. scaleToLength:Bool) {
  64. super(0,0,0,0,0,false);
  65. this.p1 = p1;
  66. this.p2 = p2;
  67. lambda = new Vector(0,0);
  68. avgVelocity = new Vector(0,0);
  69. parent = p;
  70. this.rectScale = rectScale;
  71. this.rectHeight = rectHeight;
  72. this.scaleToLength = scaleToLength;
  73. fixedEndLimit = 0;
  74. rca = new Vector();
  75. rcb = new Vector();
  76. }
  77. public function set_rectScale(s:Float) {
  78. _rectScale = s;
  79. return s;
  80. }
  81. /**
  82. * @private
  83. */
  84. public function get_rectScale():Float {
  85. return _rectScale;
  86. }
  87. public function set_rectHeight(r:Float) {
  88. _rectHeight = r;
  89. return r;
  90. }
  91. /**
  92. * @private
  93. */
  94. public function get_rectHeight():Float {
  95. return _rectHeight;
  96. }
  97. /**
  98. * For cases when the untyped __is__(SpringConstraint,both) collidable and only one of the
  99. * two end particles are fixed, this value will dispose of collisions near the
  100. * fixed particle, to correct for situations where the collision could never be
  101. * resolved.
  102. */
  103. public function set_fixedEndLimit(f:Float) {
  104. _fixedEndLimit = f;
  105. return f;
  106. }
  107. /**
  108. * @private
  109. */
  110. public function get_fixedEndLimit():Float {
  111. return _fixedEndLimit;
  112. }
  113. /**
  114. * returns the average mass of the two connected particles
  115. */
  116. public override function get_mass():Float {
  117. return (p1.mass + p2.mass) / 2;
  118. }
  119. /**
  120. * returns the average elasticity of the two connected particles
  121. */
  122. public override function get_elasticity():Float {
  123. return (p1.elasticity + p2.elasticity) / 2;
  124. }
  125. /**
  126. * returns the average friction of the two connected particles
  127. */
  128. public override function get_friction():Float {
  129. return (p1.friction + p2.friction) / 2;
  130. }
  131. /**
  132. * returns the average velocity of the two connected particles
  133. */
  134. public override function get_velocity():Vector {
  135. var p1v:Vector = p1.velocity;
  136. var p2v:Vector = p2.velocity;
  137. avgVelocity.setTo(((p1v.x + p2v.x) / 2), ((p1v.y + p2v.y) / 2));
  138. return avgVelocity;
  139. }
  140. public override function init():Void {
  141. if (displayObject != null) {
  142. initDisplay();
  143. } else {
  144. var inner:Sprite = new Sprite();
  145. parent.sprite.addChild(inner);
  146. inner.name = "inner";
  147. var w:Float = parent.currLength * rectScale;
  148. var h:Float = rectHeight;
  149. inner.graphics.clear();
  150. inner.graphics.lineStyle(parent.lineThickness, parent.lineColor, parent.lineAlpha);
  151. inner.graphics.beginFill(parent.fillColor, parent.fillAlpha);
  152. inner.graphics.drawRect(-w/2, -h/2, w, h);
  153. inner.graphics.endFill();
  154. }
  155. paint();
  156. }
  157. public override function paint():Void {
  158. var c:Vector = parent.center;
  159. var s:Sprite = parent.sprite;
  160. if (scaleToLength) {
  161. s.getChildByName("inner").width = parent.currLength * rectScale;
  162. } else if (displayObject != null) {
  163. s.getChildByName("inner").width = parent.restLength * rectScale;
  164. }
  165. s.x = c.x;
  166. s.y = c.y;
  167. s.rotation = parent.angle;
  168. }
  169. /**
  170. * @private
  171. */
  172. public override function initDisplay():Void {
  173. displayObject.x = displayObjectOffset.x;
  174. displayObject.y = displayObjectOffset.y;
  175. displayObject.rotation = displayObjectRotation;
  176. var inner:Sprite = new Sprite();
  177. inner.name = "inner";
  178. inner.addChild(displayObject);
  179. parent.sprite.addChild(inner);
  180. }
  181. /**
  182. * @private
  183. * returns the average inverse mass.
  184. */
  185. public override function get_invMass():Float {
  186. if (p1.fixed && p2.fixed) return 0;
  187. return 1 / ((p1.mass + p2.mass) / 2);
  188. }
  189. /**
  190. * called only on collision
  191. */
  192. public function updatePosition():Void {
  193. var c:Vector = parent.center;
  194. curr.setTo(c.x, c.y);
  195. width = (scaleToLength) ? parent.currLength * rectScale : parent.restLength * rectScale;
  196. height = rectHeight;
  197. radian = parent.radian;
  198. }
  199. public override function resolveCollision(
  200. mtd:Vector, vel:Vector, n:Vector, d:Float, o:Int, p:AbstractParticle):Void {
  201. var t:Float = getContactPointParam(p);
  202. var c1:Float = (1 - t);
  203. var c2:Float = t;
  204. // if untyped __is__(one,fixed) then move the other particle the entire way out of collision.
  205. // also, dispose of collisions at the sides of the scp. The higher the fixedEndLimit
  206. // value, the more of the scp not be effected by collision.
  207. if (p1.fixed) {
  208. if (c2 <= fixedEndLimit) return;
  209. lambda.setTo(mtd.x / c2, mtd.y / c2);
  210. p2.curr.plusEquals(lambda);
  211. p2.velocity = vel;
  212. } else if (p2.fixed) {
  213. if (c1 <= fixedEndLimit) return;
  214. lambda.setTo(mtd.x / c1, mtd.y / c1);
  215. p1.curr.plusEquals(lambda);
  216. p1.velocity = vel;
  217. // else both non fixed - move proportionally out of collision
  218. } else {
  219. var denom:Float = (c1 * c1 + c2 * c2);
  220. if (denom == 0) return;
  221. lambda.setTo(mtd.x / denom, mtd.y / denom);
  222. p1.curr.plusEquals(lambda.mult(c1));
  223. p2.curr.plusEquals(lambda.mult(c2));
  224. // if untyped __is__(collision,in) the middle of SCP set the velocity of both end particles
  225. if (t == 0.5) {
  226. p1.velocity = vel;
  227. p2.velocity = vel;
  228. // otherwise change the velocity of the particle closest to contact
  229. } else {
  230. var corrParticle:AbstractParticle = (t < 0.5) ? p1 : p2;
  231. corrParticle.velocity = vel;
  232. }
  233. }
  234. }
  235. /**
  236. * given point c, returns a parameterized location on this SCP. Note
  237. * untyped __is__(this,just) treating the untyped __as__(SCP,if) it were a line segment (ab).
  238. */
  239. private function closestParamPoint(c:Vector):Float {
  240. var ab:Vector = p2.curr.minus(p1.curr);
  241. var t:Float = (ab.dot(c.minus(p1.curr))) / (ab.dot(ab));
  242. return MathUtil.clamp(t, 0, 1);
  243. }
  244. /**
  245. * returns a contact location on this SCP untyped __as__(expressed,a) parametric value in [0,1]
  246. */
  247. private function getContactPointParam(p:AbstractParticle):Float {
  248. var t:Float;
  249. if (untyped __is__(p,CircleParticle)) {
  250. t = closestParamPoint(p.curr);
  251. } else if (untyped __is__(p,RectangleParticle)) {
  252. // go through the sides of the colliding untyped __as__(rectangle,line) segments
  253. var shortestIndex:Int;
  254. var paramList = [ 4.0 ];
  255. var shortestDistance:Float = Math.POSITIVE_INFINITY;
  256. var i:Int = 0;
  257. while( i < 4) {
  258. setCorners(untyped __as__(p,RectangleParticle), i);
  259. // check for closest points on SCP to side of rectangle
  260. var d:Float = closestPtSegmentSegment();
  261. if (d < shortestDistance) {
  262. shortestDistance = d;
  263. shortestIndex = i;
  264. paramList[i] = s;
  265. }
  266. i++;
  267. }
  268. t = paramList[shortestIndex];
  269. }
  270. return t;
  271. }
  272. /**
  273. *
  274. */
  275. private function setCorners(r:RectangleParticle, i:Int):Void {
  276. var rx:Float = r.curr.x;
  277. var ry:Float = r.curr.y;
  278. var axes = r.axes;
  279. var extents = r.extents;
  280. var ae0_x:Float = axes[0].x * extents[0];
  281. var ae0_y:Float = axes[0].y * extents[0];
  282. var ae1_x:Float = axes[1].x * extents[1];
  283. var ae1_y:Float = axes[1].y * extents[1];
  284. var emx:Float = ae0_x - ae1_x;
  285. var emy:Float = ae0_y - ae1_y;
  286. var epx:Float = ae0_x + ae1_x;
  287. var epy:Float = ae0_y + ae1_y;
  288. if (i == 0) {
  289. // 0 and 1
  290. rca.x = rx - epx;
  291. rca.y = ry - epy;
  292. rcb.x = rx + emx;
  293. rcb.y = ry + emy;
  294. } else if (i == 1) {
  295. // 1 and 2
  296. rca.x = rx + emx;
  297. rca.y = ry + emy;
  298. rcb.x = rx + epx;
  299. rcb.y = ry + epy;
  300. } else if (i == 2) {
  301. // 2 and 3
  302. rca.x = rx + epx;
  303. rca.y = ry + epy;
  304. rcb.x = rx - emx;
  305. rcb.y = ry - emy;
  306. } else if (i == 3) {
  307. // 3 and 0
  308. rca.x = rx - emx;
  309. rca.y = ry - emy;
  310. rcb.x = rx - epx;
  311. rcb.y = ry - epy;
  312. }
  313. }
  314. /**
  315. * pp1-pq1 will be the SCP line segment on which we need parameterized s.
  316. */
  317. private function closestPtSegmentSegment():Float {
  318. var pp1:Vector = p1.curr;
  319. var pq1:Vector = p2.curr;
  320. var pp2:Vector = rca;
  321. var pq2:Vector = rcb;
  322. var d1:Vector = pq1.minus(pp1);
  323. var d2:Vector = pq2.minus(pp2);
  324. var r:Vector = pp1.minus(pp2);
  325. var t:Float;
  326. var a:Float = d1.dot(d1);
  327. var e:Float = d2.dot(d2);
  328. var f:Float = d2.dot(r);
  329. var c:Float = d1.dot(r);
  330. var b:Float = d1.dot(d2);
  331. var denom:Float = a * e - b * b;
  332. if (denom != 0.0) {
  333. s = MathUtil.clamp((b * f - c * e) / denom, 0, 1);
  334. } else {
  335. s = 0.5;// give the midpoint for parallel lines
  336. }
  337. t = (b * s + f) / e;
  338. if (t < 0) {
  339. t = 0;
  340. s = MathUtil.clamp(-c / a, 0, 1);
  341. } else if (t > 0) {
  342. t = 1;
  343. s = MathUtil.clamp((b - c) / a, 0, 1);
  344. }
  345. var c1:Vector = pp1.plus(d1.mult(s));
  346. var c2:Vector = pp2.plus(d2.mult(t));
  347. var c1mc2:Vector = c1.minus(c2);
  348. return c1mc2.dot(c1mc2);
  349. }
  350. }