Created
August 30, 2023 23:58
-
-
Save owenbutler/199642018394f0d6771087dff59116f0 to your computer and use it in GitHub Desktop.
DR simple Verlet with collisions
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 characters
| require "xenobrain/ruby_vectormath/vectormath_2d.rb" | |
| $sub_steps = 2 | |
| $all_objects = [] | |
| $frame_elapsed_time = 0.0 | |
| $gravity = Vec2.new(0.0, -1000.0) | |
| $size = 20.0 | |
| $spawn_timer = 0 | |
| $spawn_rate = 2 | |
| $field_width = 600 | |
| $field_height = 600 | |
| def tick args | |
| frame_start_time = Time.now | |
| args.outputs.background_color = [0, 0, 0] | |
| if args.tick_count == 0 | |
| setup args | |
| end | |
| delta_time = 16.to_f / $sub_steps.to_f / 1000.0 | |
| collision_time_total = 0 | |
| update_time_total = 0 | |
| $sub_steps.times do | |
| t = Time.now | |
| collisions | |
| collision_time_total += Time.now - t | |
| t = Time.now | |
| update_all delta_time | |
| update_time_total += Time.now - t | |
| end | |
| collision_time_total = (collision_time_total * 1000).to_i | |
| update_time_total = (update_time_total * 1000).to_i | |
| if $frame_elapsed_time < 0.014 && $spawn_timer.elapsed? | |
| $all_objects << new_object(20, 450, 8.9, 2.1) | |
| $all_objects << new_object(35, 450 + $size + 3, 8.8, 2.3) | |
| $spawn_timer = args.tick_count + $spawn_rate | |
| end | |
| if click = args.inputs.mouse.click | |
| # spawn an object | |
| $all_objects << new_object(click.x, click.y, 0, 0) | |
| end | |
| render args | |
| args.outputs.labels << { | |
| x: 520, | |
| y: 700, | |
| text: "s:#{args.gtk.current_framerate_calc} n:#{$all_objects.size} c:#{collision_time_total} u:#{update_time_total} t:#{($frame_elapsed_time * 1000).to_i}", | |
| size_enum: 1, | |
| vertical_alignment_enum: 1, | |
| r: 255, g: 255, b: 255 | |
| } | |
| args.outputs.primitives << args.gtk.current_framerate_primitives | |
| $frame_elapsed_time = Time.now - frame_start_time | |
| end | |
| def new_object x, y, xvel, yvel | |
| { | |
| position: Vec2.new(x, y), | |
| last_position: Vec2.new(x - xvel, y - yvel), | |
| acceleration: Vec2.new, | |
| x: x + (1280 - $field_width) / 2, y: y + (720 - $field_height) / 2, | |
| w: $size, h: $size, | |
| anchor_x: 0.5, anchor_y: 0.5, | |
| path: 'sprites/circle.png', | |
| r: Math::sin($gtk.args.tick_count / 47).remap(-1, 1, 0, 255), | |
| g: Math::cos($gtk.args.tick_count / 17).remap(-1, 1, 0, 255), | |
| b: Math::sin($gtk.args.tick_count / 22).remap(-1, 1, 0, 255), | |
| } | |
| end | |
| def render args | |
| args.outputs.sprites << $all_objects | |
| args.outputs.borders << { | |
| x: (1280 - $field_width) / 2, | |
| y: (720 - $field_width) / 2, | |
| w: $field_width, | |
| h: $field_height, | |
| r: 255, | |
| } | |
| end | |
| def setup args | |
| end | |
| def collisions | |
| collisions_vertical_grid_with_find_intersect | |
| end | |
| def collisions_vertical_grid_with_find_intersect | |
| grid_size = ($field_width / $size).to_i | |
| grid = Array.new($field_width / $size) { Array.new } | |
| Fn.each $all_objects do |object| | |
| grid_index = (object[:position].x / $size).to_i | |
| grid[grid_index].push(object) | |
| end | |
| grid.each_with_index do |col, index| | |
| next if col.empty? | |
| if index == 0 | |
| check_columns(col, grid[index+1]) | |
| elsif index == grid_size - 1 | |
| check_columns(col, grid[index-1]) | |
| else | |
| check_columns(col, grid[index+1]) | |
| check_columns(col, grid[index-1]) | |
| end | |
| check_columns(col, col) | |
| end | |
| end | |
| def check_columns col1, col2 | |
| return if col2.empty? | |
| r = $size / 2.0 | |
| quick_check = (r + r) * (r + r) | |
| i = 0 | |
| col1_length = col1.length | |
| while i < col1_length | |
| object_to_check = col1[i] | |
| potential_outer_collisions = GTK::Geometry.find_all_intersect_rect object_to_check, col2 | |
| collision_index = 0 | |
| potential_collisions_length = potential_outer_collisions.length | |
| while collision_index < potential_collisions_length | |
| potential_collision = potential_outer_collisions[collision_index] | |
| if object_to_check != potential_collision | |
| outer_position = object_to_check[:position] | |
| inner_position = potential_collision[:position] | |
| diff = outer_position - inner_position | |
| d2 = diff.length_sq | |
| # d2 = diff.x * diff.x + diff.y * diff.y | |
| if d2 < quick_check | |
| distance = Math.sqrt(d2) | |
| delta = ($size - distance) * 0.5 | |
| half_distance = diff.div_scalar!(distance).mul_scalar!(delta) | |
| # half_distance = n.mul_scalar(delta) | |
| outer_position.add!(half_distance) | |
| inner_position.sub!(half_distance) | |
| end | |
| end | |
| collision_index += 1 | |
| end | |
| i += 1 | |
| end | |
| end | |
| def update_all delta_time | |
| dts = delta_time * delta_time | |
| Fn.each $all_objects do |obj| | |
| # apply gravity | |
| obj[:acceleration].add!($gravity) | |
| # calculate the last move | |
| last_move = obj[:position] - obj[:last_position] | |
| # find the new position using verlet | |
| new_position = obj[:position] + last_move + obj[:acceleration].mul_scalar(dts) | |
| # save the last position for next time | |
| obj[:last_position] = obj[:position] | |
| # clamp the position to the bounds of a box (screen) | |
| new_position.max!(Vec2.new($size / 2.0, $size / 2.0)).min!(Vec2.new($field_width - ($size / 2), $field_height - ($size / 2))) | |
| # save the new position | |
| obj[:position] = new_position | |
| # reset any forces (gravity etc) | |
| obj[:acceleration].set!(0.0, 0.0) | |
| # set to screen space | |
| obj[:x] = new_position.x + (1280 - $field_width) / 2 | |
| obj[:y] = new_position.y + (720 - $field_height) / 2 | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment