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, iflog
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
Collection of collections of Bodies to auto-update and draw.
A hash of color names to their values.
Is the application done?
The current font for rendering text.
The window height.
Number of update iterations per drawing tick.
Procs registered to handle key events.
Procs registered to handle keydown events.
Pause the simulation.
The renderer (software or hardware backed) the simulation is drawing in.
The window width.
Public Class Methods
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
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
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
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
Load an audio file at path
# File lib/graphics/simulation.rb, line 641 def audio path SDL::Audio.load path end
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
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, a° = nil, xscale = nil, yscale = nil, flags = nil renderer.blit src, x-src.w/2, h-y-src.h/2, a°, xscale, yscale, :center end
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 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
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 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 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
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
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 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
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
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
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 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 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
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
Load an image at path into a new surface.
# File lib/graphics/simulation.rb, line 634 def image path SDL::Surface.load path end
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
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
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 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
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
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
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
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
The pre-draw phase. Defaults to clearing.
# File lib/graphics/simulation.rb, line 422 def pre_draw n clear end
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
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, a° = nil, xscale = nil, yscale = nil, flags = nil renderer.blit src, x, h-y-src.h, a°, xscale, yscale, false end
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 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 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
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
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
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
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 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 the current window to a png.
# File lib/graphics/simulation.rb, line 678 def save path renderer.save path end
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
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
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 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
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