Skip to content

Instantly share code, notes, and snippets.

@xenobrain
Last active April 14, 2023 13:08
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 xenobrain/bf5d49269701b863aaf757e7388c29f3 to your computer and use it in GitHub Desktop.
Save xenobrain/bf5d49269701b863aaf757e7388c29f3 to your computer and use it in GitHub Desktop.
Simple Bouncing
def tick args
# Create some circles
args.state.circles ||= 10.map_with_ys 2 do |x, y|
radius = 32
diameter = radius * 2
# for static objects use mass = Float::INFINITY
mass = Math::PI * radius * radius
path = 'sprites/circle/blue.png'
offset_x = (1280 - ((diameter + radius) * 10)) / 2
{
x: offset_x + x * (diameter + radius),
y: 500 + y * (diameter + radius),
vx: 0,
vy: 0,
w: diameter,
h: diameter,
path: path,
mass: mass, #
bounce: 0.95, # 0..1
radius: radius
}
end
# Create some borders
args.state.borders ||= [
{ x: 0, y: 20, x2: 1280, y2: 0, bounce: 0.8 },
{ x: 0, y: 20, x2: 0, y2: 720, bounce: 0.5 },
{ x: 1280, y: 0, x2: 1280, y2: 720, bounce: 0.5 },
{ x: 0, y: 720, x2: 1280, y2: 720, bounce: 0.5 },
{ x: 640, y: 360, x2: 700, y2: 200, bounce: 0.8 },
{ x: 200, y: 400, x2: 360, y2: 150, bounce: 0.95 }
]
# Apply gravity
args.state.circles.each do |c|
c.vy -= 0.1
end
# Collision Tests
circles = args.state.circles
num_circles = circles.length
lines = args.state.borders
num_lines = lines.length
i = 0
while i < circles.length
a = circles[i]
# Find collision with other circles
# Don't check pairs that have already been tested
j = i + 1
while j < num_circles
b = circles[j]
collision = find_circle_circle a, b
calc_collision collision, a, b if collision
j += 1
end
# Find collision with borders
j = 0
while j < num_lines
b = lines[j]
collision = find_circle_line a, b
calc_collision collision, a, b if collision
j += 1
end
i += 1
end
# Update positions
args.state.circles.each do |c|
c.x += c.vx
c.y += c.vy
end
# Draw
args.outputs.lines << args.state.borders
args.outputs.sprites << args.state.circles
end
def find_circle_circle a, b
circle_ar = a.radius || [a.w, a.h].max * 0.5
circle_br = b.radius || [b.w, b.h].max * 0.5
circle_ax = a.x + circle_ar
circle_ay = a.y + circle_ar
circle_bx = b.x + circle_br
circle_by = b.y + circle_br
dx = circle_bx - circle_ax
dy = circle_by - circle_ay
distance = dx * dx + dy * dy
min_distance = circle_ar + circle_br
# distance should be less than the sum of radii
# zero distance means the circles have the same centers, do nothing
return if (distance > min_distance * min_distance) || distance.zero?
distance = Math.sqrt distance
dx /= distance
dy /= distance
contact = { x: circle_ax + circle_ar * dx,
y: circle_ay + circle_ar * dy,
depth: distance - min_distance }
{ normal_x: dx, normal_y: dy, contact: contact }
end
def find_circle_line c, l
circle_r = c.radius || [c.w, c.h].max * 0.5
line_r = l.radius || 0
circle_x = c.x + circle_r
circle_y = c.y + circle_r
line_x = l.x2 - l.x
line_y = l.y2 - l.y
t = ((line_x * (circle_x - l.x) + line_y * (circle_y - l.y)) /
(line_x * line_x + line_y * line_y)).clamp(0, 1)
closest_x = l.x + line_x * t
closest_y = l.y + line_y * t
dx = closest_x - circle_x
dy = closest_y - circle_y
distance = dx * dx + dy * dy
min_distance = circle_r + line_r
return if distance > min_distance * min_distance
distance = Math.sqrt distance
dx /= distance
dy /= distance
contact = { x: circle_x + circle_r * dx,
y: circle_y + circle_r * dy,
depth: distance - min_distance }
{ normal_x: dx, normal_y: dy, contact: contact }
end
def calc_collision collision, a, b
a_inv_mass = 1 / (a.mass || Float::INFINITY)
b_inv_mass = 1 / (b.mass || Float::INFINITY)
normal_x = collision.normal_x
normal_y = collision.normal_y
contact = collision.contact
depth = contact.depth
# Positional correction
correction = -depth / (a_inv_mass + b_inv_mass)
correction_x = normal_x * correction
correction_y = normal_y * correction
a.x -= correction_x * a_inv_mass
a.y -= correction_y * a_inv_mass
b.x += correction_x * b_inv_mass
b.y += correction_y * b_inv_mass
# Coefficient of Restitution
bounce = Math.sqrt (a.bounce || 0) * (b.bounce || 0)
# Relative velocity in normal direction
# Return if bodies are separating
velocity = ((b.vx || 0) - (a.vx || 0)) * normal_x + ((b.vy || 0) - (a.vy || 0)) * normal_y
return if velocity.positive?
# Impulse scale
p = (-(1 + bounce) * velocity) / (a_inv_mass + b_inv_mass)
impulse_x = normal_x * p
impulse_y = normal_y * p
a.vx -= impulse_x * a_inv_mass if a.vx
a.vy -= impulse_y * a_inv_mass if a.vy
b.vx += impulse_x * b_inv_mass if b.vx
b.vy += impulse_y * b_inv_mass if b.vy
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment