Causing Interaction Between Polar-Opposite Magnetic Balls
This JavaFX application implements a simple physical model of an interaction between several magnetized balls with opposite polarity. An attractive force and the mass of a ball are directly proportional to the ball's radius. Dragging the ball with a mouse produces an acceleration.
Understanding the Code
The Ball class, the custom node for a single ball, provides functions for moving the ball and interaction with other balls. The Main class creates several balls that start moving according to their polarity.
The event variable is used for calculating the difference between new and old positions of the ball and moving it to the new position. Note that this difference produces an acceleration of the ball when the mouse is released after dragging. The code shown in Figure 1 provides this functionality.
var event: MouseEvent on replace old {
if (event != null) {
vx = event.dragX - old.dragX;
vy = event.dragY - old.dragY;
translateX += vx;
translateY += vy;
}
}
...
cursor: Cursor.HAND
onMouseDragged: function(event) {
this.event = event;
}
onMouseReleased: function(event) {
this.event = null;
}
Figure 1: Mouse Dragging the Ball
The function shown in Figure 2 updates the ball position and validates it according to the bound of the scene. It is called 25 times per second after processing the interaction with other balls.
public function update() {
if (event == null) {
translateX += (vx *= 0.9);
translateY += (vy *= 0.9);
if (translateX < radius) { vx = -vx; translateX = radius + radius - translateX }
if (translateY < radius) { vy = -vy; translateY = radius + radius - translateY }
var w = scene.width - radius;
var h = scene.height - radius;
if (translateX > w) { vx = -vx; translateX = w + w - translateX }
if (translateY > h) { vy = -vy; translateY = h + h - translateY }
}
}
Figure 2: Ball Movement
The function shown in Figure 3 processes the collision of two balls and updates their velocity according to their polarity. The function is called 25 times per second for every pair of balls on the scene.
public function process(ball: Ball) {
var d = ball.radius + radius;
var dx = ball.translateX - translateX;
var dy = ball.translateY - translateY;
var dd = (dx * dx) + (dy * dy);
var collision = dd <= (d * d);
// process collision
if (collision) {
var dvx = ball.vx - vx;
var dvy = ball.vy - vy;
var mag = dvx * dx + dvy * dy;
if (mag <= 0) {
mag /= dd;
var delta_vx = dx * mag;
var delta_vy = dy * mag;
ball.vx -= delta_vx * radius / d;
ball.vy -= delta_vy * radius / d;
vx += delta_vx * ball.radius / d;
vy += delta_vy * ball.radius / d;
}
}
// process gravity
if (dd > 0) {
var f = 0.05 * ball.radius * radius / dd;
var delta_vx = dx * f;
var delta_vy = dy * f;
if (collision or (ball.sign == sign)) {
delta_vx = -delta_vx;
delta_vy = -delta_vy;
}
ball.vx -= delta_vx * radius / d;
ball.vy -= delta_vy * radius / d;
vx += delta_vx * ball.radius / d;
vy += delta_vy * ball.radius / d;
}
}
Figure 3: Interaction of the Balls
To summarize, Figure 4 shows the animation that processes the balls' interaction and movement every 40 ms to display 25 frames per second.
Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
time: 40ms
action: function() {
var n = scene.content.size() - 2;
for (i in [0..n]) {
def ball = scene.content[i] as Ball;
for (j in [i..n]) {
ball.process(scene.content[j+1] as Ball)
}
}
for (node in scene.content)
(node as Ball).update()
}
}
}.play()
Figure 4: Recalculating 25 Times per Second
Customizing the Code
To further customize the sample, change the preferred width and height of the scene in the Main class, as shown in Figure 5. For example, you can change a background color or apply a gradient fill effect.
def scene = Scene {
fill: Color.BLACK
width: 600
height: 400
}
Figure 5: Scene Initialization
The code shown in Figure 6 creates 10 different balls by using random values for the position, radius, and the sign of polarity.
scene.content = for (i in [1..10]) Ball { sign: 0.5 < rnd.nextDouble() radius: 10 + 20 * rnd.nextDouble() translateX: scene.width * rnd.nextDouble() translateY: scene.height * rnd.nextDouble() }
Figure 6: Creating Magnetic Balls
Note that the scene content is created after the stage initialization. It is necessary for obtaining the actual width and height of the scene, which are used to generate the start position of the balls.
Sergey Malenkov

