Code archives/Graphics/2D Metaballs
This code has been declared by its author to be Public Domain code.
Download source code
| Original was in Python. Look here for a description the method and the Python PyGame example: http://www.niksula.cs.hut.fi/~hkankaan/Homepages/metaballs.html Quite a few things are divided over far too many lines. Haven't bothered fixing this. I was having trouble converting Pythons equation-like expressions to BMX. |
[code]
SuperStrict
Framework BRL.GlMax2D
AppTitle = "premature optimization is the root of all evil"
'HideMouse()
Global resX:Int=800, resY:Int=600
SetGraphicsDriver GLMax2DDriver()
Graphics resX,resY,0,0
Local balls:tball[4]
balls[0] = TBall.Create( TPoint.Create(350,100),3)
balls[1] = TBall.Create( TPoint.Create(20,200),2)
balls[2] = TBall.Create( TPoint.Create(280,140),4)
balls[3] = TBall.Create( TPoint.Create(400,440),3)
Local threshold:Float = 0.0003'0000100000000000
Local mbs:tmetaballsystem = TMetaballsystem.Create(balls, 2.0, threshold)
Local iterations:Int = 0
Local fps:Int = 0
Local lastSec:Long = MilliSecs() + 1000
Local frames:Int = 0
DrawText "if this stays long we have an infinite loop :(",0,0
Flip
While(Not KeyHit(KEY_ESCAPE) And Not AppTerminate() )
If(MouseDown(1))
balls[0].pos.x = MouseX()
balls[0].pos.y = MouseY()
End If
mbs.drawBalls(20) 'sim_step
SetColor 255,255,0
DrawText("iterations: "+iterations+" fps: "+fps, 0,0)
DrawText("last force: ")+mbs.force, 0, 20
If(lastSec < MilliSecs() )
lastSec = MilliSecs() + 1000
fps = frames
frames = 0
EndIf
iterations:+1
frames :+ 1
Flip
Cls
Wend
End
Type TPoint
Field x:Double, y:Double
Function Create:TPoint(x:Double, y:Double)
Local tmp:TPoint = New TPoint
tmp.x = x
tmp.y = y
Return tmp
End Function
End Type
Type TBall
Field pos:TPoint
Field pos0:TPoint
Field edgePos:tpoint
Field size:Double
Field tracking:Int 'Bool
Function Create:TBall(pos:tpoint, size:Double)
Local tmp:TBall = New TBall
tmp.pos = pos
tmp.pos0 = pos
tmp.edgePos = pos
tmp.size = size
tmp.tracking = False
Return tmp
End Function
End Type
Type TMetaballsystem
Field balls:tball[]
Field goo:Double
Field threshold:Double
Field minSize:Double
Field force:Double
Function Create:TMetaballsystem(balls:TBall[], goo:Double, threshold:Double)
Local tmp:TMetaballsystem = New TMetaballsystem
tmp.balls = balls
tmp.goo = goo
tmp.threshold = threshold
tmp.minSize = 9 'smallest ball
Return tmp
End Function
' fix 26/11/08
Method calcForce:Double(pos:tpoint) 'called by stepOnceTowardsBorder
'return the metaball fields force at point "pos"
Local singleForce:Double = 0
For Local ball:tball = EachIn balls
'### Formula (1)
Local tmp:TPoint = TPoint.Create(ball.pos.y-pos.y,ball.pos.x-pos.x)
Local div:Double= Sqr(tmp.x*tmp.x + tmp.y*tmp.y)
div = div*div '^2
If(div <> 0)
singleForce :+ ball.size / div
Else
singleForce = 10000 '"big number"
EndIf
Next
Return singleForce
End Method
'checked 26.11.08
Method calcNormal:tpoint(pos:tpoint)
'return a normalized (magnitude==1) tangent at points "pos"
Local np:TPoint=TPoint.Create(0,0)
For Local ball:tball = EachIn balls
' ### Formula (3)
Local tmp:TPoint = TPoint.Create(ball.pos.y-pos.y, ball.pos.x-pos.x)
Local div:Double= Sqr(tmp.x*tmp.x + tmp.y*tmp.y)
div = div*div*div*div
np.x :+ -Self.goo * ball.size * (ball.pos.x-pos.x) /div
np.y :+ -Self.goo * ball.size * (ball.pos.y-pos.y) /div
Next
Local lengthOfVector:Double = Sqr(np.x*np.x + np.y*np.y)
Return TPoint.Create( np.x/lengthOfVector, np.y/lengthOfVector)
End Method
'checked 26.11.08
Method calcTangent:tpoint(pos:tpoint)
'return a normalized (magnitude==1) tangent at points "pos"
Local np:tpoint=Self.calcNormal(pos)
'###Formula(7)
Return TPoint.Create( -np.y, np.x )
End Method
'checked 26.11.08
Method stepOnceTowardsBorder:tpoint(pos:tpoint) 'called by drawBalls, trackTheB
'called by trackTheBorder
'step once towards the border of the metaballs field, return
'new coordinates and force at old coordinates
Local singleForce:Double = calcForce(pos)
Local np:TPoint = calcNormal(pos)
'### Formula(5)
Local stepsize:Double = (minSize/threshold)
stepsize = stepsize^(1/Self.goo)
stepsize = stepsize - (minSize / singleForce)^(1/Self.goo)
stepsize :+ 0.01
Self.force = singleForce
Return TPoint.Create( pos.x + np.x*stepsize , pos.y + np.y*stepsize )
End Method
'checked 26.11.08
Method trackTheBorder:tpoint(pos:tpoint)
'track the border of the metaball field and return new coords
force = 9999999
'loop until force is weaker than the desired threshold
Local i:Int =0
While(force > threshold)
pos = stepOnceTowardsBorder(pos)
'show a little debug output i.e. yellow pixels
' sz = screen size
'If( 0 < pos.x And pos.x < resX And 0 < pos.y And pos.y < resY)
SetColor 255,255,0
DrawRect( pos.x, pos.y, 10, 10 )
'EndIf
'Print "i, force, posx,posy "+i+", "+force+", "+pos.x+","+pos.y
'If i > 10 Then End
'i:+1
Wend
Return pos
End Method
Method rungeKutta2:TPoint(pos:TPoint, h:Double)
' PYTHON: pos + h * Self.calcTangent(pos + Self.calcTangent(pos) * h / 2)
Local tmp:TPoint = calcTangent(pos)
tmp.x = tmp.x * h / 2
tmp.y = tmp.y * h / 2
tmp.x :+ pos.x
tmp.y :+ pos.y
tmp = calcTangent( tmp )
tmp.x = tmp.x * h
tmp.y = tmp.y * h
tmp.x :+ pos.x
tmp.y :+ pos.y
Return tmp
End Method
Method drawBalls(stepping:Double)
'First track the border for all balls and store
'it to pos0 and edgPos. The latter will move along the border,
'pos0 stays at the initial coordinates
For Local ball:TBall = EachIn balls
Local borderPlusOne:TPoint = TPoint.Create(ball.pos0.x, ball.pos0.y+1)
ball.pos0 = trackTheBorder(borderPlusOne)
ball.edgePos = ball.pos0
ball.tracking = True
Next
Local loopIndex:Int = 0
Local tracking:Int=0
SetColor 255,255,255
SetLineWidth 5
While(loopIndex < 1000)
loopIndex :+ 1
For Local ball:tball = EachIn balls
If(Not ball.tracking) Then Continue 'skip if tracking
'store the old coordinates for drawing
Local old_pos:TPoint = ball.edgePos
'walk along the tangent, using chosen differential method
ball.edgePos = rungeKutta2(ball.edgePos, stepping )
'correction step towards the border
ball.edgePos = stepOnceTowardsBorder(ball.edgePos)
DrawLine(old_pos.x, old_pos.y, ball.edgePos.x, ball.edgePos.y)
'SetColor 255,0,0
'DrawOval old_pos.x-1, old_pos.y-1,2,2
'SetColor 0,0,255
'DrawOval ball.edgePos.x-2, ball.edgePos.y-2,4,4
'check if we've gone a full circle or hit some other edge tracker
For Local ob:TBall = EachIn balls
Local delta:TPoint = TPoint.Create(ob.pos0.y - ball.edgePos.y, ob.pos0.x - ball.edgePos.x)
Local distance:Double = Sqr(delta.x*delta.x + delta.y*delta.y)
If((ob <> ball Or loopIndex > 3) And distance < stepping)
ball.tracking = False
EndIf
Next 'eof check circle loop
Next 'eof For ball loop
tracking=0
For Local ball:TBall = EachIn balls
If(ball.tracking) Then tracking :+ 1
If tracking = 0 Then Exit
Next
Wend 'eof while loopIndex
End Method
End Type
[/code] |