Skip to content

Instantly share code, notes, and snippets.

@villares
Last active April 7, 2022 23:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save villares/cf44d5d191a698564321ccba24e7a060 to your computer and use it in GitHub Desktop.
Save villares/cf44d5d191a698564321ccba24e7a060 to your computer and use it in GitHub Desktop.
Noite de Processing - Simulações Físicas - com John e Alexandre https://www.youtube.com/watch?v=5u49PR9ICMg
# You'll need py5 -https://py5.ixora.io
# Also needs http://pymunk.org
# Similar to the previous one but with some monkey patching and changes the softbody grid construction
import py5
import pymunk as pm
space = pm.Space()
space.gravity = (0, 600)
constraints = []
current_poly = []
stiffness = 1000
damping = 100
mass = 1
def setup():
py5.size(500, 500)
pm.Poly.draw = draw_poly # this is called "monkeypatching"
pm.Circle.draw = draw_circle
pm.Segment.draw = draw_segment
pm.DampedSpring.draw = draw_link
gap = 5
walls = (
((gap, py5.height - gap), (py5.width - gap, py5.height - gap)),
((gap, py5.height - gap), (gap, gap)),
((py5.width - gap, py5.height - gap), (py5.width - gap, gap)),
)
for pa, pb in walls:
wall = pm.Segment(space.static_body, pa, pb, 2)
wall.friction = 0.2
space.add(wall)
def draw():
py5.background(0, 100, 20)
py5.no_stroke()
py5.fill(255, 100)
for shp in space.shapes:
shp.draw()
py5.stroke(0)
for link in constraints:
link.draw()
if current_poly:
py5.fill(255, 0, 0, 100)
with py5.begin_closed_shape():
py5.vertices(current_poly)
space.step(0.01)
def draw_poly(obj):
with py5.push_matrix():
py5.translate(obj.body.position.x, obj.body.position.y)
py5.rotate(obj.body.angle)
pts = obj.get_vertices()
with py5.begin_closed_shape():
py5.vertices(pts)
def draw_segment(obj):
with py5.push():
py5.stroke(255)
py5.stroke_weight(obj.radius*2)
py5.line(obj.a.x, obj.a.y, obj.b.x, obj.b.y)
def draw_link(obj):
xa, ya = obj.a.position
xb, yb = obj.b.position
py5.line(xa, ya, xb, yb)
def draw_circle(obj):
py5.circle(obj.body.position.x,
obj.body.position.y,
obj.radius * 2)
def build_polybody(poly):
(xa, ya), (xb, yb) = min_max(poly)
centroid = (xa + xb) / 2, (ya + yb) / 2
cx, cy = centroid
poly = [(x - cx, y - cy) for x, y in poly]
mass = poly_area(poly) * 0.1
moi = pm.moment_for_poly(mass, poly)
body = pm.Body(mass, moi)
body.position = centroid
shp = pm.Poly(body, poly)
shp.friction = 0.2
space.add(body, shp)
def build_softbody(x, y, cols, rows, ex, radius, mass): # ex é o espaçamento
ey = py5.sqrt(3) * 0.5 * ex
moi = pm.moment_for_circle(mass, 0, radius, (0, 0))
bodies = []
for j in range(rows):
p = j % 2
for i in range(cols + p):
body = pm.Body(mass, moi)
body.position = (x - (p * 0.5 * ex) + i * ex,
y + j * ey)
shp = pm.Circle(body, radius)
shp.elasticity = 0.6
shp.friction = 0.6
space.add(body, shp)
bodies.append(body)
k = 0
for j in range(rows - 1):
p = j % 2
for i in range(cols + p):
jleft = jright = 1
if i == 0 and p:
jleft = 0
if i == cols and p:
jright = 0
if jleft:
link = pm.DampedSpring(bodies[k], bodies[k + cols],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
if jright:
link = pm.DampedSpring(bodies[k], bodies[k + cols + 1],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
if p or (not p and i < cols - 1):
link = pm.DampedSpring(bodies[k], bodies[k + 1],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
k += 1
p = rows % 2
for i in range(cols - p):
link = pm.DampedSpring(bodies[k], bodies[k + 1],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
k += 1
def min_max(pts):
"""
Return two tuples with the most extreme coordinates,
resulting in "bounding box" corners.
"""
coords = tuple(zip(*pts))
return tuple(map(min, coords)), tuple(map(max, coords))
def poly_area(pts):
pts = list(pts)
area = 0.0
for (ax, ay), (bx, by) in zip(pts, pts[1:] + [pts[0]]):
area += ax * by
area -= bx * ay
return abs(area) / 2.0
def key_pressed():
if py5.key == ' ':
for obj in reversed(space.shapes):
if not is_segment(obj):
space.remove(obj)
def mouse_pressed():
if py5.mouse_button == py5.RIGHT:
build_softbody(py5.mouse_x, py5.mouse_y, 8, 4, 20, 10, mass)
def mouse_dragged():
current_poly.append((py5.mouse_x, py5.mouse_y))
def mouse_released():
if len(current_poly) > 3:
build_polybody(current_poly[:])
current_poly[:] = []
def is_circle(obj): return isinstance(obj, pm.Circle)
def is_poly(obj): return isinstance(obj, pm.Poly)
def is_segment(obj): return isinstance(obj, pm.Segment)
py5.run_sketch()
# You'll need py5 - made to run in py5 imported mode, more info at https://py5.ixora.io
# Also needs http://pymunk.org
import pymunk as pm
space = pm.Space()
space.gravity = (0, 600)
shapes, bodies, constraints = [], [], []
polys = []
current_poly = []
stiffness = 600
damping = 20
mass = 0.5
def setup():
size(500, 500)
build_softbody(50, 50, 4, 5, 20, 8, mass)
build_softbody(150, 50, 5, 5, 20, 8, mass)
build_softbody(50, 150, 6, 5, 20, 8, mass)
walls = (
((0, height), (width, height)),
((0, height), (0, 0)),
((width, height), (width, 0)),
)
for pa, pb in walls:
wall = pm.Segment(space.static_body, pa, pb, 2)
space.add(wall)
def draw():
background(0, 0, 100)
stroke(255)
for link in constraints:
xa, ya = link.a.position
xb, yb = link.b.position
line(xa, ya, xb, yb)
no_stroke()
fill(255, 100)
for b in bodies:
circle(b.position.x, b.position.y, 16)
for shp in polys:
draw_poly(shp)
if current_poly:
fill(255, 0, 0, 100)
with begin_closed_shape():
vertices(current_poly)
space.step(0.01)
def draw_poly(obj):
push_matrix()
translate(obj.body.position.x, obj.body.position.y)
rotate(obj.body.angle)
pts = obj.get_vertices()
# begin_shape()
# for x, y in pts:
# vertex(x, y)
# end_shape(CLOSE)
with begin_closed_shape():
vertices(pts)
pop_matrix()
def mouse_pressed():
if mouse_button == RIGHT:
build_softbody(mouse_x, mouse_y, 4, 8, 20, 8, mass)
def mouse_dragged():
current_poly.append((mouse_x, mouse_y))
def mouse_released():
if len(current_poly) > 3:
raw_poly = current_poly[:]
(xa, ya), (xb, yb) = min_max(raw_poly)
centroid = (xa + xb) / 2, (ya + yb) / 2
cx, cy = centroid
poly = [(x - cx, y - cy) for x, y in raw_poly]
mass = poly_area(poly) * 0.1
moi = pm.moment_for_poly(mass, poly)
body = pm.Body(mass, moi)
body.position = centroid
shp = pm.Poly(body, poly)
space.add(body, shp)
polys.append(shp)
current_poly[:] = []
def build_softbody(x, y, col, row, ex, radius, mass): # ex é o espaçamento
ey = sqrt(3) * 0.5 * ex
moi = pm.moment_for_circle(mass, 0, radius, (0, 0))
body_count = len(bodies)
for j in range(col):
p = j % 2
for i in range(row + p):
body = pm.Body(mass, moi)
body.position = (x - (p * 0.5 * ex) + i * ex,
y + j * ey)
shp = pm.Circle(body, radius)
shp.elasticity = 0.6
shp.friction = 0.8
space.add(body, shp)
bodies.append(body)
shapes.append(shp)
k = body_count
for j in range(col - 1):
p = j % 2
for i in range(row + p):
jleft = jright = 1
if i == 0 and p:
jleft = 0
if i == row and p:
jright = 0
if jleft:
link = pm.DampedSpring(bodies[k], bodies[k + row],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
if jright:
link = pm.DampedSpring(bodies[k], bodies[k + row + 1],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
if p or (not p and i < row - 1):
link = pm.DampedSpring(bodies[k], bodies[k + 1],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
k += 1
p = col % 2
for i in range(row - p):
link = pm.DampedSpring(bodies[k], bodies[k + 1],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
k += 1
def min_max(points):
"""
Return two tuples with the most extreme coordinates,
resulting in "bounding box" corners.
"""
coords = tuple(zip(*points))
return tuple(map(min, coords)), tuple(map(max, coords))
def poly_area(pts):
pts = list(pts)
area = 0.0
for (ax, ay), (bx, by) in zip(pts, pts[1:] + [pts[0]]):
area += ax * by
area -= bx * ay
return abs(area) / 2.0
# You'll need py5 -https://py5.ixora.io
# Also needs http://pymunk.org
# this is the same as the previous sketch but using py5 "module mode"
import py5
import pymunk as pm
space = pm.Space()
space.gravity = (0, 600)
shapes, bodies, constraints = [], [], []
polys = []
current_poly = []
stiffness = 600
damping = 20
mass = 0.5
def setup():
py5.size(500, 500)
build_softbody(50, 50, 4, 5, 20, 8, mass)
build_softbody(150, 50, 5, 5, 20, 8, mass)
build_softbody(50, 150, 6, 5, 20, 8, mass)
walls = (
((0, py5.height), (py5.width, py5.height)),
((0, py5.height), (0, 0)),
((py5.width, py5.height), (py5.width, 0)),
)
for pa, pb in walls:
wall = pm.Segment(space.static_body, pa, pb, 2)
space.add(wall)
def draw():
py5.background(0, 0, 100)
py5.stroke(255)
for link in constraints:
xa, ya = link.a.position
xb, yb = link.b.position
py5.line(xa, ya, xb, yb)
py5.no_stroke()
py5.fill(255, 100)
for b in bodies:
py5.circle(b.position.x, b.position.y, 16)
for shp in polys:
draw_poly(shp)
if current_poly:
py5.fill(255, 0, 0, 100)
with py5.begin_closed_shape():
py5.vertices(current_poly)
space.step(0.01)
def draw_poly(obj):
py5.push_matrix()
py5.translate(obj.body.position.x, obj.body.position.y)
py5.rotate(obj.body.angle)
pts = obj.get_vertices()
with py5.begin_closed_shape():
py5.vertices(pts)
py5.pop_matrix()
def mouse_pressed():
if py5.mouse_button == py5.RIGHT:
build_softbody(py5.mouse_x, py5.mouse_y, 4, 8, 20, 8, mass)
def mouse_dragged():
current_poly.append((py5.mouse_x, py5.mouse_y))
def mouse_released():
if len(current_poly) > 3:
raw_poly = current_poly[:]
(xa, ya), (xb, yb) = min_max(raw_poly)
centroid = (xa + xb) / 2, (ya + yb) / 2
cx, cy = centroid
poly = [(x - cx, y - cy) for x, y in raw_poly]
mass = poly_area(poly) * 0.1
moi = pm.moment_for_poly(mass, poly)
body = pm.Body(mass, moi)
body.position = centroid
shp = pm.Poly(body, poly)
space.add(body, shp)
polys.append(shp)
current_poly[:] = []
def build_softbody(x, y, col, row, ex, radius, mass): # ex é o espaçamento
ey = py5.sqrt(3) * 0.5 * ex
moi = pm.moment_for_circle(mass, 0, radius, (0, 0))
body_count = len(bodies)
for j in range(col):
p = j % 2
for i in range(row + p):
body = pm.Body(mass, moi)
body.position = (x - (p * 0.5 * ex) + i * ex,
y + j * ey)
shp = pm.Circle(body, radius)
shp.elasticity = 0.6
shp.friction = 0.8
space.add(body, shp)
bodies.append(body)
shapes.append(shp)
k = body_count
for j in range(col - 1):
p = j % 2
for i in range(row + p):
jleft = jright = 1
if i == 0 and p:
jleft = 0
if i == row and p:
jright = 0
if jleft:
link = pm.DampedSpring(bodies[k], bodies[k + row],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
if jright:
link = pm.DampedSpring(bodies[k], bodies[k + row + 1],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
if p or (not p and i < row - 1):
link = pm.DampedSpring(bodies[k], bodies[k + 1],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
k += 1
p = col % 2
for i in range(row - p):
link = pm.DampedSpring(bodies[k], bodies[k + 1],
(0, 0), (0, 0),
ex, stiffness, damping)
space.add(link)
constraints.append(link)
k += 1
def min_max(pts):
"""
Return two tuples with the most extreme coordinates,
resulting in "bounding box" corners.
"""
coords = tuple(zip(*pts))
return tuple(map(min, coords)), tuple(map(max, coords))
def poly_area(pts):
pts = list(pts)
area = 0.0
for (ax, ay), (bx, by) in zip(pts, pts[1:] + [pts[0]]):
area += ax * by
area -= bx * ay
return abs(area) / 2.0
py5.run_sketch()
@hx2A
Copy link

hx2A commented Mar 30, 2022

This is great!

Note py5 now lets you use the begin_closed_shape() and push_matrix() methods as context managers. There's also a vertices() method. You can use these to simplify the code a bit in a few places. For example:

def draw_poly(obj):
    with push_matrix():
        translate(obj.body.position.x, obj.body.position.y)
        rotate(obj.body.angle)
        with begin_closed_shape():
            vertices(obj.get_vertices())

The vertices() method was designed to accept numpy arrays as inputs, with one vertex in each row. It will also work for things like a list of tuples, etc.

If the number of vertices is large, this will also be much faster.

@villares
Copy link
Author

Thanks @hx2A! I'll try to incorporate your nice suggestions!

@hx2A
Copy link

hx2A commented Mar 30, 2022

@villares , the context manager idea was your suggestion!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment