require 'forwardable' # The Nature of Code # PBox2D example # An uneven surface boundary class Surface extend Forwardable # We'll keep track of all of the surface points def_delegators(:@app, :box2d, :width, :height, :begin_shape, :end_shape, :fill, :stroke, :stroke_weight, :vertex, :map1d, :noise) attr_reader :surface, :body, :y def initialize(app) @app = app @surface = [] # 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.map { |v| vertex(v.x, v.y) } # the mountain range vertex(0, 0) # extra vertices so we can fill sky end_shape end end # Using forwardable again class Particle extend Forwardable def_delegators(:@app, :box2d, :push_matrix, :pop_matrix, :rotate, :translate, :fill, :stroke, :stroke_weight, :noise, :map1d, :ellipse, :line) # We need to keep track of a Body attr_reader :body, :x, :y, :r # Constructor def initialize(app, x, y, r) @app, @x, @y, @r = app, 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