From 0141b626d683f2730a971114241fabb7fec95cbf Mon Sep 17 00:00:00 2001 From: monkstone Date: Wed, 3 Dec 2014 16:50:55 +0000 Subject: [PATCH] add jbox2d examples --- .../ruby_gem/jbox2d/README.md | 3 + .../ruby_gem/jbox2d/bumpy_surface_noise.rb | 41 +++++ .../ruby_gem/jbox2d/data/java_args.txt | 1 + .../ruby_gem/jbox2d/lib/boundary.rb | 27 ++++ .../ruby_gem/jbox2d/lib/box.rb | 15 ++ .../ruby_gem/jbox2d/lib/custom_shape.rb | 111 +++++++++++++ .../ruby_gem/jbox2d/lib/particle_system.rb | 148 ++++++++++++++++++ .../ruby_gem/jbox2d/lib/surface.rb | 132 ++++++++++++++++ .../ruby_gem/jbox2d/liquid_fun_test.rb | 42 +++++ .../ruby_gem/jbox2d/liquidy.rb | 38 +++++ .../ruby_gem/jbox2d/polygons.rb | 39 +++++ .../ruby_gem/jbox2d/quick_test.rb | 137 ++++++++++++++++ .../ruby_gem/jbox2d/test_contact/README.md | 6 + .../jbox2d/test_contact/lib/boundary.rb | 29 ++++ .../test_contact/lib/custom_listener.rb | 29 ++++ .../jbox2d/test_contact/lib/particle.rb | 73 +++++++++ .../jbox2d/test_contact/test_contact.rb | 24 +++ 17 files changed, 895 insertions(+) create mode 100644 samples/external_library/ruby_gem/jbox2d/README.md create mode 100644 samples/external_library/ruby_gem/jbox2d/bumpy_surface_noise.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/data/java_args.txt create mode 100644 samples/external_library/ruby_gem/jbox2d/lib/boundary.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/lib/box.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/lib/custom_shape.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/lib/particle_system.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/lib/surface.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/liquid_fun_test.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/liquidy.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/polygons.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/quick_test.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/test_contact/README.md create mode 100644 samples/external_library/ruby_gem/jbox2d/test_contact/lib/boundary.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/test_contact/lib/custom_listener.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/test_contact/lib/particle.rb create mode 100644 samples/external_library/ruby_gem/jbox2d/test_contact/test_contact.rb diff --git a/samples/external_library/ruby_gem/jbox2d/README.md b/samples/external_library/ruby_gem/jbox2d/README.md new file mode 100644 index 0000000..fc85953 --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/README.md @@ -0,0 +1,3 @@ +### java_args.txt + +The `data` folder contains java_args.txt, which currently silently enables the OpenGl-based pipeline using `-Dsun.java2d.opengl=true` add any other java args here, use `True` instead of `true` to get a message. diff --git a/samples/external_library/ruby_gem/jbox2d/bumpy_surface_noise.rb b/samples/external_library/ruby_gem/jbox2d/bumpy_surface_noise.rb new file mode 100644 index 0000000..9a51ada --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/bumpy_surface_noise.rb @@ -0,0 +1,41 @@ +# The Nature of Code +# PBox2D example +# An uneven surface + +require 'pbox2d' +require_relative 'lib/surface' + +attr_reader :surface, :box2d, :particles + +def setup + size(500, 300) + smooth 4 + # Initialize box2d physics and create the world + @box2d = Box2D.new(self) + box2d.init_options(gravity: [0, -20]) + box2d.create_world + # to later set a custom gravity + # box2d.gravity([0, -20]) + # Create the empty list + @particles = [] + # Create the surface + @surface = Surface.new(box2d) +end + +def draw + # If the mouse is pressed, we make new particles + # We must always step through time! + background(138, 66, 54) + # Draw the surface + surface.display + # NB ? reqd to call mouse_pressed value, else method gets called. + particles << Particle.new(box2d, mouse_x, mouse_y, rand(2.0..6)) if mouse_pressed? + # Draw all particles + particles.each(&:display) + # Particles that leave the screen, we delete them + # (note they have to be deleted from both the box2d world and our list + particles.reject!(&:done) + # Just drawing the framerate to see how many particles it can handle + fill(0) + text("framerate: #{frame_rate.to_i}", 12, 16) +end diff --git a/samples/external_library/ruby_gem/jbox2d/data/java_args.txt b/samples/external_library/ruby_gem/jbox2d/data/java_args.txt new file mode 100644 index 0000000..a17e7c9 --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/data/java_args.txt @@ -0,0 +1 @@ +-Dsun.java2d.opengl=true diff --git a/samples/external_library/ruby_gem/jbox2d/lib/boundary.rb b/samples/external_library/ruby_gem/jbox2d/lib/boundary.rb new file mode 100644 index 0000000..90b89e8 --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/lib/boundary.rb @@ -0,0 +1,27 @@ +# The boundary class is used to create fixtures +class Boundary + include Processing::Proxy + attr_reader :box2d, :x, :y, :w, :h, :b + def initialize(b2d, x, y, w, h) + @box2d, @x, @y, @w, @h = b2d, x, y, w, h + sd = PolygonShape.new + box2d_w = box2d.scale_to_world(w / 2) + box2d_h = box2d.scale_to_world(h / 2) + sd.setAsBox(box2d_w, box2d_h) + # Create the body + bd = BodyDef.new + bd.type = BodyType::STATIC + bd.position.set(box2d.processing_to_world(x, y)) + @b = box2d.create_body(bd) + # Attached the shape to the body using a Fixture + b.create_fixture(sd, 1) + end + + # Draw the boundary, if it were at an angle we'd have to do something fancier + def display + fill(0) + stroke(0) + rect_mode(CENTER) + rect(x, y, w, h) + end +end diff --git a/samples/external_library/ruby_gem/jbox2d/lib/box.rb b/samples/external_library/ruby_gem/jbox2d/lib/box.rb new file mode 100644 index 0000000..5ff1c66 --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/lib/box.rb @@ -0,0 +1,15 @@ +# A Box class, note how to access class ParticleGroupDef in jruby +# which is currently not on an included path for pbox2d +class Box + attr_accessor :pg + def initialize(b2d, x, y) + w = rand(1..3) + h = rand(1..3) + shape = PolygonShape.new + pos = b2d.processing_to_world(x, y) + shape.setAsBox(w, h, pos, 0) + pd = Java::OrgJbox2dParticle::ParticleGroupDef.new + pd.shape = shape + @pg = b2d.world.create_particle_group(pd) + end +end \ No newline at end of file diff --git a/samples/external_library/ruby_gem/jbox2d/lib/custom_shape.rb b/samples/external_library/ruby_gem/jbox2d/lib/custom_shape.rb new file mode 100644 index 0000000..c83b45c --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/lib/custom_shape.rb @@ -0,0 +1,111 @@ +require 'pbox2d' + +class CustomShape + include Processing::Proxy + # We need to keep track of a Body and a width and height + attr_reader :body, :box2d + + # Constructor + def initialize(b2d, x, y) + # Add the box to the box2d world + @box2d = b2d + make_body(Vec2.new(x, y)) + end + + # This function removes the particle from the box2d world + def kill_body! + box2d.destroy_body(body) + end + + # Is the particle ready for deletion? + def done + # Let's find the screen position of the particle + pos = box2d.body_coord(body) + # Is it off the bottom of the screen? + return false unless pos.y > box2d.height + kill_body! + true + end + + # Drawing the box + def display + # We look at each body and get its screen position + pos = box2d.body_coord(body) + # Get its angle of rotation + a = body.get_angle + f = body.get_fixture_list + ps = f.get_shape + rect_mode(CENTER) + push_matrix + translate(pos.x, pos.y) + rotate(-a) + fill(175) + stroke(0) + begin_shape + # For every vertex, convert to pixel vector + ps.get_vertex_count.times do |i| + v = box2d.vector_to_processing(ps.get_vertex(i)) + vertex(v.x, v.y) + end + end_shape(CLOSE) + pop_matrix + end + + # This function adds the rectangle to the box2d world + def make_body(center) + # Define a polygon (this is what we use for a rectangle) + sd = PolygonShape.new + vertices = [] + vertices << box2d.vector_to_world(Vec2.new(-15, 25)) + vertices << box2d.vector_to_world(Vec2.new(15, 0)) + vertices << box2d.vector_to_world(Vec2.new(20, -15)) + vertices << box2d.vector_to_world(Vec2.new(-10, -10)) + sd.set(vertices.to_java(Java::OrgJbox2dCommon::Vec2), vertices.length) + # Define the body and make it from the shape + bd = BodyDef.new + bd.type = BodyType::DYNAMIC + bd.position.set(box2d.processing_to_world(center)) + @body = box2d.create_body(bd) + body.create_fixture(sd, 1.0) + # Give it some initial random velocity + body.set_linear_velocity(Vec2.new(rand(-5.0..5), rand(2.0..5))) + body.set_angular_velocity(rand(-5.0..5)) + end +end + +class Boundary + include Processing::Proxy + attr_reader :box2d, :b, :x, :y, :w, :h + def initialize(b2d, x, y, w, h, a) + @box2d, @x, @y, @w, @h = b2d, x, y, w, h + # Define the polygon + sd = PolygonShape.new + # Figure out the box2d coordinates + box2d_w = box2d.scale_to_world(w / 2) + box2d_h = box2d.scale_to_world(h / 2) + # We're just a box + sd.set_as_box(box2d_w, box2d_h) + # Create the body + bd = BodyDef.new + bd.type = BodyType::STATIC + bd.angle = a + bd.position.set(box2d.processing_to_world(x, y)) + @b = box2d.create_body(bd) + # Attached the shape to the body using a Fixture + b.create_fixture(sd, 1) + end + + # Draw the boundary, it doesn't move so we don't have to ask the Body for location + def display + fill(0) + stroke(0) + stroke_weight(1) + rect_mode(CENTER) + a = b.get_angle + push_matrix + translate(x, y) + rotate(-a) + rect(0, 0, w, h) + pop_matrix + end +end diff --git a/samples/external_library/ruby_gem/jbox2d/lib/particle_system.rb b/samples/external_library/ruby_gem/jbox2d/lib/particle_system.rb new file mode 100644 index 0000000..f05a0b6 --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/lib/particle_system.rb @@ -0,0 +1,148 @@ +require 'forwardable' + +module Runnable + def run + reject! { |item| item.done } + each { |item| item.display } + end +end + +class ParticleSystem + include Enumerable, Runnable + extend Forwardable + def_delegators(:@particles, :each, :reject!, :<<, :empty?) + def_delegator(:@particles, :empty?, :dead?) + + attr_reader :x, :y + + def initialize(bd, num, x, y) + @particles = [] # Initialize the Array + @x, @y = x, y # Store the origin point + num.times do + self << Particle.new(bd, x, y) + end + end + + def add_particles(bd, n) + n.times do + self << Particle.new(bd, x, y) + end + end +end + +# A Particle +require 'pbox2d' + +class Particle + include Processing::Proxy + TRAIL_SIZE = 6 + # We need to keep track of a Body + + attr_reader :trail, :body, :box2d + + # Constructor + def initialize(b2d, x, y) + @box2d = b2d + @trail = Array.new(TRAIL_SIZE, [x, y]) + # Add the box to the box2d world + # Here's a little trick, let's make a tiny tiny radius + # This way we have collisions, but they don't overwhelm the system + make_body(x, y, 0.2) + end + + # This function removes the particle from the box2d world + def kill_body + box2d.destroy_body(body) + end + + # Is the particle ready for deletion? + def done + # Let's find the screen position of the particle + pos = box2d.body_coord(body) + # Is it off the bottom of the screen? + return false unless (pos.y > box2d.height + 20) + kill_body + true + end + + # Drawing the box + def display + # We look at each body and get its screen position + pos = box2d.body_coord(body) + # Keep track of a history of screen positions in an array + (TRAIL_SIZE - 1).times do |i| + trail[i] = trail[i + 1] + end + trail[TRAIL_SIZE - 1] = [pos.x, pos.y] + # Draw particle as a trail + begin_shape + no_fill + stroke_weight(2) + stroke(0, 150) + trail.each do |v| + vertex(v[0], v[1]) + end + end_shape + end + + # This function adds the rectangle to the box2d world + def make_body(x, y, r) + # Define and create the body + bd = BodyDef.new + bd.type = BodyType::DYNAMIC + bd.position.set(box2d.processing_to_world(x, y)) + @body = box2d.create_body(bd) + # Give it some initial random velocity + body.set_linear_velocity(Vec2.new(rand(-1.0..1), rand(-1.0..1))) + # Make the body's shape a circle + cs = CircleShape.new + cs.m_radius = box2d.scale_to_world(r) + fd = FixtureDef.new + fd.shape = cs + fd.density = 1 + fd.friction = 0 # Slippery when wet! + fd.restitution = 0.5 + # We could use this if we want to turn collisions off + # cd.filter.groupIndex = -10 + # Attach fixture to body + body.create_fixture(fd) + end +end + +class Boundary + include Processing::Proxy + attr_reader :box2d, :b, :x, :y, :w, :h + + def initialize(b2d, x, y, w, h, a) + @box2d, @x, @y, @w, @h = b2d, x, y, w, h + # Define the polygon + sd = PolygonShape.new + # Figure out the box2d coordinates + box2d_w = box2d.scale_to_world(w / 2) + box2d_h = box2d.scale_to_world(h / 2) + # We're just a box + sd.set_as_box(box2d_w, box2d_h) + # Create the body + bd = BodyDef.new + bd.type = BodyType::STATIC + bd.angle = a + bd.position.set(box2d.processing_to_world(x, y)) + @b = box2d.create_body(bd) + # Attached the shape to the body using a Fixture + b.create_fixture(sd, 1) + end + + # Draw the boundary, it doesn't move so we don't have to ask the Body for location + def display + fill(0) + stroke(0) + stroke_weight(1) + rect_mode(CENTER) + a = b.get_angle + push_matrix + translate(x, y) + rotate(-a) + rect(0, 0, w, h) + pop_matrix + end +end diff --git a/samples/external_library/ruby_gem/jbox2d/lib/surface.rb b/samples/external_library/ruby_gem/jbox2d/lib/surface.rb new file mode 100644 index 0000000..a0cfd7d --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/lib/surface.rb @@ -0,0 +1,132 @@ +# The Nature of Code +# PBox2D example + +# An uneven surface boundary + + class Surface + include Processing::Proxy + # We'll keep track of all of the surface points + attr_reader :surface, :body, :box2d, :y, :width, :height + + def initialize b2d + @box2d = b2d + @surface = [] + @width, @height = b2d.width, b2d.height + # This is what box2d uses to put the surface in its world + chain = ChainShape.new + # Perlin noise argument + xoff = 0.0 + # This has to go backwards so that the objects bounce off the top of the surface + # This "edgechain" will only work in one direction! + (width + 10).step(-10, -5) do |x| + # Doing some stuff with perlin noise to calculate a surface that points down on one side + # and up on the other + if (x > width/2) + @y = 100 + (width - x) * 1.1 + map1d(noise(xoff), (0..1.0), (-80..80)) + else + @y = 100 + x * 1.1 + map1d(noise(xoff), (0..1.0), (-80..80)) + end + # Store the vertex in screen coordinates + surface << Vec2.new(x, y) + # Move through perlin noise + xoff += 0.1 + end + # Build an array of vertices in Box2D coordinates + # from the ArrayList we made + vertices = [] + surface.each do |surf| + vertices << box2d.processing_to_world(surf) + end + # Create the chain! + chain.createChain(vertices, vertices.length) + # The edge chain is now attached to a body via a fixture + bd = BodyDef.new + bd.position.set(0.0, 0.0) + @body = box2d.createBody(bd) + # Shortcut, we could define a fixture if we + # want to specify frictions, restitution, etc. + body.createFixture(chain, 1) + end + + # A simple function to just draw the edge chain as a series of vertex points + def display + stroke_weight(2) + stroke(0) + fill(135, 206, 250) + begin_shape + vertex(width, 0) # extra vertices so we can fill sky + surface.each do |v| + vertex(v.x, v.y) # the mountain range + end + vertex(0, 0) # extra vertices so we can fill sky + end_shape + end + end + + class Particle + include Processing::Proxy + # We need to keep track of a Body + + attr_reader :body, :box2d, :x, :y, :r + + # Constructor + def initialize(b2d, x, y, r) + @box2d, @x, @y, @r = b2d, x, y, r + # This function puts the particle in the Box2d world + make_body(x, y, r) + end + + # This function removes the particle from the box2d world + def kill_body + box2d.destroy_body(body) + end + + # Is the particle ready for deletion? + def done + pos = box2d.body_coord(body) + # Is it off the bottom of the screen? + return false unless (pos.y > box2d.height + r * 2) + kill_body + true + end + + def display + # We look at each body and get its screen position + pos = box2d.body_coord(body) + # Get its angle of rotation + a = body.get_angle + push_matrix + translate(pos.x, pos.y) + rotate(-a) + fill(175) + stroke(0) + stroke_weight(1) + ellipse(0, 0, r * 2, r * 2) + # Let's add a line so we can see the rotation + line(0, 0, r, 0) + pop_matrix + end + + # This function adds the rectangle to the box2d world + def make_body(x, y, r) + # Define and create the body + bd = BodyDef.new + bd.position = box2d.processing_to_world(x, y) + bd.type = BodyType::DYNAMIC + @body = box2d.create_body(bd) + # Make the body's shape a circle + cs = CircleShape.new + cs.m_radius = box2d.scale_to_world(r) + fd = FixtureDef.new + fd.shape = cs + # Parameters that affect physics + fd.density = 1 + fd.friction = 0.01 + fd.restitution = 0.3 + # Attach fixture to body + body.create_fixture(fd) + # Give it a random initial velocity (and angular velocity) + body.set_linear_velocity(Vec2.new(rand(-10..10), rand(5..10))) + body.set_angular_velocity(rand(-10..10)) + end + end diff --git a/samples/external_library/ruby_gem/jbox2d/liquid_fun_test.rb b/samples/external_library/ruby_gem/jbox2d/liquid_fun_test.rb new file mode 100644 index 0000000..7f1ffff --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/liquid_fun_test.rb @@ -0,0 +1,42 @@ +# The Nature of Code +# Daniel Shiffman +# http://natureofcode.com +# Basic example of falling rectangles + +require 'pbox2d' +require_relative 'lib/boundary' +require_relative 'lib/box' + +attr_reader :boxes, :boundaries, :box2d + +def setup + size(640, 360, P2D) + @box2d = Box2D.new(self) + box2d.init_options(gravity: [0, -10]) + box2d.create_world + @boundaries = [] + @boxes = [] + box2d.world.set_particle_radius(0.15) + box2d.world.set_particle_damping(0.2) + boundaries << Boundary.new(box2d, width / 4, height - 5, width / 2 - 50, 10) + boundaries << Boundary.new(box2d, 3 * width / 4, height - 50, width / 2 - 50, 10) +end + +def mouse_pressed + boxes << Box.new(box2d, mouse_x, mouse_y) +end + +def draw + background(255) + boundaries.each(&:display) + pos_buffer = box2d.world.particle_position_buffer + return if pos_buffer.nil? + stroke(0) + stroke_weight(2) + pos_buffer.each do |buf| + pos = box2d.world_to_processing(buf) + point(pos.x, pos.y) + end + fill(0) + text("f.p.s #{frame_rate.to_i}", 10, 60) +end \ No newline at end of file diff --git a/samples/external_library/ruby_gem/jbox2d/liquidy.rb b/samples/external_library/ruby_gem/jbox2d/liquidy.rb new file mode 100644 index 0000000..ca4cd8a --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/liquidy.rb @@ -0,0 +1,38 @@ +require 'pbox2d' +require_relative 'lib/particle_system' +attr_reader :box2d, :boundaries, :systems + +def setup + size(400,300) + @box2d = Box2D.new(self) + box2d.init_options(gravity: [0, -20]) + box2d.create_world + # to set a custom gravity otherwise + # box2d.gravity([0, -20]) + # Create Arrays + @systems = [] + @boundaries = [] + # Add a bunch of fixed boundaries + boundaries << Boundary.new(box2d, 50, 100, 300, 5, -0.3) + boundaries << Boundary.new(box2d, 250, 175, 300, 5, 0.5) +end + +def draw + background(255) + # Run all the particle systems + if systems.size > 0 + systems.each do |system| + system.run + system.add_particles(box2d, rand(0..2)) + end + end + # Display all the boundaries + boundaries.each(&:display) +end + +def mouse_pressed + # Add a new Particle System whenever the mouse is clicked + systems << ParticleSystem.new(box2d, 0, mouse_x, mouse_y) +end + + diff --git a/samples/external_library/ruby_gem/jbox2d/polygons.rb b/samples/external_library/ruby_gem/jbox2d/polygons.rb new file mode 100644 index 0000000..d110e78 --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/polygons.rb @@ -0,0 +1,39 @@ +# Basic example of falling rectangles +require 'pbox2d' +require_relative 'lib/custom_shape' + +attr_reader :box2d, :boundaries, :polygons + +def setup + size(640, 360) + smooth + # Initialize box2d physics and create the world + @box2d = Box2D.new(self) + box2d.init_options(gravity: [0, -20]) + box2d.create_world + # To later set a custom gravity + # box2d.gravity([0, -20] + # Create Arrays + @polygons = [] + @boundaries = [] + # Add a bunch of fixed boundaries + boundaries << Boundary.new(box2d, width / 4, height - 5, width / 2 - 50, 10, 0) + boundaries << Boundary.new(box2d, 3 * width / 4, height - 50, width / 2 - 50, 10, 0) + boundaries << Boundary.new(box2d, width - 5, height / 2, 10, height, 0) + boundaries << Boundary.new(box2d, 5, height / 2, 10, height, 0) +end + +def draw + background(255) + # Display all the boundaries + boundaries.each(&:display) + # Display all the polygons + polygons.each(&:display) + # polygons that leave the screen, we delete them + # (note they have to be deleted from both the box2d world and our list + polygons.reject!(&:done) +end + +def mouse_pressed + polygons << CustomShape.new(box2d, mouse_x, mouse_y) +end diff --git a/samples/external_library/ruby_gem/jbox2d/quick_test.rb b/samples/external_library/ruby_gem/jbox2d/quick_test.rb new file mode 100644 index 0000000..81cf907 --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/quick_test.rb @@ -0,0 +1,137 @@ +require 'pbox2d' + +# A list we'll use to track fixed objects +attr_reader :box2d, :boundaries, :boxes + +java_alias :background_int, :background, [Java::int] +java_alias :stroke_int, :stroke, [Java::int] + +def setup + size(400,300) + stroke_int(0) # set stroke this way to avoid overload warnings + srand(5) + # Initialize box2d physics and create the world + @box2d = Box2D.new(self) + puts box2d.version # print out version of pbox2d gem in use + box2d.init_options(scale: 10, gravity: [0, -20.0]) + box2d.create_world + # Set a custom gravity + # box2d.gravity(0, -20) + # Create ArrayLists + @boxes = [] + @boundaries = [] + # Add a bunch of fixed boundaries + boundaries << Boundary.new(box2d, width / 4, height - 5, width / 2 - 50, 10) + boundaries << Boundary.new(box2d, 3 * width / 4, height - 50, width / 2 - 50, 10) +end + +def draw + background_int(255) # set background this way to avoid overload warnings + # Boxes fall from the top every so often + boxes << Box.new(box2d, width / 2, 30) if rand < 0.99 + boundaries.each(&:display) + boxes.each(&:display) + # Boxes that leave the screen, we delete them note they have to be deleted + # from both the box2d world and locally + boxes.reject!(&:done) + exit if frame_count >= 908 +end + +class Boundary + include Processing::Proxy + # A boundary is a simple rectangle with x, y, width, and height + attr_reader :box2d, :x, :y, :w, :h, :b + + def initialize(box2d, x ,y, w, h) + @box2d, @x ,@y, @w, @h = box2d, x ,y, w, h + # Create the body + bd = BodyDef.new + bd.position.set(box2d.processing_to_world(x, y)) + @b = box2d.create_body(bd) + # Figure out the box2d coordinates + box2d_w = box2d.scale_to_world(w / 2) + box2d_h = box2d.scale_to_world(h / 2) + # Define the polygon + sd = PolygonShape.new + sd.setAsBox(box2d_w, box2d_h) + fd = FixtureDef.new + fd.shape = sd + fd.density = 0 + fd.friction = 0.3 + fd.restitution = 0.5 + b.createFixture(fd) + end + + # Draw the boundary, if it were at an angle we'd have to do something fancier + def display + fill(0) + rect_mode(CENTER) + rect(x, y, w, h) + end +end + +# A rectangular box +class Box + include Processing::Proxy + # We need to keep track of a Body and a width and height + attr_reader :box2d, :body, :w, :h + + # Constructor + def initialize(b2d, x, y) + @w = rand(4..16) + @h = rand(4..16) + @box2d = b2d + # Add the box to the box2d world + make_body(Vec2.new(x, y), w, h) + end + + def done + # Let's find the screen position of the particle + pos = box2d.body_coord(body) + # Is it off the bottom of the screen? + return false unless (pos.y > box2d.height + w * h) + box2d.destroy_body(body) + true + end + + # Drawing the box + def display + # We look at each body and get its screen position + pos = box2d.body_coord(body) + # Get its angle of rotation + a = body.angle + rect_mode(CENTER) + push_matrix + translate(pos.x, pos.y) + rotate(-a) + fill(175) + rect(0, 0, w, h) + pop_matrix + end + + # This function adds the rectangle to the box2d world + def make_body(center, w, h) + # Define a polygon (this is what we use for a rectangle) + sd = PolygonShape.new + box2d_w = box2d.scale_to_world(w / 2) + box2d_h = box2d.scale_to_world(h / 2) + sd.setAsBox(box2d_w, box2d_h) + # Define a fixture + fd = FixtureDef.new + fd.shape = sd + # Parameters that affect physics + fd.density = 1 + fd.friction = 0.3 + fd.restitution = 0.5 + # Define the body and make it from the shape + bd = BodyDef.new + bd.type = BodyType::DYNAMIC + bd.position.set(box2d.processing_to_world(center)) + cs = CircleShape.new + @body = box2d.create_body(bd) + body.create_fixture(fd) + # Give it some initial random velocity + body.setLinearVelocity(Vec2.new(rand(-5.0..5), rand(2.0..5))) + body.setAngularVelocity(rand(-5.0..5)) + end +end diff --git a/samples/external_library/ruby_gem/jbox2d/test_contact/README.md b/samples/external_library/ruby_gem/jbox2d/test_contact/README.md new file mode 100644 index 0000000..12b9fbd --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/test_contact/README.md @@ -0,0 +1,6 @@ +### Custom ContactListener example + +This example is somewhat based on the CollisionListening example [Box2D for processing][] by Dan Shiffman, but with a CustomContact listener. It uses the jruby way of implementing an interface (which is to `include` it as if it were a `module`). Note the use of the more elegant ruby way to discriminate between Boundary (has no `:change` method) and Particle (`responds_to? :change` method) objects. + +[Box2D for processing]:https://github.com/shiffman/Box2D-for-Processing + diff --git a/samples/external_library/ruby_gem/jbox2d/test_contact/lib/boundary.rb b/samples/external_library/ruby_gem/jbox2d/test_contact/lib/boundary.rb new file mode 100644 index 0000000..f0edb45 --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/test_contact/lib/boundary.rb @@ -0,0 +1,29 @@ +CENTER ||= Java::ProcessingCore::PConstants::CENTER +# The boundary class is used to create a floor in this +# sketch. Note it does not have a change method +class Boundary + attr_reader :box2d, :x, :y, :w, :h, :b + def initialize(b2d, x, y, w, h) + @box2d, @x, @y, @w, @h = b2d, x, y, w, h + sd = PolygonShape.new + box2d_w = box2d.scale_to_world(w / 2) + box2d_h = box2d.scale_to_world(h / 2) + sd.set_as_box(box2d_w, box2d_h) + # Create the body + bd = BodyDef.new + bd.type = BodyType::STATIC + bd.position.set(box2d.processing_to_world(x, y)) + @b = box2d.create_body(bd) + # Attached the shape to the body using a Fixture + b.create_fixture(sd, 1) + b.set_user_data(self) + end + + # Draw the boundary, if it were at an angle we'd have to do something fancier + def display(app) + app.fill(0) + app.stroke(0) + app.rect_mode(CENTER) + app.rect(x, y, w, h) + end +end diff --git a/samples/external_library/ruby_gem/jbox2d/test_contact/lib/custom_listener.rb b/samples/external_library/ruby_gem/jbox2d/test_contact/lib/custom_listener.rb new file mode 100644 index 0000000..97d1a3d --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/test_contact/lib/custom_listener.rb @@ -0,0 +1,29 @@ +# A custom listener allows us to get the physics engine to +# to call our code, on say contact (collisions) +class CustomListener + include ContactListener + + def begin_contact(cp) + # Get both fixtures + f1 = cp.getFixtureA + f2 = cp.getFixtureB + # Get both bodies + b1 = f1.getBody + b2 = f2.getBody + # Get our objects that reference these bodies + o1 = b1.getUserData + o2 = b2.getUserData + return unless [o1, o2].all? { |obj| obj.respond_to?(:change) } + o1.change + o2.change + end + + def end_contact(_cp) + end + + def pre_solve(_cp, _m) + end + + def post_solve(_cp, _ci) + end +end diff --git a/samples/external_library/ruby_gem/jbox2d/test_contact/lib/particle.rb b/samples/external_library/ruby_gem/jbox2d/test_contact/lib/particle.rb new file mode 100644 index 0000000..fb22b29 --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/test_contact/lib/particle.rb @@ -0,0 +1,73 @@ +# Note the particle class change method is use to change color to red +# when two particles collide (no change just hitting boundary) +class Particle + attr_accessor :body + attr_reader :box2d, :radius, :col + + def initialize(b2d, x, y, r) + @box2d, @x, @y, @radius = b2d, x, y, r + # This function puts the particle in the Box2d world + make_body(x, y, radius) + @col = -5_263_441 # grey + body.setUserData(self) + end + + # This function removes the particle from the box2d world + def kill_body + box2d.destroy_body(body) + end + + # Change color when hit + def change + @col = -65_536 # red + end + + # Is the particle ready for deletion? + def done + # Let's find the screen position of the particle + pos = box2d.body_coord(body) + # Is it off the bottom of the screen? + return false unless pos.y > (box2d.height + radius * 2) + kill_body + true + end + + def display(app) + # We look at each body and get its screen position + pos = box2d.body_coord(body) + # Get its angle of rotation + a = body.get_angle + app.push_matrix + app.translate(pos.x, pos.y) + app.rotate(a) + app.fill(col) + app.stroke(0) + app.stroke_weight(1) + app.ellipse(0, 0, radius * 2, radius * 2) + # Let's add a line so we can see the rotation + app.line(0, 0, radius, 0) + app.pop_matrix + end + + # Here's our function that adds the particle to the Box2D world + def make_body(x, y, r) + # Define a body + bd = BodyDef.new + # Set its position + bd.position = box2d.processing_to_world(x, y) + bd.type = BodyType::DYNAMIC + @body = box2d.create_body(bd) + # Make the body's shape a circle + cs = CircleShape.new + cs.m_radius = box2d.scale_to_world(r) + fd = FixtureDef.new + fd.shape = cs + # Parameters that affect physics + fd.density = 1 + fd.friction = 0.01 + fd.restitution = 0.3 + # Attach fixture to body + body.create_fixture(fd) + body.set_angular_velocity(rand(-10.0..10)) + end +end diff --git a/samples/external_library/ruby_gem/jbox2d/test_contact/test_contact.rb b/samples/external_library/ruby_gem/jbox2d/test_contact/test_contact.rb new file mode 100644 index 0000000..3e1dcc5 --- /dev/null +++ b/samples/external_library/ruby_gem/jbox2d/test_contact/test_contact.rb @@ -0,0 +1,24 @@ +require 'pbox2d' +require_relative 'lib/custom_listener' +require_relative 'lib/particle' +require_relative 'lib/boundary' + +attr_reader :box2d, :particles, :wall + +def setup + size 400, 400 + @box2d = Box2D.new(self) + box2d.create_world + box2d.add_listener(CustomListener.new) + @particles = [] + @wall = Boundary.new(box2d, width / 2, height - 5, width, 10) +end + +def draw + col = color('#ffffff') + background(col) + particles << Particle.new(box2d, rand(width), 20, rand(4..8)) if rand < 0.1 + particles.each{ |p| p.display(self) } + particles.reject!(&:done) + wall.display(self) +end