Last active
October 17, 2024 14:18
Revisions
-
xenobrain revised this gist
Oct 17, 2024 . No changes.There are no files selected for viewing
-
xenobrain created this gist
Apr 19, 2023 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,331 @@ 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 hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,332 @@ 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 hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,364 @@ 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