-
Notifications
You must be signed in to change notification settings - Fork 0
/
Simulation.java
269 lines (218 loc) · 6.64 KB
/
Simulation.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
public class Simulation extends JFrame
{
/* SETTINGS START */
public static final int CONSTRAINT_ITERATIONS = 20; // The higher the iteration count, the more accurate the constraints will be
public static final double CONSTRAINT_BREAKPOINT = 999; // 0 - infinity, the force required to break the constraint
public static final double CONSTRAINT_STRENGTH = 1; // 0 - 1, how far the constraint can extend past its original distance
public static final double GRAVITY = 0.05;
public static final double COLLISION_DAMP = 0.75; // velocity multiplier on collision
public static final double SIDE_PADDING = 5; // the distance to the side that nodes should collide with
/* SETTINGS END */
public ArrayList<Node> nodes = new ArrayList<Node>();
public ArrayList<Constraint> constraints = new ArrayList<Constraint>();
public ArrayList<Collider> colliders = new ArrayList<Collider>();
int width; // width of the screen and borders
int height; // height of the screen and borders
GraphicsPanel graphicsPanel;
// set up the window and variables
public Simulation(int width, int height)
{
this.width = width;
this.height = height;
setTitle("Verlet Simulation");
setSize(width, height + 40); // + 40 because the titlebar is apparently included in this method call and it's 40 pixels tall
setVisible(true);
graphicsPanel = new GraphicsPanel();
getContentPane().add(graphicsPanel);
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
public void setPainter(Painter painter)
{
graphicsPanel.setPainter(painter);
}
// Handles rendering each frame
public class GraphicsPanel extends JPanel
{
Painter painter;
public GraphicsPanel()
{
}
public void setPainter(Painter painter)
{
this.painter = painter;
}
// draw the graphics
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// enable anti aliasing
Graphics2D g2d = ((Graphics2D)g);
g2d.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
if(painter != null)
painter.paintComponent(g);
// draw all nodes
for(int i = 0; i < nodes.size(); i++)
{
nodes.get(i).draw(g);
}
// draw all constraints
for(int i = 0; i < constraints.size(); i++)
{
constraints.get(i).draw(g);
}
// draw all colliders
for(int i = 0; i < colliders.size(); i++)
{
colliders.get(i).draw(g);
}
}
}
// run the physics simulation once
public void simulate()
{
simulateNodes();
// Run the simulation for constraints CONSTRAINT_ITERATIONS times
for(int s = 0; s < CONSTRAINT_ITERATIONS; s++)
{
simulateConstraints();
}
simulateCollision();
}
// simulate all nodes
void simulateNodes()
{
for(int i = 0; i < nodes.size(); i++)
{
Node node = nodes.get(i);
// Skip the node if it's fixed
if(node.fixed)
continue;
// store the current position for later
double tempX = node.x;
double tempY = node.y;
// calculate the node's current velocity
double velX = node.x - node.oldX;
double velY = node.y - node.oldY;
// add the velocity to the position, apply external forces (gravity)
node.x += velX;
node.y += velY + GRAVITY;
// set the old position to the position we had before applying forces
node.oldX = tempX;
node.oldY = tempY;
}
}
// simulate all constraints
void simulateConstraints()
{
for(int i = 0; i < constraints.size(); i++)
{
Constraint constraint = constraints.get(i);
// calculate the difference in the nodes each axis
double diffX = constraint.nodeA.x - constraint.nodeB.x;
double diffY = constraint.nodeA.y - constraint.nodeB.y;
// calculate the current distance between the nodes
double distance = Math.sqrt(diffX * diffX + diffY * diffY);
// break the constraint if it is too long
if(distance > constraint.length * (CONSTRAINT_BREAKPOINT + 1)) {
constraints.remove(i);
i--;
continue;
}
// calculate the difference between the target length and the current distance
double diff = constraint.length - distance;
// get the percent difference the nodes are off by (/2 because of 2 nodes)
double percent = diff / distance / 2;
// if they are fixed, revert the percent back to normal
if(constraint.nodeA.fixed || constraint.nodeB.fixed)
{
percent *= 2;
}
// multiply the percent by the constraint strength
percent *= CONSTRAINT_STRENGTH;
// calculate the offset required for the nodes to get them to the optimal position
double offsetX = diffX * percent;
double offsetY = diffY * percent;
// apply the offset to nodeA if it is not fixed
if(!constraint.nodeA.fixed)
{
constraint.nodeA.x += offsetX;
constraint.nodeA.y += offsetY;
}
// apply the offset to nodeB if it is not fixed
if(!constraint.nodeB.fixed)
{
constraint.nodeB.x -= offsetX;
constraint.nodeB.y -= offsetY;
}
}
}
// simulate collision
void simulateCollision()
{
// calculate the bounds for the walls
double leftBound = SIDE_PADDING;
double rightBound = width - SIDE_PADDING * 4;
double topBound = SIDE_PADDING;
double bottomBound = height - SIDE_PADDING;
for(int i = 0; i < nodes.size(); i++)
{
Node node = nodes.get(i);
// skip the node if it's fixed
if(node.fixed)
continue;
// get the velocity of the node
double velX = node.x - node.oldX;
double velY = node.y - node.oldY;
// handle collision for the top bound
if(node.y < topBound)
{
node.y = topBound;
node.oldY = topBound + velY * COLLISION_DAMP;
}
// handle collision for the bottom bound
if(node.y > bottomBound)
{
node.y = bottomBound;
node.oldY = bottomBound + velY * COLLISION_DAMP;
node.oldX = node.oldX + velX * 0.1 * COLLISION_DAMP; // friction
}
// handle collision for the right bound
if(node.x > rightBound)
{
node.x = rightBound;
node.oldX = rightBound + velX * COLLISION_DAMP;
}
// handle collision for the left bound
if(node.x < leftBound)
{
node.x = leftBound;
node.oldX = leftBound + velX * COLLISION_DAMP;
}
for(int c = 0; c < colliders.size(); c++)
{
Collider col = colliders.get(c);
if(col.intersect(node.x, node.y))
{
col.rebound(node);
}
}
}
}
// returns a random integer between the min and the max (exclusive)
public static int randomInt(int min, int max)
{
Random random = new Random(System.currentTimeMillis());
return random.nextInt(max - min) + min;
}
}