1+ # Mass-Spring Solid Simulation
2+
3+ import math
4+ import numpy as np # for vector data structure and computations
5+ import pygame # for visualization
6+ import square_mesh # for generating a square mesh
7+
8+ # simulation setup
9+ m = 1000 # mass of each particle
10+ side_length = 1 # side length of the square
11+ n_seg = 4 # number of springs per side of the square
12+ [x , e ] = square_mesh .generate (side_length , n_seg ) # array of particle positions and springs ###
13+ v = np .array ([[0.0 , 0.0 ]] * len (x )) # velocity array of particles ###
14+ g = np .array ([0 , - 9.81 ]) # gravitational acceleration
15+ spring_rest_len = [] # rest length array of the springs ###
16+ for i in range (0 , len (e )): # calculate the rest length of each spring
17+ spring_vec = x [e [i ][0 ]] - x [e [i ][1 ]] # the vector connecting two ends of spring i
18+ spring_rest_len .append (math .sqrt (spring_vec [0 ] * spring_vec [0 ] + spring_vec [1 ] * spring_vec [1 ]))
19+ spring_stiffness = 1000 # stiffness of the spring
20+ h = 0.1 # time step size in seconds
21+
22+ # visualization/rendering setup
23+ pygame .init ()
24+ render_FPS = 100 # number of frames to render per second
25+ resolution = np .array ([900 , 900 ]) # visualization window size in pixels
26+ offset = resolution / 2 # offset between window coordinates and simulated coordinates
27+ scale = 200 # scale between window coordinates and simulated coordinates
28+ def screen_projection (x ): # convert simulated coordinates to window coordinates
29+ return [offset [0 ] + scale * x [0 ], resolution [1 ] - (offset [1 ] + scale * x [1 ])]
30+ screen = pygame .display .set_mode (resolution ) # initialize visualizer
31+
32+ time_step = 0 # the number of the current time step
33+ running = True # flag indicating whether the simulation is still running
34+ while running :
35+ # run until the user asks to quit
36+ for event in pygame .event .get ():
37+ if event .type == pygame .QUIT :
38+ running = False
39+
40+ # update the frame to display according to render_FPS
41+ if time_step % int (math .ceil ((1.0 / render_FPS ) / h )) == 0 :
42+ # fill the background with white color, display simulation time at the top,
43+ # draw each spring segment, and render each particle as a circle:
44+ screen .fill ((255 , 255 , 255 ))
45+ pygame .display .set_caption ('Current time: ' + f'{ time_step * h : .2f} s' )
46+ for i in range (0 , len (e )): ###
47+ pygame .draw .aaline (screen , (0 , 0 , 255 ), screen_projection (x [e [i ][0 ]]), screen_projection (x [e [i ][1 ]]))
48+ for i in range (0 , len (x )): ###
49+ pygame .draw .circle (screen , (0 , 0 , 255 ), screen_projection (x [i ]), 0.02 * scale )
50+ pygame .display .flip () # flip the display
51+ pygame .time .wait (int (1000.0 / render_FPS )) # wait to render the next frame
52+
53+ # step forward the simulation by updating particle velocity and position ###
54+ for i in range (0 , len (e )):
55+ # calculate elasticity force of spring i:
56+ spring_vec = x [e [i ][0 ]] - x [e [i ][1 ]]
57+ spring_cur_len = math .sqrt (spring_vec [0 ] * spring_vec [0 ] + spring_vec [1 ] * spring_vec [1 ])
58+ spring_displacement = spring_cur_len - spring_rest_len [i ]
59+ spring_force = - spring_stiffness * spring_displacement * (spring_vec / spring_cur_len )
60+ # update the velocity of the two ends of spring i
61+ v [e [i ][0 ]] += h * (g + spring_force ) / m
62+ v [e [i ][1 ]] += h * (g - spring_force ) / m
63+ # fix the top left and top right corner by setting velocity to 0:
64+ v [n_seg ] = v [(n_seg + 1 ) * (n_seg + 1 ) - 1 ] = np .array ([0 , 0 ])
65+ # update the position of each particle:
66+ for i in range (0 , len (x )):
67+ x [i ] += h * v [i ]
68+
69+ time_step += 1 # update time step counter
0 commit comments