123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- #include <QVector2D.h>
- #include <math.h>
- #include "game.h"
- #pragma once
- /**
- * Ported to C++ (Qt for vector class) from original python code:
- *
- # An example implementation of the algorithm described at
- # http://www.niksula.cs.hut.fi/~hkankaan/Homepages/metaballs.html
- #
- # The code contains some references to the document above, in form
- # ### Formula (x)
- # to make clear where each of the formulas is implemented (and what
- # it looks like in Python)
- #
- # Since Python doesn't have an in-built vector type, I used complex
- # numbers for coordinates (x is the real part, y is the imaginary part)
- #
- # Made by Hannu Kankaanpää. Use for whatever you wish.
- */
- class Ball
- {
- public:
- int sharedBall;
- QVector2D pos;
- QVector2D pos0;
- QVector2D edgePos;
- bool tracking;
- float size;
- VGubyte commands[1024];
- VGfloat vertices[1024];
- int verticeCount;
- /**Single metaball. */
- Ball(QVector2D _pos0, float size0) : pos(_pos0), size(size0), tracking(false)
- {
- }
- };
- #if 0
- /*
- def main():
- # This is where the execution starts.
- # First initialize the screen.
- pygame.init()
- screen = pygame.display.set_mode((640, 480))
- # Then create a couple of balls
- balls = [Ball(350 + 100j, size=3),
- Ball(20 + 200j, size=2),
- Ball(280 + 140j, size=4),
- Ball(400 + 440j, size=3)]
- # And a metaball system (see below for class definition)
- mbs = MetaballSystem(balls, goo=2.0, threshold=0.0004, screen=screen)
- while True:
- # clear screen with black
- screen.fill((0, 0, 0))
- # move ball number 0 according to mouse position
- if pygame.mouse.get_focused():
- balls[0].pos = complex(*pygame.mouse.get_pos())
- # Draw the balls.
- # Try different methods: euler, rungeKutta2 and rungeKutta4
- drawBalls(differentialMethod=rungeKutta2, metaballSystem=mbs,
- step=20, screen=screen)
- pygame.display.flip()
- # exit when esc is pressed
- for event in pygame.event.get():
- if event.type == QUIT:
- return
- elif event.type == KEYDOWN:
- if event.key == K_ESCAPE:
- return
- */
- def drawBalls(differentialMethod, metaballSystem, step, screen):
- mbs = metaballSystem
- balls = mbs.balls
- # First, track the border for all balls and store
- # it to pos0 and edgePos. The latter will move along the border,
- # pos0 stays at the initial coordinates.
- for ball in balls:
- ball.pos0 = mbs.trackTheBorder(ball.pos + 1j)
- ball.edgePos = ball.pos0
- ball.tracking = True
- loopIndex = 0
- while loopIndex < 200:
- loopIndex += 1
- for ball in balls:
- if not ball.tracking:
- continue
- # store the old coordinates
- old_pos = ball.edgePos
- # walk along the tangent, using chosen differential method
- ball.edgePos = differentialMethod(ball.edgePos, step, mbs.calcTangent)
- # correction step towards the border
- ball.edgePos, tmp = mbs.stepOnceTowardsBorder(ball.edgePos)
- pygame.draw.line(screen, (255, 255, 255),
- (old_pos.real, old_pos.imag),
- (ball.edgePos.real, ball.edgePos.imag))
- # check if we've gone a full circle or hit some other
- # edge tracker
- for ob in balls:
- if (ob is not ball or loopIndex > 3) and \
- abs(ob.pos0 - ball.edgePos) < step:
- ball.tracking = False
- tracking = 0
- for ball in balls:
- if ball.tracking:
- tracking += 1
- if tracking == 0:
- break
- #endif
- /**A class that manages the metaballs and can calculate
- several useful values from the system.
- */
- class MetaballSystem
- {
- public:
- float minSize;
- int ballCount;
- Ball *balls;
- float goo;
- float threshold;
- MetaballSystem(int ballCount0, Ball *balls0, float goo0, float threshold0) : balls(balls0), ballCount(ballCount0), goo(goo0), threshold(threshold0)
- {
- minSize=MAXFLOAT;
- for(int i=0;i<ballCount;i++)
- {
- if(balls[i].size<minSize)
- {
- minSize=balls[i].size;
- }
- }
- }
- /**Return the metaball field's force at point 'pos'.*/
- float calcForce(QVector2D pos)
- {
- float force = 0;
- for(int i=0;i<ballCount;i++)
- {
- //### Formula (1)
- // TODO div = abs(ball.pos - pos)**self.goo
- QVector2D v=(balls[i].pos-pos);
- float div = (goo==2.0 ? v.lengthSquared() : powf(v.length(), goo));
- if(div != 0) //# to prevent division by zero
- force += balls[i].size / div;
- else
- force += 10000; //#"big number"
- }
- return force;
- }
- /**Return a normalized (magnitude = 1) normal at point 'pos'.*/
- QVector2D calcNormal(QVector2D pos)
- {
- QVector2D np(0,0);
- for(int i=0;i<ballCount;i++)
- {
- //### Formula (3)
- //div = abs(ball.pos - pos)**(2 + self.goo)
- //np += -self.goo * ball.size * (ball.pos - pos) / div
- QVector2D v=(balls[i].pos-pos);
- //float div = pow(v.length(), 2+goo);
- float div = v.lengthSquared() * (goo==2.0 ? v.lengthSquared() : powf(v.length(), goo));
- np += -goo * balls[i].size * v / div;
- }
- return np.normalized();
- }
- /**Return a normalized (magnitude = 1) tangent at point 'pos'.*/
- QVector2D calcTangent(QVector2D pos)
- {
- QVector2D np = calcNormal(pos);
- //### Formula (7)
- return QVector2D(-np.y(), np.x());
- }
- /**Step once towards the border of the metaball field, return
- new coordinates and force at old coordinates.
- */
- float stepOnceTowardsBorder(QVector2D &pos)
- {
- float force = calcForce(pos);
- QVector2D np = calcNormal(pos);
- //### Formula (5)
- float stepsize = powf(minSize / threshold, 1 / goo) -
- powf(minSize / force, 1 / goo) + 0.01;
- pos += np * stepsize;
- return force;
- }
- /**Track the border of the metaball field and return new
- coordinates.
- */
- QVector2D trackTheBorder(QVector2D pos)
- {
- float force = 9999999;
- //# loop until force is weaker than the desired threshold
- int foo=0;
- while (force > threshold)
- {
- foo++;
- if(foo>100) break;
- force = stepOnceTowardsBorder(pos);
- }
- return pos;
- }
- /**Euler's method.
- The most simple way to solve differential systems numerically.
- */
- QVector2D eulerTangent(QVector2D pos, float h)
- {
- return pos + h * calcTangent(pos);
- }
- /**Runge-Kutta 2 (=mid-point).
- This is only a little more complex than the Euler's method,
- but significantly better.
- */
- QVector2D rungeKutta2Tangent(QVector2D pos, float h)
- {
- return pos + h * calcTangent(pos + calcTangent(pos) * h / 2);
- }
- };
|