Eaglev0.1.0

3D physics

Spheres and boxes with SAT contacts, stacking pyramid, kinematic ramp.

Click fires spheres · Space throws a box · R resets

Source: examples/physics3d/main.cr

examples/physics3d/main.cr

require "../../src/eagle"
include Eagle

# 3D physics playground: click to fire spheres, space to drop a box, R to reset.
class Physics3DDemo < App
  @cam = Camera3D.new(position: v3(0, 6, 14))
  @rng = Random.new(1)
  @shot : Sound? = nil

  def load
    @shot = Sound.tone(200, 0.1, Sound::Wave::Triangle, 0.3)
    root = SceneTree.root
    Scene3D.environment.fog(30, 80)
    tex = Texture.new(Image.checkerboard(32, 32, 16, Color.hex("#cfd6dd"), Color.hex("#8b96a3")), wrap: GPU::Wrap::Repeat)
    floor = StaticBody3D.new(position: v3(0, -0.5, 0)).box(40, 1, 40)
    floor.add(MeshInstance3D.new(Mesh.box(40, 1, 40), Material.new(texture: tex, specular: 0.05)))
    root.add(floor)
    ramp = StaticBody3D.new(position: v3(-6, 1, 0))
    ramp.rotate_z(-0.4)
    ramp.box(8, 0.4, 6)
    ramp.add(MeshInstance3D.new(Mesh.box(8, 0.4, 6), Material.new(Color.hex("#6c7a89"))))
    root.add(ramp)
    # a pyramid of boxes
    4.times do |layer|
      (4 - layer).times do |i|
        b = RigidBody3D.new(position: v3(3 + i * 1.05 + layer * 0.525, 0.5 + layer * 1.02, 0)).box(1, 1, 1)
        b.add(MeshInstance3D.new(Mesh.cube(1), Material.new(Color.hsv(layer * 40 + i * 15, 0.6, 0.95))))
        root.add(b)
      end
    end
    6.times { |i| spawn_sphere(v3(-8 + i * 0.6, 4 + i, 0), Vec3::ZERO, 0.3 + @rng.rand * 0.3) }
    root.add(DirectionalLight3D.new(v3(-0.5, -1, -0.4)), @cam)
    @cam.look_at(v3(0, 1, 0))
  end

  def spawn_sphere(pos : Vec3, vel : Vec3, r : Float64)
    s = RigidBody3D.new(position: pos).sphere(r)
    s.velocity = vel
    s.restitution = 0.4
    s.add(MeshInstance3D.new(Mesh.sphere(r, 16, 12), Material.new(Color.hsv(@rng.rand * 360, 0.7, 1), shininess: 64, specular: 0.5)))
    SceneTree.root.add(s)
  end

  def update(dt : Float32)
    @cam.fly(dt, 8)
    if Input.mouse_pressed?(MouseButton::Left)
      ray = @cam.mouse_ray
      spawn_sphere(ray.origin + ray.direction, ray.direction * 20, 0.35)
      @shot.try(&.play)
    end
    if Input.pressed?(Key::Space)
      b = RigidBody3D.new(position: @cam.position + @cam.forward * 3).box(1, 1, 1)
      b.rotation = Quat.from_euler(@rng.rand, @rng.rand, @rng.rand)
      b.velocity = @cam.forward * 8
      b.add(MeshInstance3D.new(Mesh.cube(1), Material.new(Color::ORANGE)))
      SceneTree.root.add(b)
    end
    if Input.pressed?(Key::R)
      SceneTree.reset
      load
    end
    Eagle.quit if Input.pressed?(Key::Escape)
  end

  def draw(g : Graphics)
    g.rect(0, 0, Window.width, 30, color: Color.new(0, 0, 0, 0.4))
    g.print("3D physics  bodies #{Physics3D.world.bodies.size}  fps #{Clock.fps.round}   click: fire sphere  space: throw box  R reset  WASD/right-drag fly", 10, 8)
  end
end

Eagle.run(Physics3DDemo, title: "Eagle physics 3D", width: 1024, height: 640, msaa: 4)