Last active
October 17, 2024 14:18
-
-
Save xenobrain/46356970992a3dd2fcb5a776ca60fe8b to your computer and use it in GitHub Desktop.
Physics stuff
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
DEG2RAD = Math::PI / 180.0 | |
def tick args | |
b = args.state.box_a ||= { x: 540, y: 360, w: 200, h: 200, angle: 45, path: 'sprites/square/blue.png' } | |
a = args.state.box_b ||= { x: 340, y: 360, w: 100, h: 100, angle: 45, path: 'sprites/square/green.png' } | |
$a = a | |
a.y += 1 if args.inputs.up | |
a.y -= 1 if args.inputs.down | |
a.x -= 1 if args.inputs.left | |
a.x += 1 if args.inputs.right | |
a.angle -= 1 if args.inputs.keyboard.e | |
a.angle += 1 if args.inputs.keyboard.q | |
args.outputs.background_color = [32, 32, 32] | |
args.outputs.sprites << b << a | |
start = Time.now | |
100.times do | |
p = closest_points a, b | |
#p = find_collision_polygon_polygon a, b | |
end | |
stop = (Time.now - start) * 1000 | |
#if p | |
# args.outputs.sprites << p.contacts.map do |c| | |
# args.outputs.sprites << { x: c.r1x - 4, y: c.r1y - 4, w: 8, h: 8, path: 'sprites/circle/yellow.png' } | |
# args.outputs.sprites << { x: c.r2x - 4, y: c.r2y - 4, w: 8, h: 8, path: 'sprites/circle/orange.png' } | |
# end | |
#end | |
#a.intersect_rect? b | |
#args.outputs.labels << { x: 10, y: 500, text: "#{stop}", r: 255, g: 255, b: 255 } | |
#puts stop | |
end | |
def furthest p, dx, dy | |
p_len = p.length | |
index = 0 | |
x, y = p[0] | |
max = x * dx + y * dy | |
i = 1 | |
while i < p_len | |
x, y = p[i] | |
d = x * dx + y * dy | |
if d > max | |
max = d | |
index = i | |
end | |
i += 1 | |
end | |
index | |
end | |
def closest_points a, b | |
dx = a.x - b.x | |
dy = a.y - b.y | |
a = create_vertices a | |
b = create_vertices b | |
## Initialize the Simplex ## | |
# Point A | |
index_a = furthest a, -dx, -dy | |
index_b = furthest b, dx, dy | |
a_id = (index_a & 0xFF) << 8 | (index_b & 0xFF) | |
ax, ay = a[index_a] | |
bx, by = b[index_b] | |
simplex_a = [bx - ax, by - ay, index_a, index_b, a_id] | |
# Point B | |
index_a = furthest a, dx, dy | |
index_b = furthest b, -dx, -dy | |
b_id = (index_a & 0xFF) << 8 | (index_b & 0xFF) | |
ax, ay = a[index_a] | |
bx, by = b[index_b] | |
simplex_b = [bx - ax, by - ay, index_a, index_b, b_id] | |
## Iterations ## | |
while true | |
simplex_ax, simplex_ay, a_index_a, a_index_b, a_id = simplex_a | |
simplex_bx, simplex_by, b_index_a, b_index_b, b_id = simplex_b | |
# Preserve winding order by flipping the points | |
if (simplex_by - simplex_ay) * (simplex_ax + simplex_bx) < (simplex_bx - simplex_ax) * (simplex_ay + simplex_by) | |
t = simplex_a | |
simplex_a = simplex_b | |
simplex_b = t | |
next | |
end | |
# Find closest point on the line to the origin | |
line_x = simplex_bx - simplex_ax | |
line_y = simplex_by - simplex_ay | |
closest_t = -((line_x * (simplex_ax + simplex_bx) + line_y * (simplex_ay + simplex_by)) / | |
(line_x * line_x + line_y * line_y)).clamp(-1, 1) | |
# If the origin projection is in Voronoi region AB, the search direction is perpendicular to the simplex | |
if closest_t > -1 && closest_t < 1 | |
dy = (simplex_bx - simplex_ax) | |
dx = -(simplex_by - simplex_ay) | |
else | |
# The point is in Voronoi region A or B, lerp along the simplex towards the origin | |
closest_t *= 0.5 | |
dx = -(simplex_ax * (0.5 - closest_t) + simplex_bx * (0.5 + closest_t)) | |
dy = -(simplex_ay * (0.5 - closest_t) + simplex_by * (0.5 + closest_t)) | |
end | |
# Point C | |
index_a = furthest a, -dx, -dy | |
index_b = furthest b, dx, dy | |
id = (index_a & 0xFF) << 8 | (index_b & 0xFF) | |
break if id == a_id || id == b_id || index_a == a_index_a || index_a == b_index_a || index_b == a_index_b || index_b == b_index_b | |
ax, ay = a[index_a] | |
bx, by = b[index_b] | |
simplex_c = [bx - ax, by - ay, index_a, index_b, id] | |
simplex_cx, simplex_cy = simplex_c | |
# If CA and BC are to the right of the origin, the simplex contains the origin | |
if ((simplex_ay - simplex_cy) * (simplex_cx + simplex_ax) > (simplex_ax - simplex_cx) * (simplex_cy + simplex_ay)) && | |
((simplex_cy - simplex_by) * (simplex_bx + simplex_cx) > (simplex_cx - simplex_bx) * (simplex_by + simplex_cy)) | |
$gtk.args.outputs.labels << { x: 10, y: 600, text: 'EPA', r: 255, g: 255, b: 255 } | |
break | |
end | |
# Simplex A and B are closer to the origin than Simplex C | |
na = simplex_ax * dx + simplex_ay * dy | |
nb = simplex_bx * dx + simplex_by * dy | |
break if (simplex_cx * dx + simplex_cy * dy) <= (na > nb ? na : nb) | |
## Drop the furthest point from the origin ## | |
# distance from origin A, C | |
line_x = simplex_cx - simplex_ax | |
line_y = simplex_cy - simplex_ay | |
closest_t = -((line_x * (simplex_ax + simplex_cx) + line_y * (simplex_ay + simplex_cy)) / | |
(line_x * line_x + line_y * line_y)).clamp(-1, 1) * 0.5 | |
x = simplex_ax * (0.5 - closest_t) + simplex_cx * (0.5 + closest_t) | |
y = simplex_ay * (0.5 - closest_t) + simplex_cy * (0.5 + closest_t) | |
ac = x * x + y * y | |
# distance from origin C, B | |
line_x = simplex_bx - simplex_cx | |
line_y = simplex_by - simplex_cy | |
closest_t = -((line_x * (simplex_cx + simplex_bx) + line_y * (simplex_cy + simplex_by)) / | |
(line_x * line_x + line_y * line_y)).clamp(-1, 1) * 0.5 | |
x = simplex_cx * (0.5 - closest_t) + simplex_bx * (0.5 + closest_t) | |
y = simplex_cy * (0.5 - closest_t) + simplex_by * (0.5 + closest_t) | |
cb = x * x + y * y | |
ac < cb ? simplex_b = simplex_c : simplex_a = simplex_c | |
end | |
a_ax, a_ay, a_index_a, a_index_b, id = simplex_a | |
b_ax, b_ay, b_index_a, b_index_b, id = simplex_b | |
# closest point on the simplex | |
line_x = simplex_bx - simplex_ax | |
line_y = simplex_by - simplex_ay | |
closest_t = -((line_x * (simplex_ax + simplex_bx) + line_y * (simplex_ay + simplex_by)) / | |
(line_x * line_x + line_y * line_y)).clamp(-1, 1) * 0.5 | |
#closest_sx = a_ax * (0.5 - closest_t) + a_bx * (0.5 + closest_t) | |
#closest_sy = a_ay * (0.5 - closest_t) + a_by * (0.5 + closest_t) | |
# interpolate original points to get the closest points | |
a_ax, a_ay = a[a_index_a] | |
b_ax, b_ay = a[b_index_a] | |
closest_ax = a_ax * (0.5 - closest_t) + b_ax * (0.5 + closest_t) | |
closest_ay = a_ay * (0.5 - closest_t) + b_ay * (0.5 + closest_t) | |
a_bx, a_by = b[a_index_b] | |
b_bx, b_by = b[b_index_b] | |
closest_bx = a_bx * (0.5 - closest_t) + b_bx * (0.5 + closest_t) | |
closest_by = a_by * (0.5 - closest_t) + b_by * (0.5 + closest_t) | |
$gtk.args.outputs.lines << { x: closest_ax, y: closest_ay, x2: closest_bx, y2: closest_by, g: 255 } | |
end | |
def create_vertices rect | |
x = rect.x; y = rect.y | |
w = rect.w; h = rect.h | |
cx = x + w * 0.5; cy = y + h * 0.5 | |
sin = Math.sin (rect.angle || 0.0) * DEG2RAD; cos = Math.cos (rect.angle || 0.0) * DEG2RAD | |
[ | |
[(x - cx) * cos - (y - cy) * sin + cx, (x - cx) * sin + (y - cy) * cos + cy], # Bottom Left | |
[(x + w - cx) * cos - (y - cy) * sin + cx, (x + w - cx) * sin + (y - cy) * cos + cy], # Bottom Right | |
[(x + w - cx) * cos - (y + h - cy) * sin + cx, (x + w - cx) * sin + (y + h - cy) * cos + cy], # Top Right | |
[(x - cx) * cos - (y + h - cy) * sin + cx, (x - cx) * sin + (y + h - cy) * cos + cy] # Top Left | |
].reverse | |
end | |
def find_collision_polygon_polygon a, b | |
a = create_vertices a | |
b = create_vertices b | |
## Find Minimum Separation and Reference Edge ## | |
separation = -Float::INFINITY | |
k = 0 | |
while k < 2 | |
a_length = a.length | |
b_length = b.length | |
i = 0 | |
while i < b_length | |
v1x, v1y = b[(i + 1) % b_length] | |
v0x, v0y = b[i] | |
nx = -(v1y - v0y) | |
ny = v1x - v0x | |
l = 1.0 / (Math.sqrt nx * nx + ny * ny) | |
nx *= l | |
ny *= l | |
v2x, v2y = a[0] | |
min_projection = (v2x - v0x) * nx + (v2y - v0y) * ny | |
j = 1 | |
while j < a_length | |
v2x, v2y = a[j] | |
projection = (v2x - v0x) * nx + (v2y - v0y) * ny | |
min_projection = projection if projection < min_projection | |
j += 1 | |
end | |
# early exit if the axis are separating | |
return if min_projection >= 0 | |
# bias to improve coherence | |
if min_projection * 0.95 > separation + 0.01 | |
separation = min_projection | |
reference_edge_ax = v0x | |
reference_edge_ay = v0y | |
reference_edge_bx = v1x | |
reference_edge_by = v1y | |
incident_shape = a | |
normal_x = nx | |
normal_y = ny | |
end | |
i += 1 | |
end | |
# swap shapes for the next test | |
t = a | |
a = b | |
b = t | |
k += 1 | |
end | |
## Find Incident Edge ## | |
i_length = incident_shape.length | |
min_projection = Float::INFINITY | |
i = 0 | |
while i < i_length | |
v1x, v1y = incident_shape[(i + 1) % i_length] | |
v0x, v0y = incident_shape[i] | |
nx = -(v1y - v0y) | |
ny = (v1x - v0x) | |
projection = nx * normal_x + ny * normal_y | |
if projection < min_projection | |
min_projection = projection | |
incident_edge_ax = v0x | |
incident_edge_ay = v0y | |
incident_edge_bx = v1x | |
incident_edge_by = v1y | |
end | |
i += 1 | |
end | |
## Clipping ## | |
contacts = [] | |
dist_reference_a = reference_edge_ax * normal_y - reference_edge_ay * normal_x | |
dist_reference_b = reference_edge_bx * normal_y - reference_edge_by * normal_x | |
dist_incident_a = incident_edge_ax * normal_y - incident_edge_ay * normal_x | |
dist_incident_b = incident_edge_bx * normal_y - incident_edge_by * normal_x | |
reference_denominator = 1.0 / (dist_reference_b - dist_reference_a) | |
incident_denominator = 1.0 / (dist_incident_b - dist_incident_a) | |
# project the segment ends onto the other segment, clamping t | |
t_ibra = ((dist_incident_b - dist_reference_a) * reference_denominator).clamp(0, 1) | |
t_raia = ((dist_reference_a - dist_incident_a) * incident_denominator).clamp(0, 1) | |
t_iara = ((dist_incident_a - dist_reference_a) * reference_denominator).clamp(0, 1) | |
t_rbia = ((dist_reference_b - dist_incident_a) * incident_denominator).clamp(0, 1) | |
reference_point_x = reference_edge_ax + t_ibra * (reference_edge_bx - reference_edge_ax) | |
reference_point_y = reference_edge_ay + t_ibra * (reference_edge_by - reference_edge_ay) | |
incident_point_x = incident_edge_ax + t_raia * (incident_edge_bx - incident_edge_ax) | |
incident_point_y = incident_edge_ay + t_raia * (incident_edge_by - incident_edge_ay) | |
if ((incident_point_x - reference_point_x) * normal_x + (incident_point_y - reference_point_y) * normal_y) <= 0 | |
contacts << { r1x: reference_point_x, | |
r1y: reference_point_y, | |
r2x: incident_point_x, | |
r2y: incident_point_y, | |
depth: separation, | |
jn: 0, jt: 0 } | |
end | |
reference_point_x = reference_edge_ax.lerp reference_edge_bx, t_iara | |
reference_point_y = reference_edge_ay.lerp reference_edge_by, t_iara | |
incident_point_x = incident_edge_ax.lerp incident_edge_bx, t_rbia | |
incident_point_y = incident_edge_ay.lerp incident_edge_by, t_rbia | |
if ((incident_point_x - reference_point_x) * normal_x + (incident_point_y - reference_point_y) * normal_y) <= 0 | |
contacts << { r1x: reference_point_x, | |
r1y: reference_point_y, | |
r2x: incident_point_x, | |
r2y: incident_point_y, | |
depth: separation, | |
jn: 0, jt: 0 } | |
end | |
{ a: a, ax: 0, ay: 0, b: b, bx: 0, by: 0, normal_x: normal_x, normal_y: normal_y, contacts: contacts } | |
end |
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
RAD2DEG = 180.0 / Math::PI | |
DEG2RAD = Math::PI / 180.0 | |
TIME_STEP = 0.2 / 60 # delta time isn't required in DragonRuby but it really handy for tuning and debugging physics | |
COLLISION_BIAS = 0.05 # adds some energy into the collision to get objects to separate. tune this in steps of 0.01 | |
COLLISION_SLOP = 0.1 # amount shapes are allowed to overlap without triggering correction. helps avoid position jitter | |
COLLISION_ITERATIONS = 10 # how many times to run the solver. a good range is between 5 and 15 | |
def tick args | |
args.gtk.reset if args.inputs.keyboard.r | |
box = args.state.box ||= { x: 300, y: 150, w: 50, h: 50, vx: 0, vy: 0, vw: 0, angle: 0, bounce: 0.8, friction: 0.8, mass: Math::PI*50*50, rotational_inertia: 0.83*(50*50+50*50) * (Math::PI*50*50), path: 'sprites/square/blue.png' } | |
ground = args.state.ground ||= { x: 100, y: 50, w: 1100, h: 50, vx: 0, vy: 0, vw: 0, bounce: 0.8, friction: 0.8, mass: Float::INFINITY, rotational_inertia: Float::INFINITY, path: 'sprites/square/green.png' } | |
box.vy -= 100.0 * TIME_STEP | |
args.outputs.sprites << ground | |
args.outputs.sprites << box | |
collision = find_collision_polygon_polygon box, ground | |
if collision | |
contact0 = collision.contacts[0] | |
contact1 = collision.contacts[1] | |
calc_collision collision | |
end | |
box.x += box.vx * TIME_STEP | |
box.y += box.vy * TIME_STEP | |
box.angle += box.vw * TIME_STEP * RAD2DEG | |
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 = { r1x: circle_ax + circle_ar * dx, | |
r1y: circle_ay + circle_ar * dy, | |
r2x: circle_bx - circle_br * dx, | |
r2y: circle_by - circle_br * dy, | |
depth: distance - min_distance, | |
jn: 0, jt: 0 } | |
{ a: a, ax: circle_ax, ay: circle_ay, | |
b: b, bx: circle_bx, by: circle_by, | |
normal_x: dx, normal_y: dy, | |
contacts: [contact] } | |
end | |
def find_circle_line c, l | |
circle_r = c.radius || [c.w, c.h].max * 0.5 | |
line_r = l.radius || 0.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.0, 1.0) | |
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.zero? | |
distance = Math.sqrt distance | |
dx /= distance | |
dy /= distance | |
contact = { r1x: circle_x + circle_r * dx, | |
r1y: circle_y + circle_r * dy, | |
r2x: closest_x - line_r * dx, | |
r2y: closest_y - line_r * dy, | |
depth: distance - min_distance, | |
jn: 0, jt: 0 } | |
{ a: c, ax: circle_x, ay: circle_y, | |
b: l, bx: line_x, by: line_y, | |
normal_x: dx, normal_y: dy, | |
contacts: [contact] } | |
end | |
def find_collision_polygon_polygon poly_a, poly_b | |
a_cx = poly_a.x + (poly_a.w/2) | |
a_cy = poly_a.y + (poly_a.h/2) | |
b_cx = poly_b.x + (poly_b.w/2) | |
b_cy = poly_b.y + (poly_b.h/2) | |
a = create_vertices poly_a | |
b = create_vertices poly_b | |
## Find Minimum Separation and Reference Edge ## | |
separation = -Float::INFINITY | |
k = 0 | |
while k < 2 | |
a_length = a.length | |
b_length = b.length | |
i = 0 | |
while i < b_length | |
v1x, v1y = b[(i + 1) % b_length] | |
v0x, v0y = b[i] | |
nx = -(v1y - v0y) | |
ny = v1x - v0x | |
l = 1.0 / (Math.sqrt nx * nx + ny * ny) | |
nx *= l | |
ny *= l | |
v2x, v2y = a[0] | |
min_projection = (v2x - v0x) * nx + (v2y - v0y) * ny | |
j = 1 | |
while j < a_length | |
v2x, v2y = a[j] | |
projection = (v2x - v0x) * nx + (v2y - v0y) * ny | |
min_projection = projection if projection < min_projection | |
j += 1 | |
end | |
# early exit if the axis are separating | |
return if min_projection >= 0 | |
# bias to improve coherence | |
if min_projection * 0.95 > separation + 0.01 | |
separation = min_projection | |
reference_edge_ax = v0x | |
reference_edge_ay = v0y | |
reference_edge_bx = v1x | |
reference_edge_by = v1y | |
incident_shape = a | |
normal_x = nx | |
normal_y = ny | |
end | |
i += 1 | |
end | |
# swap shapes for the next test | |
t = a | |
a = b | |
b = t | |
k += 1 | |
end | |
## Find Incident Edge ## | |
i_length = incident_shape.length | |
min_projection = Float::INFINITY | |
i = 0 | |
while i < i_length | |
v1x, v1y = incident_shape[(i + 1) % i_length] | |
v0x, v0y = incident_shape[i] | |
nx = -(v1y - v0y) | |
ny = (v1x - v0x) | |
projection = nx * normal_x + ny * normal_y | |
if projection < min_projection | |
min_projection = projection | |
incident_edge_ax = v0x | |
incident_edge_ay = v0y | |
incident_edge_bx = v1x | |
incident_edge_by = v1y | |
end | |
i += 1 | |
end | |
## Clipping ## | |
contacts = [] | |
dist_reference_a = reference_edge_ax * normal_y - reference_edge_ay * normal_x | |
dist_reference_b = reference_edge_bx * normal_y - reference_edge_by * normal_x | |
dist_incident_a = incident_edge_ax * normal_y - incident_edge_ay * normal_x | |
dist_incident_b = incident_edge_bx * normal_y - incident_edge_by * normal_x | |
reference_denominator = 1.0 / (dist_reference_b - dist_reference_a) | |
incident_denominator = 1.0 / (dist_incident_b - dist_incident_a) | |
# project the segment ends onto the other segment, clamping t | |
t_ibra = ((dist_incident_b - dist_reference_a) * reference_denominator).clamp(0, 1) | |
t_raia = ((dist_reference_a - dist_incident_a) * incident_denominator).clamp(0, 1) | |
t_iara = ((dist_incident_a - dist_reference_a) * reference_denominator).clamp(0, 1) | |
t_rbia = ((dist_reference_b - dist_incident_a) * incident_denominator).clamp(0, 1) | |
reference_point_x = reference_edge_ax + t_ibra * (reference_edge_bx - reference_edge_ax) | |
reference_point_y = reference_edge_ay + t_ibra * (reference_edge_by - reference_edge_ay) | |
incident_point_x = incident_edge_ax + t_raia * (incident_edge_bx - incident_edge_ax) | |
incident_point_y = incident_edge_ay + t_raia * (incident_edge_by - incident_edge_ay) | |
if ((incident_point_x - reference_point_x) * normal_x + (incident_point_y - reference_point_y) * normal_y) <= 0 | |
contacts << { r1x: reference_point_x, | |
r1y: reference_point_y, | |
r2x: incident_point_x, | |
r2y: incident_point_y, | |
depth: separation, | |
jn: 0, jt: 0 } | |
end | |
reference_point_x = reference_edge_ax.lerp reference_edge_bx, t_iara | |
reference_point_y = reference_edge_ay.lerp reference_edge_by, t_iara | |
incident_point_x = incident_edge_ax.lerp incident_edge_bx, t_rbia | |
incident_point_y = incident_edge_ay.lerp incident_edge_by, t_rbia | |
if ((incident_point_x - reference_point_x) * normal_x + (incident_point_y - reference_point_y) * normal_y) <= 0 | |
contacts << { r1x: reference_point_x, | |
r1y: reference_point_y, | |
r2x: incident_point_x, | |
r2y: incident_point_y, | |
depth: separation, | |
jn: 0, jt: 0 } | |
end | |
{ a: poly_a, ax: a_cx, ay: a_cy, b: poly_b, bx: b_cx, by: b_cy, normal_x: normal_x, normal_y: normal_y, contacts: contacts } | |
end | |
def calc_collision collision | |
return unless collision | |
a = collision[:a] | |
b = collision[:b] | |
nx = collision[:normal_x] | |
ny = collision[:normal_y] | |
average_bounce = a.bounce * b.bounce | |
average_friction = a.friction * b.friction | |
inv_m_a = 1.0 / a.mass | |
inv_m_b = 1.0 / b.mass | |
inv_i_a = 1.0 / a.rotational_inertia | |
inv_i_b = 1.0 / b.rotational_inertia | |
inv_mass_sum = inv_m_a + inv_m_b | |
fn.each collision.contacts do |contact| | |
# contact point in local space | |
r1x = contact[:r1x] - collision[:ax] | |
r1y = contact[:r1y] - collision[:ay] | |
r2x = contact[:r2x] - collision[:bx] | |
r2y = contact[:r2y] - collision[:by] | |
# contact point cross normal, tangent | |
r1cn = r1x * ny - r1y * nx | |
r2cn = r2x * ny - r2y * nx | |
r1ct = r1x * nx + r1y * ny | |
r2ct = r2x * nx + r2y * ny | |
# sum of masses in normal and tangent directions | |
mass_normal = 1.0 / (inv_mass_sum + inv_i_a * r1cn * r1cn + inv_i_b * r2cn * r2cn) | |
mass_tangent = 1.0 / (inv_mass_sum + inv_i_a * r1ct * r1ct + inv_i_b * r2ct * r2ct) | |
# penetration correction -- feed positional error into separation impulse (Baumgarte stabilization) | |
bias = COLLISION_BIAS * [0.0, contact[:depth] + COLLISION_SLOP].min / TIME_STEP | |
# relative velocity | |
rvx = b.vx - r2y * b.vw - (a.vx - r1y * a.vw) | |
rvy = b.vy + r2x * b.vw - (a.vy + r1x * a.vw) | |
# relative velocity along normal * average bounce | |
bounce = (rvx * nx + rvy * ny) * average_bounce | |
COLLISION_ITERATIONS.times do | |
# update the relative velocity | |
vrx = b.vx - r2y * b.vw - (a.vx - r1y * a.vw) | |
vry = b.vy + r2x * b.vw - (a.vy + r1x * a.vw) | |
# relative velocity along normal and tangent | |
rvn = vrx * nx + vry * ny | |
rvt = vrx * -ny + vry * nx | |
# impulse scalar (aka lambda, lagrange multiplier) | |
jn = -(bounce + rvn + bias) * mass_normal | |
previous_jn = contact[:jn] | |
contact[:jn] = [previous_jn + jn, 0.0].max | |
# tangent scalar, cannot exceed force along normal (Coulomb's law) | |
max_jt = average_friction * contact[:jn] | |
jt = -rvt * mass_tangent | |
previous_jt = contact[:jt] | |
contact[:jt] = (previous_jt + jt).clamp(-max_jt, max_jt) | |
jn = contact[:jn] - previous_jn | |
jt = contact[:jt] - previous_jt | |
impulse_x = nx * jn - ny * jt | |
impulse_y = nx * jt + ny * jn | |
a[:vx] -= impulse_x * inv_m_a | |
a[:vy] -= impulse_y * inv_m_a | |
a[:vw] -= inv_i_a * (r1x * impulse_y - r1y * impulse_x) | |
b[:vx] += impulse_x * inv_m_b | |
b[:vy] += impulse_y * inv_m_b | |
b[:vw] += inv_i_b * (r2x * impulse_y - r2y * impulse_x) | |
end | |
end | |
end | |
def create_vertices rect | |
x = rect.x; y = rect.y | |
w = rect.w; h = rect.h | |
cx = x + w * 0.5; cy = y + h * 0.5 | |
sin = Math.sin (rect.angle || 0.0) * DEG2RAD; cos = Math.cos (rect.angle || 0.0) * DEG2RAD | |
[ | |
[(x - cx) * cos - (y - cy) * sin + cx, (x - cx) * sin + (y - cy) * cos + cy], # Bottom Left | |
[(x + w - cx) * cos - (y - cy) * sin + cx, (x + w - cx) * sin + (y - cy) * cos + cy], # Bottom Right | |
[(x + w - cx) * cos - (y + h - cy) * sin + cx, (x + w - cx) * sin + (y + h - cy) * cos + cy], # Top Right | |
[(x - cx) * cos - (y + h - cy) * sin + cx, (x - cx) * sin + (y + h - cy) * cos + cy] # Top Left | |
].reverse | |
end |
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
module Physics | |
class << self | |
attr_accessor :debug_draw | |
INV_3 = 1.0 / 3.0 | |
NUM_ITERATIONS = 10 | |
MAX_GJK_ITERATIONS = 30 | |
MAX_EPA_ITERATIONS = 30 | |
@debug_draw = false | |
def new_polygon x, y, vertices, density = 1 | |
num_vertices = vertices.length | |
hull_vertices = [] | |
hull_indices = [] | |
moment_of_inertia = 0 | |
polygon_area = 0 | |
center_x = 0 | |
center_y = 0 | |
right = 0 | |
left = vertices.at(0).at(0) | |
i = 1 | |
while i < num_vertices | |
hx, hy = vertices.at(i) | |
if hx > left | |
left = hx | |
right = i | |
elsif hx == left | |
right = i if hy < vertices.at(right).at(1) | |
end | |
i += 1 | |
end | |
index = right | |
while true | |
next_index = 0 | |
hull_indices << index | |
hull_index = hull_indices.last | |
i = 1 | |
while i < num_vertices | |
if next_index == index | |
next_index = i | |
next | |
end | |
px, py = vertices.at(i) | |
hx, hy = vertices.at(hull_index) | |
nx, ny = vertices.at(next_index) | |
e1x = nx - hx | |
e1y = ny - hy | |
e2x = px - hx | |
e2y = py - hy | |
cross = e1x * e2y - e1y * e2x | |
next_index = i if cross.negative? | |
next_index = i if cross.zero? && (e2x*e2x + e2y*e2y) > (e1x*e1x + e1y*e1y) | |
i += 1 | |
end | |
index = next_index | |
break if next_index == right | |
end | |
num_vertices = hull_indices.length | |
i = 0 | |
while i < num_vertices | |
x0, y0 = vertices.at(hull_indices.at(i)) | |
x1, y1 = vertices.at(hull_indices.at((i + 1) % num_vertices)) | |
nx = y1 - y0 | |
ny = x0 - y1 | |
cross = x0*y1 - y0*x1 | |
triangle_area = 0.5*cross | |
polygon_area += triangle_area | |
center_x += triangle_area*INV_3*(x0 + x1) | |
center_y += triangle_area*INV_3*(y0 + y1) | |
moment_of_inertia += 0.25*INV_3*cross*(x0*x0 + x0*x1 + x1*x1 + y0*y0 + y0*y1 + y1*y1) | |
hull_vertices << [x0, y0, nx, ny] | |
i += 1 | |
end | |
center_x *= 1.0 / polygon_area | |
center_y *= 1.0 / polygon_area | |
min_x = min_y = Float::INFINITY | |
max_x = max_y = -Float::INFINITY | |
i = 0 | |
while i < num_vertices | |
vertex = hull_vertices.at i | |
px = vertex[0] -= center_x - x | |
py = vertex[1] -= center_y - y | |
min_x = px if px < min_x | |
min_y = py if py < min_y | |
max_x = px if px > max_x | |
max_y = py if py > max_y | |
i += 1 | |
end | |
w = max_x - min_x | |
h = max_y - min_y | |
inverse_mass = 1.0 / (density*polygon_area) | |
inverse_moment_of_inertia = 1.0 / moment_of_inertia | |
aabb = { x: min_x, y: min_y, w: w, h: h, tick: Kernel.tick_count } | |
hash = { | |
x: x, | |
y: y, | |
w: w, | |
h: h, | |
vertices: hull_vertices, | |
velocity_x: 0.0, | |
velocity_y: 0.0, | |
angular_velocity: 0.0, | |
aabb: aabb, | |
radius: [w, h].max * 0.5, | |
inverse_mass: inverse_mass, | |
inverse_moment_of_inertia: inverse_moment_of_inertia | |
} | |
puts hash.to_s | |
hash | |
end | |
def find_collision_polygon_polygon shape_a, shape_b | |
## Medium phase ## | |
radii_sum = shape_a[:radius] + shape_b[:radius] | |
dx = shape_b[:x] - shape_a[:x] | |
dy = shape_b[:y] - shape_a[:y] | |
distance = dx*dx + dy*dy | |
#return unless distance < radii_sum*radii_sum | |
current_tick = Kernel.tick_count | |
if shape_a[:inverse_mass].positive? && shape_a[:aabb][:tick] != current_tick | |
num_vertices = shape_a[:vertices].length | |
max_x = max_y = -Float::INFINITY | |
min_x = min_y = Float::INFINITY | |
aabb = shape_a[:aabb] | |
i = 0 | |
while i < num_vertices | |
x, y = shape_a[:vertices].at i | |
min_x = x if x < min_x | |
min_y = y if y < min_y | |
max_x = x if x > max_x | |
max_y = y if y > max_y | |
i += 1 | |
end | |
aabb[:x] = min_x | |
aabb[:y] = min_y | |
aabb[:w] = max_x - min_x | |
aabb[:h] = max_y - min_y | |
aabb[:tick] = current_tick | |
end | |
if shape_b[:inverse_mass].positive? && shape_b[:aabb][:tick] != current_tick | |
num_vertices = shape_b[:vertices].length | |
max_x = max_y = -Float::INFINITY | |
min_x = min_y = Float::INFINITY | |
aabb = shape_b[:aabb] | |
i = 0 | |
while i < num_vertices | |
x, y = shape_b[:vertices].at i | |
min_x = x if x < min_x | |
min_y = y if y < min_y | |
max_x = x if x > max_x | |
max_y = y if y > max_y | |
i += 1 | |
end | |
aabb[:x] = min_x | |
aabb[:y] = min_y | |
aabb[:w] = max_x - min_x | |
aabb[:h] = max_y - min_y | |
aabb[:tick] = current_tick | |
end | |
#return false unless $gtk.args.geometry.intersect_rect? shape_a[:aabb], shape_b[:aabb] | |
## Narrow phase ## | |
# Lookup cached points from the collision ID | |
# Collision ID is modified from default--for polys it stores the index of 2 minkowski points in a bitfield | |
support_x = 0 | |
support_y = 0 | |
support_id = 0 | |
support_point = proc do | |
max_ax = max_bx = max_ay = max_by = max_projection_a = max_projection_b = -Float::INFINITY | |
index_a = index_b = 0 | |
j = 0 | |
vertices = shape_a[:vertices] | |
num_vertices = vertices.length | |
while j < num_vertices | |
x, y = vertices.at j | |
projection = x*dx + y*dy | |
if projection > max_projection_a | |
max_projection_a = projection | |
index_a = j | |
max_ax = x | |
max_ay = y | |
end | |
j += 1 | |
end | |
j = 0 | |
vertices = shape_b[:vertices] | |
num_vertices = vertices.length | |
while j < num_vertices | |
x, y = vertices.at j | |
projection = x*dx + y*dy | |
if projection > max_projection_b | |
max_projection_b = projection | |
index_b = j | |
max_bx = x | |
max_by = y | |
end | |
j += 1 | |
end | |
support_x = max_ax - max_bx | |
support_y = max_ay - max_by | |
support_id = (index_a & 0xFF) << 8 | (index_b & 0xFF) | |
end | |
# Begin GJK Closest Points algorithm | |
temp = dx | |
dx = -dy | |
dy = temp | |
support_point.call | |
point_ax = support_x | |
point_ay = support_y | |
dx = -dx | |
dy = -dy | |
support_point.call | |
point_bx = support_x | |
point_by = support_y | |
i = 0 | |
while i < MAX_GJK_ITERATIONS | |
if (point_ay - point_by)*(point_bx + point_ax) > (point_ax - point_bx)*(point_by + point_ay) | |
temp = point_ax | |
point_ax = point_bx | |
point_bx = temp | |
temp = point_ay | |
point_by = point_ay | |
point_ay = temp | |
next | |
end | |
# To find the next search direction, interpolate segment ab towards the origin(0,0) of the simplex | |
sx = point_bx - point_ax | |
sy = point_by - point_ay | |
t = -((sx*(point_ax + point_bx) + sy*(point_ay + point_by)) / (sx*sx + sy*sy)).clamp(-1.0, 1.0) | |
if t > -1 && t < 1 | |
# t wasn't clamped, choose a perpendicular direction | |
dx = -(point_by - point_ay) | |
dy = point_bx - point_ax | |
else | |
# t was clamped, inverse of interpolate point_a, point_b towards the origin with t | |
t *= 0.5 | |
dx = -((point_ax*(0.5 - t)) + (point_bx*(0.5 + t))) | |
dy = -((point_ay*(0.5 - t)) + (point_by*(0.5 + t))) | |
end | |
# Get a new support point based on the current direction | |
support_point.call | |
point_cx = support_x | |
point_cy = support_y | |
if @debug_draw | |
cx = point_ax*0.5 + point_bx*0.5 | |
cy = point_ay*0.5 + point_by*0.5 | |
len = Math.sqrt(dx*dx + dy*dy) | |
nx = dx*len | |
ny = dy*len | |
cx2 = cx + nx*5.0 | |
cy2 = cy + ny*5.0 | |
# Draw center of the screen | |
$gtk.args.outputs.sprites << {x: 640-3, y: 360-3, w: 6, h: 6, path: 'sprites/circle/white.png' } | |
$gtk.args.outputs.lines << { x: point_ax + 640, y: point_ay + 360, x2: point_bx + 640, y2: point_by + 360, r: 255, g: 255, b: 255 } | |
#$gtk.args.outputs.lines << { x: cx + 640, y: cy + 360, x2: cx2 + 640, y2: cy2 + 360, r: 255, g: 0, b: 255 } | |
end | |
if (point_ay - point_cy)*(point_cx + point_ax) > (point_ax - point_cx)*(point_cy + point_ay) && | |
(point_cy - point_by)*(point_bx + point_cx) > (point_cx - point_bx)*(point_by + point_cy) | |
# The triangle three points contains the origin so we're overlapping | |
# Begin EPA algorithm | |
# For now, just return true that there was a collision | |
$gtk.args.outputs.labels << { x: 10, y: 600, text: 'EPA', r: 255, g: 255, b: 255 } | |
break | |
else | |
# check axis point_a, point_b, point_c, n | |
if (point_cx*dx + point_cy*dx) <= [(point_ax*dx + point_ay*dy), (point_bx*dx + point_by*dy)].max | |
break | |
else | |
# ClosestDist point_a, point_c < ClosestDistance point_c, point_b | |
# Closest_t for point_a, point_c | |
sx = point_cx - point_ax | |
sy = point_cy - point_ay | |
t = -((sx*(point_ax + point_bx) + sy*(point_ay + point_by)) / (sx*sx + sy*sy)).clamp(-1.0, 1.0) | |
t *= 0.5 | |
# lerp point_a, point_c | |
ac_x = (point_ax*(0.5 - t)) + (point_cx*(0.5 + t)) | |
ac_y = (point_ay*(0.5 - t)) + (point_cy*(0.5 + t)) | |
ac_dist = ac_x*ac_x + ac_y*ac_y | |
# closest_t for point_c, point_b | |
sx = point_bx - point_cx | |
sy = point_bx - point_cx | |
t = -((sx*(point_ax + point_bx) + sy*(point_ay + point_by)) / (sx*sx + sy*sy)).clamp(-1.0, 1.0) | |
t *= 0.5 | |
# lerp point_c, point_b | |
cb_x = (point_cx*(0.5 - t)) + (point_bx*(0.5 + t)) | |
cb_y = (point_cy*(0.5 - t)) + (point_by*(0.5 + t)) | |
cb_dist = cb_x*cb_x + cb_y*cb_y | |
if ac_dist < cb_dist | |
point_bx = point_cx | |
point_by = point_cy | |
else | |
point_ax = point_cx | |
point_ay = point_cy | |
end | |
end | |
end | |
i += 1 | |
end | |
if @debug_draw | |
$gtk.args.outputs.lines << { x: point_ax+640, y: point_ay+360, x2: point_bx+640, y2: point_by+360, r: 255, g: 0, b: 0 } | |
end | |
# For now just return false if we got here | |
false | |
end | |
def calc_collision collision | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment