Last active
April 14, 2023 13:08
-
-
Save xenobrain/bf5d49269701b863aaf757e7388c29f3 to your computer and use it in GitHub Desktop.
Simple Bouncing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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