class Graphics::AbstractSimulation

An abstract simulation. See Graphics::Simulation and Graphics::Drawing.

Constants

CLEAR_COLOR

The default color to clear the window.

D2R

degrees to radians

DEBUG_COLOR

The default font color for ‘debug` calls.

DEFAULT_FONT

The default font. Menlo on OS X, Deja Vu Sans Mono on linux.

LOG_INTERVAL

Call log every N ticks, if log is defined.

R2D

radians to degrees

SCREEN_FLAGS

Flags to be used when initializing a window. Defaults to 0. See SDL doco for more.

Attributes

_bodies[RW]

Collection of collections of Bodies to auto-update and draw.

color[RW]

A hash of color names to their values.

done[RW]

Is the application done?

font[RW]

The current font for rendering text.

h[RW]

The window height.

iter_per_tick[RW]

Number of update iterations per drawing tick.

key_handler[RW]

Procs registered to handle key events.

keydown_handler[RW]

Procs registered to handle keydown events.

paused[RW]

Pause the simulation.

renderer[RW]

The renderer (software or hardware backed) the simulation is drawing in.

w[RW]

The window width.

Public Class Methods

new(w=nil, h=nil, name=self.class.name, full=false) click to toggle source

Create a new simulation of a certain width and height. Optionally, you can set the bits per pixel (0 for current screen settings), the name of the window, and whether or not to run in full screen mode.

This also names a bunch colors and hues for convenience.

# File lib/graphics/simulation.rb, line 119
def initialize w=nil, h=nil, name=self.class.name, full=false
  w ||= SDL::Screen::W/2
  h ||= SDL::Screen::H/2

  # TODO: remove for 1.0.0 final
  raise "Do NOT pass bpp to Simulation anymore" if !name || Integer === name

  full = full ? SDL::FULLSCREEN : 0

  self._bodies = []

  self.font = find_font(DEFAULT_FONT, 32)

  name ||= "Unknown"
  name = name.gsub(/[A-Z]/, ' \0').strip

  self.renderer = SDL::Screen.open w, h, 32, self.class::SCREEN_FLAGS|full
  self.w, self.h = w, h

  renderer.title = name

  self.color = {}
  self.paused = false

  self.iter_per_tick = 1

  self.key_handler = []
  self.keydown_handler = {}

  initialize_keys
  initialize_colors

  clear # so you start with the right color blank window on frame 0
  renderer.present
end

Public Instance Methods

add_key_handler(k, remove = nil, &b) click to toggle source

Register a block to run for a particular key-press. This allows you to register multiple blocks for the same key and also to handle multiple keys down at the same time.

# File lib/graphics/simulation.rb, line 350
def add_key_handler k, remove = nil, &b
  k = SDL::Key.const_get k
  key_handler.delete_if { |a, _| k==a } if remove
  key_handler.unshift [k, b]
end
add_keydown_handler(k, &b) click to toggle source

Register a block to run for a particular keydown event. This is a single key handler per tick and only on a key-down event.

# File lib/graphics/simulation.rb, line 360
def add_keydown_handler k, &b
  keydown_handler[k] = b
end
angle(x1, y1, a, m, c) click to toggle source

Draw a line from x1/y1 to a particular magnitude and angle in color c.

# File lib/graphics/simulation.rb, line 512
def angle x1, y1, a, m, c
  x2, y2 = project x1, y1, a, m
  line x1, y1, x2, y2, c
end
audio(path) click to toggle source

Load an audio file at path

# File lib/graphics/simulation.rb, line 641
def audio path
  SDL::Audio.load path
end
bezier(*points, c) click to toggle source

Draw an antialiased curve from x1/y1 to x2/y2 via control points cx1/cy1 & cx2/cy2 in color c.

# File lib/graphics/simulation.rb, line 575
def bezier *points, c
  h = self.h-1

  # TODO: there is probably a cleaner way... or move entirely into C
  xs, ys = points.each_slice(2).to_a.transpose
  ys.map! { |y| h-y }

  renderer.draw_bezier xs, ys, 5, color[c]
end
blit(src, x, y, a° = nil, xscale = nil, yscale = nil, flags = nil) click to toggle source

Draw a bitmap centered at x/y with optional angle, x/y scale, and flags.

# File lib/graphics/simulation.rb, line 664
def blit src, x, y,  = nil, xscale = nil, yscale = nil, flags = nil
  renderer.blit src, x-src.w/2, h-y-src.h/2, , xscale, yscale, :center
end
circle(x, y, r, c, fill = false, aa = true) click to toggle source

Draw a circle at x/y with radius r in color c.

# File lib/graphics/simulation.rb, line 558
def circle x, y, r, c, fill = false, aa = true
  y = h-y-1
  renderer.draw_circle x, y, r, color[c], aa, fill
end
clear(c = self.class::CLEAR_COLOR) click to toggle source

Clear the whole window. Defaults to CLEAR_COLOR.

# File lib/graphics/simulation.rb, line 464
def clear c = self.class::CLEAR_COLOR
  cc = color[c]
  if cc then
    renderer.clear cc
  else
    warn "Color #{c} doesn't appear to be registered. Skipping clear."
  end
end
debug(fmt, *args) click to toggle source

Print out some extra debugging information underneath the fps line (if any).

# File lib/graphics/simulation.rb, line 613
def debug fmt, *args
  s = fmt % args
  text s, 10, h-40-font.height, self.class::DEBUG_COLOR
end
draw(n) click to toggle source

Draw the scene by clearing the window and drawing all registered bodies. You are free to completely override this or call super and add any extras at the end.

# File lib/graphics/simulation.rb, line 414
def draw n
  pre_draw n
  post_draw n
end
draw_collection(ary) click to toggle source

Draw a homogeneous collection of bodies. This assumes that the MVC pattern described on this class is being used.

# File lib/graphics/simulation.rb, line 440
def draw_collection ary
  return if ary.empty?

  cls = ary.first.class.const_get :View

  ary.each do |obj|
    cls.draw self, obj
  end
end
ellipse(x, y, w, h, c, fill = false, aa = true) click to toggle source

Draw a circle at x/y with radiuses w/h in color c.

# File lib/graphics/simulation.rb, line 566
def ellipse x, y, w, h, c, fill = false, aa = true
  y = self.h-y-1
  renderer.draw_ellipse x, y, w, h, color[c], aa, fill
end
fast_rect(x, y, w, h, c) click to toggle source

Draw a rect at x/y with w by h dimensions in color c. Ignores blending.

# File lib/graphics/simulation.rb, line 520
def fast_rect x, y, w, h, c
  y = self.h-y-h # TODO: -1???
  renderer.fast_rect x, y, w, h, color[c]
end
find_font(name, size = 16) click to toggle source

Find and open a (TTF) font. Should be as system agnostic as possible.

# File lib/graphics/simulation.rb, line 215
def find_font name, size = 16
  font = Dir["#{FONT_GLOB}/#{name}.{ttc,ttf}"].first

  raise ArgumentError, "Can't find font named '#{name}'" unless font

  SDL::TTF.open(font, size)
end
fps(n, color = :green) click to toggle source

Draw the current frames-per-second in the top left corner. Defaults to green.

# File lib/graphics/simulation.rb, line 623
def fps n, color = :green
  secs = Time.now - start_time
  fps = "%5.1f fps" % [n / secs]
  text fps, 10, h-font.height, color
end
from_hsl(h, s, l) click to toggle source

Convert HSL to RGB.

en.wikipedia.org/wiki/HSL_and_HSV#From_HSV

# File lib/graphics/simulation.rb, line 265
def from_hsl h, s, l # 0..360, 0..1, 0..1
  raise ArgumentError, "%f, %f, %f out of range" % [h, s, v] unless
    h.between?(0, 360) && s.between?(0, 1) && l.between?(0, 1)

  c  = (1 - (2*l - 1).abs) * s
  h2 = h / 60.0
  x  = c * (1 - (h2 % 2 - 1).abs)
  m  = l - c/2

  r, g, b = case
            when 0 <= h2 && h2 < 1 then [c+m, x+m, 0+m]
            when 1 <= h2 && h2 < 2 then [x+m, c+m, 0+m]
            when 2 <= h2 && h2 < 3 then [0+m, c+m, x+m]
            when 3 <= h2 && h2 < 4 then [0+m, x+m, c+m]
            when 4 <= h2 && h2 < 5 then [x+m, 0+m, c+m]
            when 5 <= h2 && h2 < 6 then [c+m, 0+m, x+m]
            else
              raise [h, s, v, h2, x, m].inspect
            end

  [(r*255).round, (g*255).round, (b*255).round]
end
from_hsv(h, s, v) click to toggle source

Convert HSV to RGB.

en.wikipedia.org/wiki/HSL_and_HSV#From_HSV

# File lib/graphics/simulation.rb, line 293
def from_hsv h, s, v # 0..360, 0..1, 0..1
  raise ArgumentError, "%f, %f, %f out of range" % [h, s, v] unless
    h.between?(0, 360) && s.between?(0, 1) && v.between?(0, 1)

  c  = v * s
  h2 = h / 60.0
  x  = c * (1 - (h2 % 2 - 1).abs)
  m  = v - c

  r, g, b = case
            when 0 <= h2 && h2 < 1 then [c+m, x+m, 0+m]
            when 1 <= h2 && h2 < 2 then [x+m, c+m, 0+m]
            when 2 <= h2 && h2 < 3 then [0+m, c+m, x+m]
            when 3 <= h2 && h2 < 4 then [0+m, x+m, c+m]
            when 4 <= h2 && h2 < 5 then [x+m, 0+m, c+m]
            when 5 <= h2 && h2 < 6 then [c+m, 0+m, x+m]
            else
              raise [h, s, v, h2, x, m].inspect
            end

  [(r*255).round, (g*255).round, (b*255).round]
end
handle_event(event, n) click to toggle source

Handle an event. By default only handles the Quit event. Override if you want to add more handlers. Be sure to call super or you won’t be able to quit.

# File lib/graphics/simulation.rb, line 334
def handle_event event, n
  case event
  when SDL::Event::Quit then
    exit
  when SDL::Event::Keydown then
    c = event.sym.chr rescue nil
    b = keydown_handler[c]
    b[self] if b
  end
end
handle_keys() click to toggle source

Handle key events by looking through key_handler and running any blocks that match the key(s) being pressed.

# File lib/graphics/simulation.rb, line 368
def handle_keys
  SDL::Key.scan
  key_handler.each do |k, blk|
    blk[self] if SDL::Key.press? k
  end
end
hline(y, c, x1 = 0, x2 = w) click to toggle source

Draw a horizontal line from x1 to x2 at y in color c.

# File lib/graphics/simulation.rb, line 484
def hline y, c, x1 = 0, x2 = w
  line x1, y, x2, y, c
end
image(path) click to toggle source

Load an image at path into a new surface.

# File lib/graphics/simulation.rb, line 634
def image path
  SDL::Surface.load path
end
initialize_keys() click to toggle source

Register default key events. Handles ESC & Q (quit) and P (pause).

# File lib/graphics/simulation.rb, line 158
def initialize_keys
  add_keydown_handler("\e") { self.done = true }
  add_keydown_handler("q")  { self.done = true }
  add_keydown_handler("p")  { self.paused = !paused }
  add_keydown_handler("/")  { self.iter_per_tick += 1 }
  add_keydown_handler("-")  { self.iter_per_tick -= 1; self.iter_per_tick = 1  if iter_per_tick < 1 }
end
line(x1, y1, x2, y2, c, aa = true) click to toggle source

Draw an antialiased line from x1/y1 to x2/y2 in color c.

# File lib/graphics/simulation.rb, line 476
def line x1, y1, x2, y2, c, aa = true
  h = self.h
  renderer.draw_line x1, h-y1-1, x2, h-y2-1, color[c], aa
end
mouse() click to toggle source

Return the current mouse state: x, y, buttons.

# File lib/graphics/simulation.rb, line 655
def mouse
  r = SDL::Mouse.state
  r[1] = h-r[1]
  r
end
open_mixer(channels = 1) click to toggle source

Open the audio mixer with a number of channels open.

# File lib/graphics/simulation.rb, line 648
def open_mixer channels = 1
  SDL::Audio.open channels
end
point(x, y, c = nil) click to toggle source

Read or write a color to x/y. If c is given, write, otherwise read.

Reading is pretty slow. Try to avoid.

# File lib/graphics/simulation.rb, line 530
def point x, y, c = nil
  if c then
    renderer[x, h-y-1] = color[c]
  else
    renderer[x, h-y-1]
  end
end
polygon(points, c) click to toggle source

Draw a closed form polygon from an array of points in a particular color.

# File lib/graphics/simulation.rb, line 499
def polygon points, c
  return unless points.size > 1

  points << points.first

  xs, ys = points.transpose

  renderer.draw_polygon xs, ys.map { |y| h-y-1 }, c, :aa
end
populate(klass, n = klass::COUNT) { |o| ... } click to toggle source

Return an array populated by instances of klass. You can specify how many to create here or it will access klass::COUNT as the default.

# File lib/graphics/simulation.rb, line 321
def populate klass, n = klass::COUNT
  n.times.map {
    o = klass.new self
    yield o if block_given?
    o
  }
end
post_draw(n) click to toggle source

The post-draw phase. Defaults to having all bodies draw themselves.

# File lib/graphics/simulation.rb, line 430
def post_draw n
  _bodies.each do |ary|
    draw_collection ary
  end
end
pre_draw(n) click to toggle source

The pre-draw phase. Defaults to clearing.

# File lib/graphics/simulation.rb, line 422
def pre_draw n
  clear
end
project(x1, y1, a, m) click to toggle source

Calculate the x/y coordinate offset from x1/y1 with an angle and a magnitude.

# File lib/graphics/simulation.rb, line 542
def project x1, y1, a, m
  rad = a * D2R
  [x1 + Math.cos(rad) * m, y1 + Math.sin(rad) * m]
end
put(src, x, y, a° = nil, xscale = nil, yscale = nil, flags = nil) click to toggle source

Draw a bitmap at x/y with optional angle, x/y scale, and flags.

# File lib/graphics/simulation.rb, line 671
def put src, x, y,  = nil, xscale = nil, yscale = nil, flags = nil
  renderer.blit src, x, h-y-src.h, , xscale, yscale, false
end
rect(x, y, w, h, c, fill = false) click to toggle source

Draw a rect at x/y with w by h dimensions in color c.

# File lib/graphics/simulation.rb, line 550
def rect x, y, w, h, c, fill = false
  y = self.h-y-h # TODO: -1???
  renderer.draw_rect x, y, w, h, color[c], fill
end
register_bodies(ary) click to toggle source

Register a collection of bodies to be auto-updated and drawn.

# File lib/graphics/simulation.rb, line 226
def register_bodies ary
  _bodies << ary
  ary
end
register_body(obj) click to toggle source

Register a single Body to be auto-updated and drawn.

# File lib/graphics/simulation.rb, line 234
def register_body obj
  register_bodies Array(obj)
  obj
end
register_color(name, r, g, b, a = 255) click to toggle source

Name a color w/ rgba values.

# File lib/graphics/simulation.rb, line 242
def register_color name, r, g, b, a = 255
  color[name] = renderer.format.map_rgba r, g, b, a
end
register_hsla(n, h, s, l, a = 1.0) click to toggle source

Name a color w/ HSL values.

# File lib/graphics/simulation.rb, line 249
def register_hsla n, h, s, l, a = 1.0
  register_color n, *from_hsl(h, s, l), (a*255).round
end
register_hsva(n, h, s, v, a = 1.0) click to toggle source

Name a color w/ HSV values.

# File lib/graphics/simulation.rb, line 256
def register_hsva n, h, s, v, a = 1.0
  register_color n, *from_hsv(h, s, v), (a*255).round
end
render_text(s, c, f = font) click to toggle source

Return the rendered text s in color c in font f.

# File lib/graphics/simulation.rb, line 597
def render_text s, c, f = font
  f.render renderer, s, color[c]
end
run() click to toggle source

Run the simulation. This handles all events by polling and scanning for key presses (multiple keys at once are possible).

On each tick, call update, then draw the scene.

# File lib/graphics/simulation.rb, line 381
def run
  self.start_time = Time.now
  n = 0
  event = nil
  self.done = false

  logger = respond_to? :log
  log_interval = self.class::LOG_INTERVAL

  loop do
    handle_event event, n while event = SDL::Event.poll
    handle_keys

    break if done
    next  if paused

    iter_per_tick.times { update n; n += 1 }
    draw_and_flip n

    log if logger and n % log_interval == 0
  end
end
save(path) click to toggle source

Save the current window to a png.

# File lib/graphics/simulation.rb, line 678
def save path
  renderer.save path
end
sprite(w, h) { || ... } click to toggle source

Create a new renderer with a given width and height and yield to a block for drawing. The resulting surface is returned.

# File lib/graphics/simulation.rb, line 686
def sprite w, h
  old_renderer   = renderer
  new_renderer   = renderer.sprite w, h
  old_w, old_h   = renderer.w, renderer.h
  self.w, self.h = w, h
  self.renderer  = new_renderer

  yield if block_given?

  new_renderer.surface
ensure
  self.renderer  = old_renderer
  self.w, self.h = old_w, old_h
end
text(s, x, y, c, f = font) click to toggle source

Draw text s at x/y in color c in font f.

# File lib/graphics/simulation.rb, line 604
def text s, x, y, c, f = font
  y = self.h-y-f.height-1
  f.draw renderer, s, x, y, color[c]
end
text_size(s, f = font) click to toggle source

Return the w/h of the text s in font f.

# File lib/graphics/simulation.rb, line 590
def text_size s, f = font
  f.text_size s.to_s
end
update(n) click to toggle source

Update the simulation by telling all registered bodies to update. You are free to completely override this or call super and add any extras at the end.

# File lib/graphics/simulation.rb, line 455
def update n
  _bodies.each do |ary|
    ary.each(&:update)
  end
end
vline(x, c, y1 = h-1, y2 = 0) click to toggle source

Draw a vertical line from y1 to y2 at y in color c.

# File lib/graphics/simulation.rb, line 491
def vline x, c, y1 = h-1, y2 = 0
  line x, y1, x, y2, c
end