metaballs.h 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. #include <QVector2D.h>
  2. #include <math.h>
  3. #include "game.h"
  4. #pragma once
  5. /**
  6. * Ported to C++ (Qt for vector class) from original python code:
  7. *
  8. # An example implementation of the algorithm described at
  9. # http://www.niksula.cs.hut.fi/~hkankaan/Homepages/metaballs.html
  10. #
  11. # The code contains some references to the document above, in form
  12. # ### Formula (x)
  13. # to make clear where each of the formulas is implemented (and what
  14. # it looks like in Python)
  15. #
  16. # Since Python doesn't have an in-built vector type, I used complex
  17. # numbers for coordinates (x is the real part, y is the imaginary part)
  18. #
  19. # Made by Hannu Kankaanpää. Use for whatever you wish.
  20. */
  21. class Ball
  22. {
  23. public:
  24. int sharedBall;
  25. QVector2D pos;
  26. QVector2D pos0;
  27. QVector2D edgePos;
  28. bool tracking;
  29. float size;
  30. VGubyte commands[1024];
  31. VGfloat vertices[1024];
  32. int verticeCount;
  33. /**Single metaball. */
  34. Ball(QVector2D _pos0, float size0) : pos(_pos0), size(size0), tracking(false)
  35. {
  36. }
  37. };
  38. #if 0
  39. /*
  40. def main():
  41. # This is where the execution starts.
  42. # First initialize the screen.
  43. pygame.init()
  44. screen = pygame.display.set_mode((640, 480))
  45. # Then create a couple of balls
  46. balls = [Ball(350 + 100j, size=3),
  47. Ball(20 + 200j, size=2),
  48. Ball(280 + 140j, size=4),
  49. Ball(400 + 440j, size=3)]
  50. # And a metaball system (see below for class definition)
  51. mbs = MetaballSystem(balls, goo=2.0, threshold=0.0004, screen=screen)
  52. while True:
  53. # clear screen with black
  54. screen.fill((0, 0, 0))
  55. # move ball number 0 according to mouse position
  56. if pygame.mouse.get_focused():
  57. balls[0].pos = complex(*pygame.mouse.get_pos())
  58. # Draw the balls.
  59. # Try different methods: euler, rungeKutta2 and rungeKutta4
  60. drawBalls(differentialMethod=rungeKutta2, metaballSystem=mbs,
  61. step=20, screen=screen)
  62. pygame.display.flip()
  63. # exit when esc is pressed
  64. for event in pygame.event.get():
  65. if event.type == QUIT:
  66. return
  67. elif event.type == KEYDOWN:
  68. if event.key == K_ESCAPE:
  69. return
  70. */
  71. def drawBalls(differentialMethod, metaballSystem, step, screen):
  72. mbs = metaballSystem
  73. balls = mbs.balls
  74. # First, track the border for all balls and store
  75. # it to pos0 and edgePos. The latter will move along the border,
  76. # pos0 stays at the initial coordinates.
  77. for ball in balls:
  78. ball.pos0 = mbs.trackTheBorder(ball.pos + 1j)
  79. ball.edgePos = ball.pos0
  80. ball.tracking = True
  81. loopIndex = 0
  82. while loopIndex < 200:
  83. loopIndex += 1
  84. for ball in balls:
  85. if not ball.tracking:
  86. continue
  87. # store the old coordinates
  88. old_pos = ball.edgePos
  89. # walk along the tangent, using chosen differential method
  90. ball.edgePos = differentialMethod(ball.edgePos, step, mbs.calcTangent)
  91. # correction step towards the border
  92. ball.edgePos, tmp = mbs.stepOnceTowardsBorder(ball.edgePos)
  93. pygame.draw.line(screen, (255, 255, 255),
  94. (old_pos.real, old_pos.imag),
  95. (ball.edgePos.real, ball.edgePos.imag))
  96. # check if we've gone a full circle or hit some other
  97. # edge tracker
  98. for ob in balls:
  99. if (ob is not ball or loopIndex > 3) and \
  100. abs(ob.pos0 - ball.edgePos) < step:
  101. ball.tracking = False
  102. tracking = 0
  103. for ball in balls:
  104. if ball.tracking:
  105. tracking += 1
  106. if tracking == 0:
  107. break
  108. #endif
  109. /**A class that manages the metaballs and can calculate
  110. several useful values from the system.
  111. */
  112. class MetaballSystem
  113. {
  114. public:
  115. float minSize;
  116. int ballCount;
  117. Ball *balls;
  118. float goo;
  119. float threshold;
  120. MetaballSystem(int ballCount0, Ball *balls0, float goo0, float threshold0) : balls(balls0), ballCount(ballCount0), goo(goo0), threshold(threshold0)
  121. {
  122. minSize=MAXFLOAT;
  123. for(int i=0;i<ballCount;i++)
  124. {
  125. if(balls[i].size<minSize)
  126. {
  127. minSize=balls[i].size;
  128. }
  129. }
  130. }
  131. /**Return the metaball field's force at point 'pos'.*/
  132. float calcForce(QVector2D pos)
  133. {
  134. float force = 0;
  135. for(int i=0;i<ballCount;i++)
  136. {
  137. //### Formula (1)
  138. // TODO div = abs(ball.pos - pos)**self.goo
  139. QVector2D v=(balls[i].pos-pos);
  140. float div = (goo==2.0 ? v.lengthSquared() : powf(v.length(), goo));
  141. if(div != 0) //# to prevent division by zero
  142. force += balls[i].size / div;
  143. else
  144. force += 10000; //#"big number"
  145. }
  146. return force;
  147. }
  148. /**Return a normalized (magnitude = 1) normal at point 'pos'.*/
  149. QVector2D calcNormal(QVector2D pos)
  150. {
  151. QVector2D np(0,0);
  152. for(int i=0;i<ballCount;i++)
  153. {
  154. //### Formula (3)
  155. //div = abs(ball.pos - pos)**(2 + self.goo)
  156. //np += -self.goo * ball.size * (ball.pos - pos) / div
  157. QVector2D v=(balls[i].pos-pos);
  158. //float div = pow(v.length(), 2+goo);
  159. float div = v.lengthSquared() * (goo==2.0 ? v.lengthSquared() : powf(v.length(), goo));
  160. np += -goo * balls[i].size * v / div;
  161. }
  162. return np.normalized();
  163. }
  164. /**Return a normalized (magnitude = 1) tangent at point 'pos'.*/
  165. QVector2D calcTangent(QVector2D pos)
  166. {
  167. QVector2D np = calcNormal(pos);
  168. //### Formula (7)
  169. return QVector2D(-np.y(), np.x());
  170. }
  171. /**Step once towards the border of the metaball field, return
  172. new coordinates and force at old coordinates.
  173. */
  174. float stepOnceTowardsBorder(QVector2D &pos)
  175. {
  176. float force = calcForce(pos);
  177. QVector2D np = calcNormal(pos);
  178. //### Formula (5)
  179. float stepsize = powf(minSize / threshold, 1 / goo) -
  180. powf(minSize / force, 1 / goo) + 0.01;
  181. pos += np * stepsize;
  182. return force;
  183. }
  184. /**Track the border of the metaball field and return new
  185. coordinates.
  186. */
  187. QVector2D trackTheBorder(QVector2D pos)
  188. {
  189. float force = 9999999;
  190. //# loop until force is weaker than the desired threshold
  191. int foo=0;
  192. while (force > threshold)
  193. {
  194. foo++;
  195. if(foo>100) break;
  196. force = stepOnceTowardsBorder(pos);
  197. }
  198. return pos;
  199. }
  200. /**Euler's method.
  201. The most simple way to solve differential systems numerically.
  202. */
  203. QVector2D eulerTangent(QVector2D pos, float h)
  204. {
  205. return pos + h * calcTangent(pos);
  206. }
  207. /**Runge-Kutta 2 (=mid-point).
  208. This is only a little more complex than the Euler's method,
  209. but significantly better.
  210. */
  211. QVector2D rungeKutta2Tangent(QVector2D pos, float h)
  212. {
  213. return pos + h * calcTangent(pos + calcTangent(pos) * h / 2);
  214. }
  215. };