38 Commits

Author SHA1 Message Date
3e56d48556 Update dependencies, bump version 2020-12-31 07:48:44 -06:00
0c9874c53b Added support for tiled 9 slice backgrounds 2020-12-18 12:43:53 -06:00
164a46c1fb Added Background Nine Slice (Not yet usable for ui widgets) 2020-12-15 11:38:17 -06:00
26dd688124 Ran rubocop -a 2020-12-14 16:04:31 -06:00
2447dde1af Added rubocop config 2020-12-14 15:39:30 -06:00
d3ff7c1fc1 Added support for future borderless window option, added initial support for label word wrapping (needs more work), fixed toggle button checkmark not displaying due to changes in Label 2020-12-14 15:39:16 -06:00
2d57d62bc2 Don't trigger a recalc when hiding/showing elements if they're already in that state 2020-12-09 19:11:28 -06:00
fb36436d79 Added color support to Image 2020-12-09 09:19:32 -06:00
246e0c54b2 Fixed crashes when using EditBox with no content 2020-12-08 20:27:01 -06:00
d02c001989 Added download manager, added excon dependency 2020-09-25 20:51:25 -05:00
3ba635f157 Fixed ConfigFile not storing values deeper then one level, added markup_width to Text 2020-09-24 10:47:55 -05:00
695c77b183 Added support for Label text alignement, improved EditBox to correctly position caret and support mouse caret positioning, added debugging Container boundry (WIP) 2020-09-09 09:51:18 -05:00
b06ceaabce Fixed crash if GuiState @mouse_over has no parent 2020-08-28 08:37:24 -05:00
ce64b8a205 Implemented ListBox- Menu 2020-08-21 15:56:11 -05:00
da4188764c Added min/max_view_distance to attr_accessor for PerspectiveCamera 2020-07-19 09:44:09 -05:00
d01e91c3fd Added support for draw mode to Text 2020-07-18 21:34:21 -05:00
041cfcccaa Added a large portion of I-MIC-FPS's opengl rendering and model loading systems 2020-07-15 21:33:24 -05:00
d7dbcf8511 Added orthographic support to Transform 2020-07-10 14:51:02 -05:00
2a8e852b15 Added support for passing a filter method/proc to EditLine 2020-06-23 09:51:36 -05:00
d392d8249d Added support for deciding text drawing method, more work on edit_box 2020-06-16 10:12:21 -05:00
5d7e2028b1 Added support for image icon for ToggleButton, added initial implementation of EditBox, fixed a few ruby -w warnings by initializing select instance nilable variables, added clipboard support to EditLine, added drag selection to EditLine, added keyboard shortcuts to EditLine, GuiState now passes button_down/button_up callbacks to @focus 2020-06-16 00:19:30 -05:00
d902e5d111 Fixed EditLine not positioning caret correctly when clicked 2020-06-10 07:32:31 -05:00
39964e5bd4 Added support for using an image in a button 2020-06-09 10:49:31 -05:00
5b2a015421 Call recalulate less often to speed up gui updates, call root_container.recalculate a third time when recalculating gui. Gui calls recalculate 10x less when doing a full recalculate. 2020-06-09 09:11:57 -05:00
eb4d5d4d21 Use Gosu.clip_to to prevent elements from overdrawing 2020-06-08 09:01:10 -05:00
c83b204447 Removed debugging puts from EditLine 2020-06-08 05:48:30 -05:00
d0449c7b65 Add support back for fixed x and y positioning of elements, fixed theming not using deep_merge 2020-06-08 05:35:03 -05:00
a05ee57f6f Fixed EditLine text not visible if using dynamic width/height, fixed cursor in wrong position when at start/end 2020-06-07 13:18:03 -05:00
c3b227d6e7 Fix vector transform using 5th element instead if 15th 2020-06-07 12:36:37 -05:00
7aaf37d43e Added Vector#multiply_transform, needs more testing. 2020-05-12 09:32:50 -05:00
0850336e55 Bump version 2020-05-06 22:01:15 -05:00
8b6d7b6eb2 Fixed Slider default value was always 0.5 instead of middle of range 2020-05-06 12:54:52 -05:00
3226eb2bda Sliders work better, fixed CheckBox not passing along options hash, Text width/height now accept a string, changed EditLine text selection color, temporary back to back gui recalculations to fix positioning errors until Container#layout can safely be called after determining element width and height. 2020-05-06 12:08:20 -05:00
a98bb4ec82 Fixed hidden elements were interactable, added comments 2020-05-05 10:48:00 -05:00
da5d740c6e Sync: Renamed Engine to Window to be less confusing, elements are now supposed to throw a changed event if their value is changed 2020-05-04 11:17:11 -05:00
4055f645f3 Implemented Slider (still have some positioning issues to resolve but it works), added files for ListBox, Radio, and EditBox, implemented dragging support in GuiState. 2020-04-10 18:45:55 -05:00
185ab000d6 Updated rake 2020-04-06 09:40:41 -05:00
ed061c8408 Fixed incorrect element positioning when using margin 2020-04-06 09:38:30 -05:00
65 changed files with 3434 additions and 587 deletions

8
.rubocop.yml Normal file
View File

@@ -0,0 +1,8 @@
Style/StringLiterals:
EnforcedStyle: double_quotes
Metrics/MethodLength:
Max: 40
Style/EmptyMethod:
EnforcedStyle: expanded

View File

@@ -1,6 +1,6 @@
source "https://rubygems.org" source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
# Specify your gem's dependencies in cyberarm_engine.gemspec # Specify your gem's dependencies in cyberarm_engine.gemspec
gemspec gemspec

View File

@@ -42,7 +42,7 @@ class Hello < CyberarmEngine::GuiState
end end
end end
class Window < CyberarmEngine::Engine class Window < CyberarmEngine::Window
def initialize def initialize
super super
self.show_cursor = true self.show_cursor = true

View File

@@ -7,4 +7,4 @@ Rake::TestTask.new(:test) do |t|
t.test_files = FileList["test/**/*_test.rb"] t.test_files = FileList["test/**/*_test.rb"]
end end
task :default => :test task default: :test

BIN
assets/textures/default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

View File

@@ -1,5 +1,4 @@
lib = File.expand_path("lib", __dir__)
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "cyberarm_engine/version" require "cyberarm_engine/version"
@@ -9,8 +8,8 @@ Gem::Specification.new do |spec|
spec.authors = ["Cyberarm"] spec.authors = ["Cyberarm"]
spec.email = ["matthewlikesrobots@gmail.com"] spec.email = ["matthewlikesrobots@gmail.com"]
spec.summary = %q{Make games quickly and easily with gosu} spec.summary = "Make games quickly and easily with gosu"
spec.description = %q{Yet another game making framework around gosu} spec.description = "Yet another game making framework around gosu"
spec.homepage = "https://github.com/cyberarm/cyberarm_engine" spec.homepage = "https://github.com/cyberarm/cyberarm_engine"
spec.license = "MIT" spec.license = "MIT"
@@ -26,11 +25,15 @@ Gem::Specification.new do |spec|
end end
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"] spec.require_paths = %w[lib assets]
spec.add_dependency "gosu", "~> 0.15.0" spec.add_dependency "clipboard", "~> 1.3.5"
spec.add_dependency "excon", "~> 0.78.0"
spec.add_dependency "gosu", "~> 1.0.0"
spec.add_dependency "gosu_more_drawables", "~> 0.3"
# spec.add_dependency "ffi", :platforms => [:mswin, :mingw] # Required by Clipboard on Windows
spec.add_development_dependency "bundler", "~> 1.16" spec.add_development_dependency "bundler", "~> 1.16"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "minitest", "~> 5.0" spec.add_development_dependency "minitest", "~> 5.0"
spec.add_development_dependency "rake", "~> 13.0"
end end

View File

@@ -1,3 +1,5 @@
CYBERARM_ENGINE_ROOT_PATH = File.expand_path("..", __dir__)
begin begin
require File.expand_path("../../ffi-gosu/lib/gosu", File.dirname(__FILE__)) require File.expand_path("../../ffi-gosu/lib/gosu", File.dirname(__FILE__))
rescue LoadError => e rescue LoadError => e
@@ -5,21 +7,22 @@ rescue LoadError => e
require "gosu" require "gosu"
end end
require "json" require "json"
require "excon"
require "gosu_more_drawables"
require "clipboard"
require_relative "cyberarm_engine/version" require_relative "cyberarm_engine/version"
require_relative "cyberarm_engine/stats"
require_relative "cyberarm_engine/common" require_relative "cyberarm_engine/common"
require_relative "cyberarm_engine/gosu_ext/circle"
require_relative "cyberarm_engine/game_object" require_relative "cyberarm_engine/game_object"
require_relative "cyberarm_engine/engine" require_relative "cyberarm_engine/window"
require_relative "cyberarm_engine/bounding_box" require_relative "cyberarm_engine/bounding_box"
require_relative "cyberarm_engine/vector" require_relative "cyberarm_engine/vector"
require_relative "cyberarm_engine/transform" require_relative "cyberarm_engine/transform"
require_relative "cyberarm_engine/ray" require_relative "cyberarm_engine/ray"
require_relative "cyberarm_engine/shader" if defined?(OpenGL)
require_relative "cyberarm_engine/background" require_relative "cyberarm_engine/background"
require_relative "cyberarm_engine/animator" require_relative "cyberarm_engine/animator"
@@ -37,13 +40,25 @@ require_relative "cyberarm_engine/ui/element"
require_relative "cyberarm_engine/ui/elements/label" require_relative "cyberarm_engine/ui/elements/label"
require_relative "cyberarm_engine/ui/elements/button" require_relative "cyberarm_engine/ui/elements/button"
require_relative "cyberarm_engine/ui/elements/toggle_button" require_relative "cyberarm_engine/ui/elements/toggle_button"
require_relative "cyberarm_engine/ui/elements/list_box"
require_relative "cyberarm_engine/ui/elements/edit_line" require_relative "cyberarm_engine/ui/elements/edit_line"
require_relative "cyberarm_engine/ui/elements/edit_box"
require_relative "cyberarm_engine/ui/elements/image" require_relative "cyberarm_engine/ui/elements/image"
require_relative "cyberarm_engine/ui/elements/container" require_relative "cyberarm_engine/ui/elements/container"
require_relative "cyberarm_engine/ui/elements/flow" require_relative "cyberarm_engine/ui/elements/flow"
require_relative "cyberarm_engine/ui/elements/stack" require_relative "cyberarm_engine/ui/elements/stack"
require_relative "cyberarm_engine/ui/elements/check_box" require_relative "cyberarm_engine/ui/elements/check_box"
require_relative "cyberarm_engine/ui/elements/radio"
require_relative "cyberarm_engine/ui/elements/progress" require_relative "cyberarm_engine/ui/elements/progress"
require_relative "cyberarm_engine/ui/elements/slider"
require_relative "cyberarm_engine/game_state" require_relative "cyberarm_engine/game_state"
require_relative "cyberarm_engine/ui/gui_state" require_relative "cyberarm_engine/ui/gui_state"
require_relative "cyberarm_engine/model"
require_relative "cyberarm_engine/model_cache"
require_relative "cyberarm_engine/model/material"
require_relative "cyberarm_engine/model/model_object"
require_relative "cyberarm_engine/model/parser"
require_relative "cyberarm_engine/model/parsers/wavefront_parser"
require_relative "cyberarm_engine/model/parsers/collada_parser" if defined?(Nokogiri)

View File

@@ -2,8 +2,10 @@ module CyberarmEngine
class Animator class Animator
DEFAULT_TWEEN = :linear DEFAULT_TWEEN = :linear
def initialize(start_time:, duration:, from:, to:, &block) def initialize(start_time:, duration:, from:, to:, &block)
@start_time, @duration = start_time, duration @start_time = start_time
@from, @to = from.dup, to.dup @duration = duration
@from = from.dup
@to = to.dup
@block = block @block = block
end end
@@ -23,7 +25,7 @@ module CyberarmEngine
from + (to - from) * send("tween_#{tween}", progress) from + (to - from) * send("tween_#{tween}", progress)
end end
def color_transition(from, to, tween = DEFAULT_TWEEN) def color_transition(from, to, _tween = DEFAULT_TWEEN)
r = transition(from.red, to.red) r = transition(from.red, to.red)
g = transition(from.green, to.green) g = transition(from.green, to.green)
b = transition(from.blue, to.blue) b = transition(from.blue, to.blue)

View File

@@ -2,9 +2,13 @@ module CyberarmEngine
class Background class Background
attr_accessor :x, :y, :z, :width, :height, :angle, :debug attr_accessor :x, :y, :z, :width, :height, :angle, :debug
attr_reader :background attr_reader :background
def initialize(x: 0, y: 0, z: 0, width: 0, height: 0, background: Gosu::Color::BLACK, angle: 0, debug: false) def initialize(x: 0, y: 0, z: 0, width: 0, height: 0, background: Gosu::Color::BLACK, angle: 0, debug: false)
@x,@y,@z = x,y,z @x = x
@width,@height = width,height @y = y
@z = z
@width = width
@height = height
@debug = debug @debug = debug
@paint = Paint.new(background) @paint = Paint.new(background)
@@ -31,8 +35,8 @@ module CyberarmEngine
end end
def update def update
origin_x = (@x + (@width/2)) origin_x = (@x + (@width / 2))
origin_y = (@y + (@height/2)) origin_y = (@y + (@height / 2))
points = [ points = [
@top_left = Vector.new(@x, @y), @top_left = Vector.new(@x, @y),
@@ -47,8 +51,8 @@ module CyberarmEngine
# 90 is up here, while gosu uses 0 for up. # 90 is up here, while gosu uses 0 for up.
radians = (@angle + 90).gosu_to_radians radians = (@angle + 90).gosu_to_radians
vector.x = (@x + (@width/2)) + ((temp_x * Math.cos(radians)) - (temp_y * Math.sin(radians))) vector.x = (@x + (@width / 2)) + ((temp_x * Math.cos(radians)) - (temp_y * Math.sin(radians)))
vector.y = (@y + (@height/2)) + ((temp_x * Math.sin(radians)) + (temp_y * Math.cos(radians))) vector.y = (@y + (@height / 2)) + ((temp_x * Math.sin(radians)) + (temp_y * Math.cos(radians)))
end end
# [ # [
@@ -67,11 +71,11 @@ module CyberarmEngine
a = la.x - lb.x a = la.x - lb.x
b = la.y - lb.y b = la.y - lb.y
c = Gosu.distance(la.x, la.y, lb.x, lb.y) c = Gosu.distance(la.x, la.y, lb.x, lb.y)
p a,b,c p a, b, c
d = (a * point.x + b * point.y + c).abs / (Math.sqrt(a * a + b * b)) d = (a * point.x + b * point.y + c).abs / Math.sqrt(a * a + b * b)
puts "Distance: #{d}" puts "Distance: #{d}"
exit! exit!
return d d
end end
def debug_outline def debug_outline
@@ -117,6 +121,7 @@ module CyberarmEngine
class Paint class Paint
attr_accessor :top_left, :top_right, :bottom_left, :bottom_right attr_accessor :top_left, :top_right, :bottom_left, :bottom_right
def initialize(background) def initialize(background)
set(background) set(background)
end end
@@ -124,7 +129,6 @@ module CyberarmEngine
def set(background) def set(background)
@background = background @background = background
if background.is_a?(Numeric) if background.is_a?(Numeric)
@top_left = background @top_left = background
@top_right = background @top_right = background
@@ -168,7 +172,7 @@ end
# Add <=> method to support Range based gradients # Add <=> method to support Range based gradients
module Gosu module Gosu
class Color class Color
def <=>(other) def <=>(_other)
self self
end end
end end

View File

@@ -0,0 +1,125 @@
module CyberarmEngine
class BackgroundNineSlice
include CyberarmEngine::Common
attr_accessor :x, :y, :z, :width, :height
def initialize(image_path:, x: 0, y: 0, z: 0, width: 64, height: 64, mode: :tiled, left: 4, top: 4, right: 56, bottom: 56)
@image = get_image(image_path)
@x = x
@y = y
@z = z
@width = width
@height = height
@mode = mode
@left = left
@top = top
@right = right
@bottom = bottom
nine_slice
end
def nine_slice
@segment_top_left = Gosu.render(@left, @top) { @image.draw(0, 0, 0) }
@segment_top_right = Gosu.render(@image.width - @right, @top) { @image.draw(-@right, 0, 0) }
@segment_left = Gosu.render(@left, @bottom - @top) { @image.draw(0, -@top, 0) }
@segment_right = Gosu.render(@image.width - @right, @bottom - @top) { @image.draw(-@right, -@top, 0) }
@segment_bottom_left = Gosu.render(@left, @image.height - @bottom) { @image.draw(0, -@bottom, 0) }
@segment_bottom_right = Gosu.render(@image.width - @right, @image.height - @bottom) { @image.draw(-@right, -@bottom, 0) }
@segment_top = Gosu.render(@right - @left, @top) { @image.draw(-@left, 0, 0) }
@segment_bottom = Gosu.render(@right - @left, @image.height - @bottom) { @image.draw(-@left, -@bottom, 0) }
@segment_middle = Gosu.render(@right - @left, @bottom - @top) { @image.draw(-@left, -@top, 0) }
end
def cx
@x + @left
end
def cy
@y + @top
end
def cwidth
@cx - @width
end
def cheight
@cy - @height
end
def width_scale
width_scale = (@width - (@left + (@image.width - @right))).to_f / (@right - @left)
end
def height_scale
height_scale = (@height - (@top + (@image.height - @bottom))).to_f / (@bottom - @top)
end
def draw
@mode == :tiled ? draw_tiled : draw_stretched
end
def draw_stretched
@segment_top_left.draw(@x, @y, @z)
@segment_top.draw(@x + @segment_top_left.width, @y, @z, width_scale) # SCALE X
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z)
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top, @z, 1, height_scale) # SCALE Y
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z)
@segment_bottom.draw(@x + @segment_bottom_left.width, (@y + @height) - @segment_bottom.height, @z, width_scale) # SCALE X
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z)
@segment_left.draw(@x, @y + @top, @z, 1, height_scale) # SCALE Y
@segment_middle.draw(@x + @segment_top_left.width, @y + @segment_top.height, @z, width_scale, height_scale) # SCALE X and SCALE Y
end
def draw_tiled
@segment_top_left.draw(@x, @y, @z)
Gosu.clip_to(@x + @segment_top_left.width, @y, @segment_top.width * width_scale, @segment_top.height) do
width_scale.ceil.times do |i|
@segment_top.draw(@x + @segment_top_left.width + (@segment_top.width * i), @y, @z) # SCALE X
end
end
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z)
Gosu.clip_to(@x + @width - @segment_top_right.width, @y + @top, @segment_right.width, @segment_right.height * height_scale) do
height_scale.ceil.times do |i|
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top + (@segment_right.height * i), @z) # SCALE Y
end
end
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z)
Gosu.clip_to(@x + @segment_top_left.width, @y + @height - @segment_bottom.height, @segment_top.width * width_scale, @segment_bottom.height) do
width_scale.ceil.times do |i|
@segment_bottom.draw(@x + @segment_bottom_left.width + (@segment_bottom.width * i), (@y + @height) - @segment_bottom.height, @z) # SCALE X
end
end
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z)
Gosu.clip_to(@x, @y + @top, @segment_left.width, @segment_left.height * height_scale) do
height_scale.ceil.times do |i|
@segment_left.draw(@x, @y + @top + (@segment_left.height * i), @z) # SCALE Y
end
end
Gosu.clip_to(@x + @segment_top_left.width, @y + @segment_top.height, @width - (@segment_left.width + @segment_right.width), @height - (@segment_top.height + @segment_bottom.height)) do
height_scale.ceil.times do |y|
width_scale.ceil.times do |x|
@segment_middle.draw(@x + @segment_top_left.width + (@segment_middle.width * x), @y + @segment_top.height + (@segment_middle.height * y), @z) # SCALE X and SCALE Y
end
end
end
end
end
end

View File

@@ -37,7 +37,7 @@ module CyberarmEngine
temp.max.y = [@max.y, other.max.y].max temp.max.y = [@max.y, other.max.y].max
temp.max.z = [@max.z, other.max.z].max temp.max.z = [@max.z, other.max.z].max
return temp temp
end end
# returns the difference between both bounding boxes # returns the difference between both bounding boxes
@@ -46,7 +46,7 @@ module CyberarmEngine
temp.min = @min - other.min temp.min = @min - other.min
temp.max = @max - other.max temp.max = @max - other.max
return temp temp
end end
# returns whether bounding box intersects other # returns whether bounding box intersects other
@@ -107,7 +107,7 @@ module CyberarmEngine
temp.max.y = @max.y.to_f * entity.scale.y temp.max.y = @max.y.to_f * entity.scale.y
temp.max.z = @max.z.to_f * entity.scale.z temp.max.z = @max.z.to_f * entity.scale.z
return temp temp
end end
def normalize_with_offset(entity) def normalize_with_offset(entity)
@@ -120,23 +120,23 @@ module CyberarmEngine
temp.max.y = @max.y.to_f * entity.scale.y + entity.position.y temp.max.y = @max.y.to_f * entity.scale.y + entity.position.y
temp.max.z = @max.z.to_f * entity.scale.z + entity.position.z temp.max.z = @max.z.to_f * entity.scale.z + entity.position.z
return temp temp
end end
def +(other) def +(other)
box = BoundingBox.new box = BoundingBox.new
box.min = self.min + other.min box.min = min + other.min
box.min = self.max + other.max box.min = max + other.max
return box box
end end
def -(other) def -(other)
box = BoundingBox.new box = BoundingBox.new
box.min = self.min - other.min box.min = min - other.min
box.min = self.max - other.max box.min = max - other.max
return box box
end end
def sum def sum

View File

@@ -0,0 +1,4 @@
module CyberarmEngine
module Cache
end
end

View File

@@ -0,0 +1,121 @@
module CyberarmEngine
module Cache
class DownloadManager
attr_reader :downloads
def initialize(max_parallel_downloads: 4)
@max_parallel_downloads = max_parallel_downloads
@downloads = []
end
def download(url:, save_as: nil, &callback)
uri = URI(url)
save_as ||= "filename_path" # TODO: if no save_as path is provided, then get one from the Cache controller
@downloads << Download.new(uri: uri, save_as: save_as, callback: callback)
end
def status
if active_downloads > 0
:busy
else
:idle
end
end
def progress
remaining_bytes = @downloads.map { |d| d.remaining_bytes }.sum
total_bytes = @downloads.map { |d| d.total_bytes }.sum
v = 1.0 - (remaining_bytes.to_f / total_bytes)
return 0.0 if v.nan?
v
end
def active_downloads
@downloads.select { |d| %i[pending downloading].include?(d.status) }
end
def update
@downloads.each do |download|
if download.status == :pending && active_downloads.size <= @max_parallel_downloads
download.status = :downloading
Thread.start { download.download }
end
end
end
def prune
@downloads.delete_if { |d| d.status == :finished || d.status == :failed }
end
class Download
attr_accessor :status
attr_reader :uri, :save_as, :callback, :remaining_bytes, :total_downloaded_bytes, :total_bytes,
:error_message, :started_at, :finished_at
def initialize(uri:, save_as:, callback: nil)
@uri = uri
@save_as = save_as
@callback = callback
@status = :pending
@remaining_bytes = 0.0
@total_downloaded_bytes = 0.0
@total_bytes = 0.0
@error_message = ""
end
def progress
v = 1.0 - (@remaining_bytes.to_f / total_bytes)
return 0.0 if v.nan?
v
end
def download
@status = :downloading
@started_at = Time.now # TODO: monotonic time
io = File.open(@save_as, "w")
streamer = lambda do |chunk, remaining_bytes, total_bytes|
io.write(chunk)
@remaining_bytes = remaining_bytes.to_f
@total_downloaded_bytes += chunk.size
@total_bytes = total_bytes.to_f
end
begin
response = Excon.get(
@uri.to_s,
middlewares: Excon.defaults[:middlewares] + [Excon::Middleware::RedirectFollower],
response_block: streamer
)
if response.status == 200
@status = :finished
@finished_at = Time.now # TODO: monotonic time
@callback.call(self) if @callback
else
@error_message = "Got a non 200 HTTP status of #{response.status}"
@status = :failed
@finished_at = Time.now # TODO: monotonic time
@callback.call(self) if @callback
end
rescue StandardError => e # TODO: cherrypick errors to cature
@status = :failed
@finished_at = Time.now # TODO: monotonic time
@error_message = e.message
@callback.call(self) if @callback
end
ensure
io.close if io
end
end
end
end
end

View File

@@ -1,6 +1,6 @@
module CyberarmEngine module CyberarmEngine
module Common module Common
def push_state(klass, options={}) def push_state(klass, options = {})
window.push_state(klass, options) window.push_state(klass, options)
end end
@@ -20,7 +20,7 @@ module CyberarmEngine
window.show_cursor window.show_cursor
end end
def show_cursor=boolean def show_cursor=(boolean)
window.show_cursor = boolean window.show_cursor = boolean
end end
@@ -34,24 +34,24 @@ module CyberarmEngine
def lighten(color, amount = 25) def lighten(color, amount = 25)
if defined?(color.alpha) if defined?(color.alpha)
return Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha) Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha)
else else
return Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount) Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount)
end end
end end
def darken(color, amount = 25) def darken(color, amount = 25)
if defined?(color.alpha) if defined?(color.alpha)
return Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha) Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha)
else else
return Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount) Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount)
end end
end end
def opacity(color, ratio = 1.0) def opacity(color, ratio = 1.0)
alpha = 255 * ratio alpha = 255 * ratio
return Gosu::Color.rgba(color.red, color.green, color.blue, alpha) Gosu::Color.rgba(color.red, color.green, color.blue, alpha)
end end
def get_asset(path, hash, klass, retro = false, tileable = false) def get_asset(path, hash, klass, retro = false, tileable = false)
@@ -65,28 +65,28 @@ module CyberarmEngine
unless asset unless asset
instance = nil instance = nil
if klass == Gosu::Image instance = if klass == Gosu::Image
instance = klass.new(path, retro: retro, tileable: tileable) klass.new(path, retro: retro, tileable: tileable)
else else
instance = klass.new(path) klass.new(path)
end end
hash[path] = instance hash[path] = instance
asset = instance asset = instance
end end
return asset asset
end end
def get_image(path, retro: false, tileable: false) def get_image(path, retro: false, tileable: false)
get_asset(path, Engine::IMAGES, Gosu::Image, retro, tileable) get_asset(path, Window::IMAGES, Gosu::Image, retro, tileable)
end end
def get_sample(path) def get_sample(path)
get_asset(path, Engine::SAMPLES, Gosu::Sample) get_asset(path, Window::SAMPLES, Gosu::Sample)
end end
def get_song(path) def get_song(path)
get_asset(path, Engine::SONGS, Gosu::Song) get_asset(path, Window::SONGS, Gosu::Song)
end end
def window def window

View File

@@ -10,7 +10,7 @@ module CyberarmEngine
end end
end end
def []= *keys, value def []=(*keys, value)
last_key = keys.last last_key = keys.last
if keys.size == 1 if keys.size == 1
@@ -20,7 +20,7 @@ module CyberarmEngine
hash = @data[keys.shift] ||= {} hash = @data[keys.shift] ||= {}
keys.each do |key| keys.each do |key|
hash[key] ||= {} hash = hash[key] ||= {}
end end
end end

View File

@@ -5,55 +5,60 @@ module CyberarmEngine
attr_accessor :image, :angle, :position, :velocity, :center_x, :center_y, :scale_x, :scale_y, attr_accessor :image, :angle, :position, :velocity, :center_x, :center_y, :scale_x, :scale_y,
:color, :mode, :options, :paused, :radius, :last_position :color, :mode, :options, :paused, :radius, :last_position
attr_reader :alpha attr_reader :alpha
def initialize(options={})
if options[:auto_manage] || options[:auto_manage] == nil def initialize(options = {})
$window.current_state.add_game_object(self) $window.current_state.add_game_object(self) if options[:auto_manage] || options[:auto_manage].nil?
end
@options = options @options = options
@image = options[:image] ? image(options[:image]) : nil @image = options[:image] ? image(options[:image]) : nil
x = options[:x] ? options[:x] : 0 x = options[:x] || 0
y = options[:y] ? options[:y] : 0 y = options[:y] || 0
z = options[:z] ? options[:z] : 0 z = options[:z] || 0
@position = Vector.new(x, y, z) @position = Vector.new(x, y, z)
@velocity = Vector.new @velocity = Vector.new
@last_position = Vector.new @last_position = Vector.new
@angle = options[:angle] ? options[:angle] : 0 @angle = options[:angle] || 0
@center_x = options[:center_x] ? options[:center_x] : 0.5 @center_x = options[:center_x] || 0.5
@center_y = options[:center_y] ? options[:center_y] : 0.5 @center_y = options[:center_y] || 0.5
@scale_x = options[:scale_x] ? options[:scale_x] : 1 @scale_x = options[:scale_x] || 1
@scale_y = options[:scale_y] ? options[:scale_y] : 1 @scale_y = options[:scale_y] || 1
@color = options[:color] ? options[:color] : Gosu::Color.argb(0xff_ffffff) @color = options[:color] || Gosu::Color.argb(0xff_ffffff)
@alpha = options[:alpha] ? options[:alpha] : 255 @alpha = options[:alpha] || 255
@mode = options[:mode] ? options[:mode] : :default @mode = options[:mode] || :default
@paused = false @paused = false
@speed = 0 @speed = 0
@debug_color = Gosu::Color::GREEN @debug_color = Gosu::Color::GREEN
@world_center_point = Vector.new(0,0) @world_center_point = Vector.new(0, 0)
setup setup
@debug_text = Text.new("", color: @debug_color, y: @position.y-(self.height*self.scale), z: 9999) @debug_text = Text.new("", color: @debug_color, y: @position.y - (height * scale), z: 9999)
@debug_text.x = @position.x @debug_text.x = @position.x
if @radius == 0 || @radius == nil if @radius == 0 || @radius.nil?
@radius = options[:radius] ? options[:radius] : defined?(@image.width) ? ((@image.width+@image.height)/4)*scale : 1 @radius = if options[:radius]
options[:radius]
else
defined?(@image.width) ? ((@image.width + @image.height) / 4) * scale : 1
end
end end
end end
def draw def draw
if @image if @image
@image.draw_rot(@position.x, @position.y, @position.z, @angle, @center_x, @center_y, @scale_x, @scale_y, @color, @mode) @image.draw_rot(@position.x, @position.y, @position.z, @angle, @center_x, @center_y, @scale_x, @scale_y,
@color, @mode)
end end
if $debug if $debug
show_debug_heading show_debug_heading
$window.draw_circle(@position.x, @position.y, radius, 9999, @debug_color) $window.draw_circle(@position.x, @position.y, radius, 9999, @debug_color)
if @debug_text.text != "" if @debug_text.text != ""
$window.draw_rect(@debug_text.x-10, (@debug_text.y-10), @debug_text.width+20, @debug_text.height+20, Gosu::Color.rgba(0,0,0,200), 9999) $window.draw_rect(@debug_text.x - 10, (@debug_text.y - 10), @debug_text.width + 20, @debug_text.height + 20,
Gosu::Color.rgba(0, 0, 0, 200), 9999)
@debug_text.draw @debug_text.draw
end end
end end
@@ -64,13 +69,13 @@ module CyberarmEngine
def debug_text(text) def debug_text(text)
@debug_text.text = text @debug_text.text = text
@debug_text.x = @position.x-(@debug_text.width / 2) @debug_text.x = @position.x - (@debug_text.width / 2)
@debug_text.y = @position.y-(@debug_text.height + self.radius + self.height) @debug_text.y = @position.y - (@debug_text.height + radius + height)
end end
def scale def scale
if @scale_x == @scale_y if @scale_x == @scale_y
return @scale_x @scale_x
else else
false false
# maths? # maths?
@@ -80,7 +85,7 @@ module CyberarmEngine
def scale=(int) def scale=(int)
self.scale_x = int self.scale_x = int
self.scale_y = int self.scale_y = int
self.radius = ((@image.width+@image.height)/4)*self.scale self.radius = ((@image.width + @image.height) / 4) * scale
end end
def visible def visible
@@ -97,16 +102,16 @@ module CyberarmEngine
end end
def _x_visible def _x_visible
self.x.between?(($window.width/2)-(@world_center_point.x), ($window.width/2)+@world_center_point.x) || x.between?(($window.width / 2) - @world_center_point.x, ($window.width / 2) + @world_center_point.x) ||
self.x.between?(((@world_center_point.x)-$window.width/2), ($window.width/2)+@world_center_point.x) x.between?((@world_center_point.x - $window.width / 2), ($window.width / 2) + @world_center_point.x)
end end
def _y_visible def _y_visible
self.y.between?(($window.height/2)-(@world_center_point.y), ($window.height/2)+@world_center_point.y) || y.between?(($window.height / 2) - @world_center_point.y, ($window.height / 2) + @world_center_point.y) ||
self.y.between?((@world_center_point.y)-($window.height/2), ($window.height/2)+@world_center_point.y) y.between?(@world_center_point.y - ($window.height / 2), ($window.height / 2) + @world_center_point.y)
end end
def heading(ahead_by = 100, object = nil, angle_only = false) def heading(ahead_by = 100, _object = nil, angle_only = false)
direction = Gosu.angle(@last_position.x, @last_position.x, @position.x, position.y).gosu_to_radians direction = Gosu.angle(@last_position.x, @last_position.x, @position.x, position.y).gosu_to_radians
_x = @position.x + (ahead_by * Math.cos(direction)) _x = @position.x + (ahead_by * Math.cos(direction))
@@ -122,11 +127,11 @@ module CyberarmEngine
end end
def width def width
@image ? @image.width * self.scale : 0 @image ? @image.width * scale : 0
end end
def height def height
@image ? @image.height * self.scale : 0 @image ? @image.height * scale : 0
end end
def pause def pause
@@ -138,8 +143,8 @@ module CyberarmEngine
end end
def rotate(int) def rotate(int)
self.angle+=int self.angle += int
self.angle%=360 self.angle %= 360
end end
def alpha=(int) # 0-255 def alpha=(int) # 0-255
@@ -149,7 +154,7 @@ module CyberarmEngine
end end
def draw_rect(x, y, width, height, color, z = 0) def draw_rect(x, y, width, height, color, z = 0)
$window.draw_rect(x,y,width,height,color,z) $window.draw_rect(x, y, width, height, color, z)
end end
def button_up(id) def button_up(id)
@@ -163,14 +168,14 @@ module CyberarmEngine
best_distance = 100_000_000_000 # Huge default number best_distance = 100_000_000_000 # Huge default number
game_object_class.all.each do |object| game_object_class.all.each do |object|
distance = Gosu::distance(self.x, self.y, object.x, object.y) distance = Gosu.distance(x, y, object.x, object.y)
if distance <= best_distance if distance <= best_distance
best_object = object best_object = object
best_distance = distance best_distance = distance
end end
end end
return best_object best_object
end end
def look_at(object) def look_at(object)
@@ -178,31 +183,24 @@ module CyberarmEngine
end end
def circle_collision?(object) def circle_collision?(object)
distance = Gosu.distance(self.x, self.y, object.x, object.y) distance = Gosu.distance(x, y, object.x, object.y)
if distance <= self.radius+object.radius distance <= radius + object.radius
true
else
false
end
end end
# Duplication... so DRY. # Duplication... so DRY.
def each_circle_collision(object, resolve_with = :width, &block) def each_circle_collision(object, _resolve_with = :width, &block)
if object.class != Class && object.instance_of?(object.class) if object.class != Class && object.instance_of?(object.class)
$window.current_state.game_objects.select {|i| i.class == object.class}.each do |o| $window.current_state.game_objects.select { |i| i.instance_of?(object.class) }.each do |o|
distance = Gosu.distance(self.x, self.y, object.x, object.y) distance = Gosu.distance(x, y, object.x, object.y)
if distance <= self.radius+object.radius block.call(o, object) if distance <= radius + object.radius && block
block.call(o, object) if block
end
end end
else else
list = $window.current_state.game_objects.select {|i| i.class == object} list = $window.current_state.game_objects.select { |i| i.instance_of?(object) }
list.each do |o| list.each do |o|
next if self == o next if self == o
distance = Gosu.distance(self.x, self.y, o.x, o.y)
if distance <= self.radius+o.radius distance = Gosu.distance(x, y, o.x, o.y)
block.call(self, o) if block block.call(self, o) if distance <= radius + o.radius && block
end
end end
end end
end end
@@ -210,35 +208,30 @@ module CyberarmEngine
def destroy def destroy
if $window.current_state if $window.current_state
$window.current_state.game_objects.each do |o| $window.current_state.game_objects.each do |o|
if o.is_a?(self.class) && o == self $window.current_state.game_objects.delete(o) if o.is_a?(self.class) && o == self
$window.current_state.game_objects.delete(o)
end
end end
end end
end end
# NOTE: This could be implemented more reliably # NOTE: This could be implemented more reliably
def all def all
INSTANCES.select {|i| i.class == self} INSTANCES.select { |i| i.instance_of?(self) }
end end
def self.each_circle_collision(object, resolve_with = :width, &block) def self.each_circle_collision(object, _resolve_with = :width, &block)
if object.class != Class && object.instance_of?(object.class) if object.class != Class && object.instance_of?(object.class)
$window.current_state.game_objects.select {|i| i.class == self}.each do |o| $window.current_state.game_objects.select { |i| i.instance_of?(self) }.each do |o|
distance = Gosu.distance(o.x, o.y, object.x, object.y) distance = Gosu.distance(o.x, o.y, object.x, object.y)
if distance <= o.radius+object.radius block.call(o, object) if distance <= o.radius + object.radius && block
block.call(o, object) if block
end
end end
else else
lista = $window.current_state.game_objects.select {|i| i.class == self} lista = $window.current_state.game_objects.select { |i| i.instance_of?(self) }
listb = $window.current_state.game_objects.select {|i| i.class == object} listb = $window.current_state.game_objects.select { |i| i.instance_of?(object) }
lista.product(listb).each do |o, o2| lista.product(listb).each do |o, o2|
next if o == o2 next if o == o2
distance = Gosu.distance(o.x, o.y, o2.x, o2.y) distance = Gosu.distance(o.x, o.y, o2.x, o2.y)
if distance <= o.radius+o2.radius block.call(o, o2) if distance <= o.radius + o2.radius && block
block.call(o, o2) if block
end
end end
end end
end end
@@ -247,9 +240,7 @@ module CyberarmEngine
INSTANCES.clear INSTANCES.clear
if $window.current_state if $window.current_state
$window.current_state.game_objects.each do |o| $window.current_state.game_objects.each do |o|
if o.is_a?(self.class) $window.current_state.game_objects.delete(o) if o.is_a?(self.class)
$window.current_state.game_objects.delete(o)
end
end end
end end
end end

View File

@@ -5,7 +5,7 @@ module CyberarmEngine
attr_accessor :options, :global_pause attr_accessor :options, :global_pause
attr_reader :game_objects attr_reader :game_objects
def initialize(options={}) def initialize(options = {})
@options = options @options = options
@game_objects = [] @game_objects = []
@global_pause = false @global_pause = false
@@ -26,7 +26,10 @@ module CyberarmEngine
end end
def draw_bounding_box(box) def draw_bounding_box(box)
x,y, max_x, max_y = box.x, box.y, box.max_x, box.max_y x = box.x
y = box.y
max_x = box.max_x
max_y = box.max_y
color = Gosu::Color.rgba(255, 127, 64, 240) color = Gosu::Color.rgba(255, 127, 64, 240)

View File

@@ -1,9 +0,0 @@
module Gosu
# Sourced from https://gist.github.com/ippa/662583
def self.draw_circle(cx,cy,r, z = 9999,color = Gosu::Color::GREEN, step = 10)
0.step(360, step) do |a1|
a2 = a1 + step
draw_line(cx + Gosu.offset_x(a1, r), cy + Gosu.offset_y(a1, r), color, cx + Gosu.offset_x(a2, r), cy + Gosu.offset_y(a2, r), color, z)
end
end
end

View File

@@ -0,0 +1,207 @@
module CyberarmEngine
class Model
attr_accessor :objects, :materials, :vertices, :uvs, :texures, :normals, :faces, :colors, :bones, :material_file,
:current_material, :current_object, :vertex_count, :smoothing
attr_reader :position, :bounding_box, :textured_material, :file_path, :positions_buffer_id, :colors_buffer_id,
:normals_buffer_id, :uvs_buffer_id, :textures_buffer_id, :vertex_array_id, :aabb_tree
def initialize(file_path:)
@file_path = file_path
@material_file = nil
@current_object = nil
@current_material = nil
@vertex_count = 0
@objects = []
@materials = {}
@vertices = []
@colors = []
@uvs = []
@normals = []
@faces = []
@bones = []
@smoothing = 0
@bounding_box = BoundingBox.new
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
type = File.basename(file_path).split(".").last.to_sym
parser = Model::Parser.find(type)
raise "Unsupported model type '.#{type}', supported models are: #{Model::Parser.supported_formats}" unless parser
parse(parser)
@has_texture = false
@materials.each do |_key, material|
@has_texture = true if material.texture_id
end
allocate_gl_objects
populate_vertex_buffer
configure_vao
@objects.each { |o| @vertex_count += o.vertices.size }
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
# build_collision_tree
end
def parse(parser)
parser.new(self).parse
end
def calculate_bounding_box(vertices, bounding_box)
unless bounding_box.min.x.is_a?(Float)
vertex = vertices.last
bounding_box.min.x = vertex.x
bounding_box.min.y = vertex.y
bounding_box.min.z = vertex.z
bounding_box.max.x = vertex.x
bounding_box.max.y = vertex.y
bounding_box.max.z = vertex.z
end
vertices.each do |vertex|
bounding_box.min.x = vertex.x if vertex.x <= bounding_box.min.x
bounding_box.min.y = vertex.y if vertex.y <= bounding_box.min.y
bounding_box.min.z = vertex.z if vertex.z <= bounding_box.min.z
bounding_box.max.x = vertex.x if vertex.x >= bounding_box.max.x
bounding_box.max.y = vertex.y if vertex.y >= bounding_box.max.y
bounding_box.max.z = vertex.z if vertex.z >= bounding_box.max.z
end
end
def allocate_gl_objects
# Allocate arrays for future use
@vertex_array_id = nil
buffer = " " * 4
glGenVertexArrays(1, buffer)
@vertex_array_id = buffer.unpack1("L2")
# Allocate buffers for future use
@positions_buffer_id = nil
buffer = " " * 4
glGenBuffers(1, buffer)
@positions_buffer_id = buffer.unpack1("L2")
@colors_buffer_id = nil
buffer = " " * 4
glGenBuffers(1, buffer)
@colors_buffer_id = buffer.unpack1("L2")
@normals_buffer_id = nil
buffer = " " * 4
glGenBuffers(1, buffer)
@normals_buffer_id = buffer.unpack1("L2")
@uvs_buffer_id = nil
buffer = " " * 4
glGenBuffers(1, buffer)
@uvs_buffer_id = buffer.unpack1("L2")
end
def populate_vertex_buffer
pos = []
colors = []
norms = []
uvs = []
@faces.each do |face|
pos << face.vertices.map { |vert| [vert.x, vert.y, vert.z] }
colors << face.colors.map { |color| [color.red, color.green, color.blue] }
norms << face.normals.map { |vert| [vert.x, vert.y, vert.z, vert.weight] }
uvs << face.uvs.map { |vert| [vert.x, vert.y, vert.z] } if has_texture?
end
glBindBuffer(GL_ARRAY_BUFFER, @positions_buffer_id)
glBufferData(GL_ARRAY_BUFFER, pos.flatten.size * Fiddle::SIZEOF_FLOAT, pos.flatten.pack("f*"), GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, @colors_buffer_id)
glBufferData(GL_ARRAY_BUFFER, colors.flatten.size * Fiddle::SIZEOF_FLOAT, colors.flatten.pack("f*"),
GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, @normals_buffer_id)
glBufferData(GL_ARRAY_BUFFER, norms.flatten.size * Fiddle::SIZEOF_FLOAT, norms.flatten.pack("f*"), GL_STATIC_DRAW)
if has_texture?
glBindBuffer(GL_ARRAY_BUFFER, @uvs_buffer_id)
glBufferData(GL_ARRAY_BUFFER, uvs.flatten.size * Fiddle::SIZEOF_FLOAT, uvs.flatten.pack("f*"), GL_STATIC_DRAW)
end
glBindBuffer(GL_ARRAY_BUFFER, 0)
end
def configure_vao
glBindVertexArray(@vertex_array_id)
gl_error?
# index, size, type, normalized, stride, pointer
# vertices (positions)
glBindBuffer(GL_ARRAY_BUFFER, @positions_buffer_id)
gl_error?
# inPosition
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nil)
gl_error?
# colors
glBindBuffer(GL_ARRAY_BUFFER, @colors_buffer_id)
# inColor
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nil)
gl_error?
# normals
glBindBuffer(GL_ARRAY_BUFFER, @normals_buffer_id)
# inNormal
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, nil)
gl_error?
if has_texture?
# uvs
glBindBuffer(GL_ARRAY_BUFFER, @uvs_buffer_id)
# inUV
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, nil)
gl_error?
end
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
end
def build_collision_tree
@aabb_tree = AABBTree.new
@faces.each do |face|
box = BoundingBox.new
box.min = face.vertices.first.dup
box.max = face.vertices.first.dup
face.vertices.each do |vertex|
if vertex.sum < box.min.sum
box.min = vertex.dup
elsif vertex.sum > box.max.sum
box.max = vertex.dup
end
end
# FIXME: Handle negatives
box.min *= 1.5
box.max *= 1.5
@aabb_tree.insert(face, box)
end
end
def has_texture?
@has_texture
end
def release_gl_resources
if @vertex_array_id
end
end
end
end

View File

@@ -0,0 +1,21 @@
module CyberarmEngine
class Model
class Material
attr_accessor :name, :ambient, :diffuse, :specular
attr_reader :texture_id
def initialize(name)
@name = name
@ambient = Color.new(1, 1, 1, 1)
@diffuse = Color.new(1, 1, 1, 1)
@specular = Color.new(1, 1, 1, 1)
@texture = nil
@texture_id = nil
end
def set_texture(texture_path)
@texture_id = Texture.new(path: texture_path).id
end
end
end
end

View File

@@ -0,0 +1,131 @@
module CyberarmEngine
class Model
class ModelObject
attr_reader :id, :name, :vertices, :uvs, :normals, :materials, :bounding_box, :debug_color
attr_accessor :faces, :scale
def initialize(id, name)
@id = id
@name = name
@vertices = []
@uvs = []
@normals = []
@faces = []
@materials = []
@bounding_box = BoundingBox.new
@debug_color = Color.new(1.0, 1.0, 1.0)
@scale = 1.0
# Faces array packs everything:
# vertex = index[0]
# uv = index[1]
# normal = index[2]
# material = index[3]
end
def has_texture?
@materials.find { |mat| mat.texture_id } ? true : false
end
def reflatten
@vertices_list = nil
@uvs_list = nil
@normals_list = nil
flattened_vertices
flattened_uvs
flattened_normals
end
def flattened_vertices
unless @vertices_list
@debug_color = @faces.first.material.diffuse
list = []
@faces.each do |face|
face.vertices.each do |v|
next unless v
list << v.x * @scale
list << v.y * @scale
list << v.z * @scale
list << v.weight
end
end
@vertices_list_size = list.size
@vertices_list = list.pack("f*")
end
@vertices_list
end
def flattened_vertices_size
@vertices_list_size
end
def flattened_uvs
unless @uvs_list
list = []
@faces.each do |face|
face.uvs.each do |v|
next unless v
list << v.x
list << v.y
list << v.z
end
end
@uvs_list_size = list.size
@uvs_list = list.pack("f*")
end
@uvs_list
end
def flattened_normals
unless @normals_list
list = []
@faces.each do |face|
face.normals.each do |n|
next unless n
list << n.x
list << n.y
list << n.z
end
end
@normals_list_size = list.size
@normals_list = list.pack("f*")
end
@normals_list
end
def flattened_materials
unless @materials_list
list = []
@faces.each do |face|
material = face.material
next unless material
face.vertices.each do # Add material to each vertex
list << material.diffuse.red
list << material.diffuse.green
list << material.diffuse.blue
# list << material.alpha
end
end
@materials_list_size = list.size
@materials_list = list.pack("f*")
end
@materials_list
end
end
end
end

View File

@@ -0,0 +1,74 @@
module CyberarmEngine
TextureCoordinate = Struct.new(:u, :v, :weight)
Point = Struct.new(:x, :y)
Color = Struct.new(:red, :green, :blue, :alpha)
Face = Struct.new(:vertices, :uvs, :normals, :colors, :material, :smoothing)
class Model
class Parser
@@parsers = []
def self.handles
raise NotImplementedError,
"Model::Parser#handles must return an array of file extensions that this parser supports"
end
def self.inherited(parser)
@@parsers << parser
end
def self.find(file_type)
@@parsers.find do |parser|
parser.handles.include?(file_type)
end
end
def self.supported_formats
@@parsers.map { |parser| parser.handles }.flatten.map { |s| ".#{s}" }.join(", ")
end
def initialize(model)
@model = model
end
def parse
end
def set_object(id: nil, name: nil)
_model = nil
if id
_model = @model.objects.find { |o| o.id == id }
elsif name
_model = @model.objects.find { |o| o.name == name }
else
raise "Must provide either an id: or name:"
end
if _model
@model.current_object = _model
else
raise "Couldn't find ModelObject!"
end
end
def change_object(id, name)
@model.objects << Model::ModelObject.new(id, name)
@model.current_object = @model.objects.last
end
def set_material(name)
@model.current_material = name
@model.current_object.materials << current_material
end
def add_material(name, material)
@model.materials[name] = material
end
def current_material
@model.materials[@model.current_material]
end
end
end
end

View File

@@ -0,0 +1,138 @@
module CyberarmEngine
class ColladaParser < Model::Parser
def self.handles
[:dae]
end
def parse
@collada = Nokogiri::XML(File.read(@model.file_path))
@collada.css("library_materials material").each do |material|
parse_material(material)
end
@collada.css("library_geometries geometry").each do |geometry|
parse_geometry(geometry)
end
@model.calculate_bounding_box(@model.vertices, @model.bounding_box)
@model.objects.each do |o|
@model.calculate_bounding_box(o.vertices, o.bounding_box)
end
end
def parse_material(material)
name = material.attributes["id"].value
effect_id = material.at_css("instance_effect").attributes["url"].value
mat = Model::Material.new(name)
effect = @collada.at_css("[id=\"#{effect_id.sub('#', '')}\"]")
emission = effect.at_css("emission color")
diffuse = effect.at_css("diffuse color").children.first.to_s.split(" ").map { |c| Float(c) }
mat.diffuse = Color.new(*diffuse[0..2])
add_material(name, mat)
end
def parse_geometry(geometry)
geometry_id = geometry.attributes["id"].value
geometry_name = geometry.attributes["name"].value
change_object(geometry_id, geometry_name)
mesh = geometry.at_css("mesh")
get_positions(geometry_id, mesh)
get_normals(geometry_id, mesh)
get_texture_coordinates(geometry_id, mesh)
project_node(geometry_name)
build_faces(geometry_id, mesh)
end
def get_positions(id, mesh)
positions = mesh.at_css("[id=\"#{id}-positions\"]")
array = positions.at_css("[id=\"#{id}-positions-array\"]")
stride = Integer(positions.at_css("[source=\"##{id}-positions-array\"]").attributes["stride"].value)
list = array.children.first.to_s.split(" ").map { |f| Float(f) }.each_slice(stride).each do |slice|
position = Vector.new(*slice)
@model.current_object.vertices << position
@model.vertices << position
end
end
def get_normals(id, mesh)
normals = mesh.at_css("[id=\"#{id}-normals\"]")
array = normals.at_css("[id=\"#{id}-normals-array\"]")
stride = Integer(normals.at_css("[source=\"##{id}-normals-array\"]").attributes["stride"].value)
list = array.children.first.to_s.split(" ").map { |f| Float(f) }.each_slice(stride).each do |slice|
normal = Vector.new(*slice)
@model.current_object.normals << normal
@model.normals << normal
end
end
def get_texture_coordinates(id, mesh)
end
def project_node(name)
@collada.css("library_visual_scenes visual_scene node").each do |node|
next unless node.attributes["name"].value == name
transform = Transform.new(node.at_css("matrix").children.first.to_s.split(" ").map { |f| Float(f) })
@model.current_object.vertices.each do |vert|
v = vert.multiply_transform(transform)
vert.x = v.x
vert.y = v.y
vert.z = v.z
vert.w = v.w
end
break
end
end
def build_faces(_id, mesh)
material_name = mesh.at_css("triangles").attributes["material"].value
set_material(material_name)
positions_index = []
normals_index = []
uvs_index = []
mesh.at_css("triangles p").children.first.to_s.split(" ").map { |i| Integer(i) }.each_slice(3).each do |slice|
positions_index << slice[0]
normals_index << slice[1]
uvs_index << slice[2]
end
norm_index = 0
positions_index.each_slice(3) do |slice|
face = Face.new
face.vertices = []
face.uvs = []
face.normals = []
face.colors = []
face.material = current_material
face.smoothing = @model.smoothing
slice.each do |index|
face.vertices << @model.vertices[index]
# face.uvs << @model.uvs[index]
face.normals << @model.normals[normals_index[norm_index]]
face.colors << current_material.diffuse
norm_index += 1
end
@model.current_object.faces << face
@model.faces << face
end
end
end
end

View File

@@ -0,0 +1,154 @@
module CyberarmEngine
class WavefrontParser < Model::Parser
def self.handles
[:obj]
end
def parse
lines = 0
list = File.read(@model.file_path).split("\n")
list.each do |line|
lines += 1
line = line.strip
array = line.split(" ")
case array[0]
when "mtllib"
@model.material_file = array[1]
parse_mtllib
when "usemtl"
set_material(array[1])
when "o"
change_object(nil, array[1])
when "s"
set_smoothing(array[1])
when "v"
add_vertex(array)
when "vt"
add_texture_coordinate(array)
when "vn"
add_normal(array)
when "f"
verts = []
uvs = []
norms = []
array[1..3].each do |f|
verts << f.split("/")[0]
uvs << f.split("/")[1]
norms << f.split("/")[2]
end
face = Face.new
face.vertices = []
face.uvs = []
face.normals = []
face.colors = []
face.material = current_material
face.smoothing = @model.smoothing
mat = face.material.diffuse
color = mat
verts.each_with_index do |v, index|
if uvs.first != ""
face.vertices << @model.vertices[Integer(v) - 1]
face.uvs << @model.uvs[Integer(uvs[index]) - 1]
face.normals << @model.normals[Integer(norms[index]) - 1]
face.colors << color
else
face.vertices << @model.vertices[Integer(v) - 1]
face.uvs << nil
face.normals << @model.normals[Integer(norms[index]) - 1]
face.colors << color
end
end
@model.current_object.faces << face
@model.faces << face
end
end
@model.calculate_bounding_box(@model.vertices, @model.bounding_box)
@model.objects.each do |o|
@model.calculate_bounding_box(o.vertices, o.bounding_box)
end
end
def parse_mtllib
file = File.open(@model.file_path.sub(File.basename(@model.file_path), "") + @model.material_file, "r")
file.readlines.each do |line|
array = line.strip.split(" ")
case array.first
when "newmtl"
material = Model::Material.new(array.last)
@model.current_material = array.last
@model.materials[array.last] = material
when "Ns" # Specular Exponent
when "Ka" # Ambient color
@model.materials[@model.current_material].ambient = Color.new(Float(array[1]), Float(array[2]),
Float(array[3]))
when "Kd" # Diffuse color
@model.materials[@model.current_material].diffuse = Color.new(Float(array[1]), Float(array[2]),
Float(array[3]))
when "Ks" # Specular color
@model.materials[@model.current_material].specular = Color.new(Float(array[1]), Float(array[2]),
Float(array[3]))
when "Ke" # Emissive
when "Ni" # Unknown (Blender Specific?)
when "d" # Dissolved (Transparency)
when "illum" # Illumination model
when "map_Kd" # Diffuse texture
texture = File.basename(array[1])
texture_path = "#{File.expand_path('../../', @model.file_path)}/textures/#{texture}"
@model.materials[@model.current_material].set_texture(texture_path)
end
end
end
def set_smoothing(value)
@model.smoothing = value == "1"
end
def add_vertex(array)
@model.vertex_count += 1
vert = nil
if array.size == 5
vert = Vector.new(Float(array[1]), Float(array[2]), Float(array[3]), Float(array[4]))
elsif array.size == 4
vert = Vector.new(Float(array[1]), Float(array[2]), Float(array[3]), 1.0)
else
raise
end
@model.current_object.vertices << vert
@model.vertices << vert
end
def add_normal(array)
vert = nil
if array.size == 5
vert = Vector.new(Float(array[1]), Float(array[2]), Float(array[3]), Float(array[4]))
elsif array.size == 4
vert = Vector.new(Float(array[1]), Float(array[2]), Float(array[3]), 1.0)
else
raise
end
@model.current_object.normals << vert
@model.normals << vert
end
def add_texture_coordinate(array)
texture = nil
if array.size == 4
texture = Vector.new(Float(array[1]), 1 - Float(array[2]), Float(array[3]))
elsif array.size == 3
texture = Vector.new(Float(array[1]), 1 - Float(array[2]), 1.0)
else
raise
end
@model.uvs << texture
@model.current_object.uvs << texture
end
end
end

View File

@@ -0,0 +1,31 @@
module CyberarmEngine
module ModelCache
CACHE = {}
def self.find_or_cache(manifest:)
model_file = manifest.file_path + "/model/#{manifest.model}"
type = File.basename(model_file).split(".").last.to_sym
if model = load_model_from_cache(type, model_file)
model
else
model = CyberarmEngine::Model.new(file_path: model_file)
cache_model(type, model_file, model)
model
end
end
def self.load_model_from_cache(type, model_file)
return CACHE[type][model_file] if CACHE[type].is_a?(Hash) && (CACHE[type][model_file])
false
end
def self.cache_model(type, model_file, model)
CACHE[type] = {} unless CACHE[type].is_a?(Hash)
CACHE[type][model_file] = model
end
end
end

View File

@@ -0,0 +1,28 @@
begin
require "opengl"
rescue LoadError
puts "Required gem is not installed, please install 'opengl-bindings' and try again."
exit(1)
end
module CyberarmEngine
def gl_error?
e = glGetError
if e != GL_NO_ERROR
warn "OpenGL error detected by handler at: #{caller[0]}"
warn " #{gluErrorString(e)} (#{e})\n"
exit if window.exit_on_opengl_error?
end
end
end
require_relative "opengl/shader"
require_relative "opengl/texture"
require_relative "opengl/light"
require_relative "opengl/perspective_camera"
require_relative "opengl/orthographic_camera"
require_relative "opengl/renderer/g_buffer"
require_relative "opengl/renderer/bounding_box_renderer"
require_relative "opengl/renderer/opengl_renderer"
require_relative "opengl/renderer/renderer"

View File

@@ -0,0 +1,50 @@
module CyberarmEngine
class Light
DIRECTIONAL = 0
POINT = 1
SPOT = 2
attr_reader :light_id
attr_accessor :type, :ambient, :diffuse, :specular, :position, :direction, :intensity
def initialize(
id:,
type: Light::POINT,
ambient: Vector.new(0.5, 0.5, 0.5),
diffuse: Vector.new(1, 1, 1),
specular: Vector.new(0.2, 0.2, 0.2),
position: Vector.new(0, 0, 0),
direction: Vector.new(0, 0, 0),
intensity: 1
)
@light_id = id
@type = type
@ambient = ambient
@diffuse = diffuse
@specular = specular
@position = position
@direction = direction
@intensity = intensity
end
def draw
glLightfv(@light_id, GL_AMBIENT, convert(@ambient).pack("f*"))
glLightfv(@light_id, GL_DIFFUSE, convert(@diffuse, true).pack("f*"))
glLightfv(@light_id, GL_SPECULAR, convert(@specular, true).pack("f*"))
glLightfv(@light_id, GL_POSITION, convert(@position).pack("f*"))
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1)
glEnable(GL_LIGHTING)
glEnable(@light_id)
end
def convert(struct, apply_intensity = false)
if apply_intensity
struct.to_a.compact.map { |i| i * @intensity }
else
struct.to_a.compact
end
end
end
end

View File

@@ -0,0 +1,46 @@
module CyberarmEngine
class OrthographicCamera
attr_accessor :position, :orientation, :zoom, :left, :right, :bottom, :top,
:min_view_distance, :max_view_distance
def initialize(
position:, right:, top:, orientation: Vector.new(0, 0, 0),
zoom: 1, left: 0, bottom: 0,
min_view_distance: 0.1, max_view_distance: 200.0
)
@position = position
@orientation = orientation
@zoom = zoom
@left = left
@right = right
@bottom = bottom
@top = top
@min_view_distance = min_view_distance
@max_view_distance = max_view_distance
end
# Immediate mode renderering fallback
def draw
glMatrixMode(GL_PROJECTION)
glLoadIdentity
glOrtho(@left, @right, @bottom, @top, @min_view_distance, @max_view_distance)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
glRotatef(@orientation.x, 1, 0, 0)
glRotatef(@orientation.y, 0, 1, 0)
glTranslatef(-@position.x, -@position.y, -@position.z)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity
end
def projection_matrix
Transform.orthographic(@left, @right, @bottom, @top, @min_view_distance, @max_view_distance)
end
def view_matrix
Transform.translate_3d(@position * -1) * Transform.rotate_3d(@orientation)
end
end
end

View File

@@ -0,0 +1,38 @@
module CyberarmEngine
class PerspectiveCamera
attr_accessor :position, :orientation, :aspect_ratio, :field_of_view,
:min_view_distance, :max_view_distance
def initialize(position:, aspect_ratio:, orientation: Vector.new(0, 0,
0), field_of_view: 70.0, min_view_distance: 0.1, max_view_distance: 155.0)
@position = position
@orientation = orientation
@aspect_ratio = aspect_ratio
@field_of_view = field_of_view
@min_view_distance = min_view_distance
@max_view_distance = max_view_distance
end
def draw
glMatrixMode(GL_PROJECTION)
glLoadIdentity
gluPerspective(@field_of_view, @aspect_ratio, @min_view_distance, @max_view_distance)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
glRotatef(@orientation.x, 1, 0, 0)
glRotatef(@orientation.y, 0, 1, 0)
glTranslatef(-@position.x, -@position.y, -@position.z)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity
end
def projection_matrix
Transform.perspective(@field_of_view, @aspect_ratio, @min_view_distance, @max_view_distance)
end
def view_matrix
Transform.translate_3d(@position * -1) * Transform.rotate_3d(@orientation)
end
end
end

View File

@@ -0,0 +1,249 @@
module CyberarmEngine
class BoundingBoxRenderer
attr_reader :bounding_boxes, :vertex_count
def initialize
@bounding_boxes = {}
@vertex_count = 0
end
def render(entities)
entities.each do |entity|
create_bounding_box(entity, color = nil)
draw_bounding_boxes
end
(@bounding_boxes.keys - entities.map { |e| e.object_id }).each do |key|
@bounding_boxes.delete(key)
end
end
def create_bounding_box(entity, color = nil)
color ||= entity.debug_color
entity_id = entity.object_id
if @bounding_boxes[entity_id]
if @bounding_boxes[entity_id][:color] != color
@bounding_boxes[entity_id][:colors] = mesh_colors(color).pack("f*")
@bounding_boxes[entity_id][:color] = color
return
else
return
end
end
@bounding_boxes[entity_id] = {
entity: entity,
color: color,
objects: []
}
box = entity.normalize_bounding_box
normals = mesh_normals
colors = mesh_colors(color)
vertices = mesh_vertices(box)
@vertex_count += vertices.size
@bounding_boxes[entity_id][:vertices_size] = vertices.size
@bounding_boxes[entity_id][:vertices] = vertices.pack("f*")
@bounding_boxes[entity_id][:normals] = normals.pack("f*")
@bounding_boxes[entity_id][:colors] = colors.pack("f*")
entity.model.objects.each do |mesh|
data = {}
box = mesh.bounding_box.normalize(entity)
normals = mesh_normals
colors = mesh_colors(mesh.debug_color)
vertices = mesh_vertices(box)
@vertex_count += vertices.size
data[:vertices_size] = vertices.size
data[:vertices] = vertices.pack("f*")
data[:normals] = normals.pack("f*")
data[:colors] = colors.pack("f*")
@bounding_boxes[entity_id][:objects] << data
end
end
def mesh_normals
[
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0
]
end
def mesh_colors(color)
[
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue
]
end
def mesh_vertices(box)
[
box.min.x, box.max.y, box.max.z,
box.min.x, box.max.y, box.min.z,
box.max.x, box.max.y, box.min.z,
box.min.x, box.max.y, box.max.z,
box.max.x, box.max.y, box.max.z,
box.max.x, box.max.y, box.min.z,
box.max.x, box.min.y, box.min.z,
box.max.x, box.min.y, box.max.z,
box.min.x, box.min.y, box.max.z,
box.max.x, box.min.y, box.min.z,
box.min.x, box.min.y, box.min.z,
box.min.x, box.min.y, box.max.z,
box.min.x, box.max.y, box.max.z,
box.min.x, box.max.y, box.min.z,
box.min.x, box.min.y, box.min.z,
box.min.x, box.min.y, box.max.z,
box.min.x, box.min.y, box.min.z,
box.min.x, box.max.y, box.max.z,
box.max.x, box.max.y, box.max.z,
box.max.x, box.max.y, box.min.z,
box.max.x, box.min.y, box.min.z,
box.max.x, box.min.y, box.max.z,
box.max.x, box.min.y, box.min.z,
box.max.x, box.max.y, box.max.z,
box.min.x, box.max.y, box.max.z,
box.max.x, box.max.y, box.max.z,
box.max.x, box.min.y, box.max.z,
box.min.x, box.max.y, box.max.z,
box.max.x, box.min.y, box.max.z,
box.min.x, box.min.y, box.max.z,
box.max.x, box.min.y, box.min.z,
box.min.x, box.min.y, box.min.z,
box.min.x, box.max.y, box.min.z,
box.max.x, box.min.y, box.min.z,
box.min.x, box.max.y, box.min.z,
box.max.x, box.max.y, box.min.z
]
end
def draw_bounding_boxes
@bounding_boxes.each do |key, bounding_box|
glPushMatrix
glTranslatef(
bounding_box[:entity].position.x,
bounding_box[:entity].position.y,
bounding_box[:entity].position.z
)
draw_bounding_box(bounding_box)
@bounding_boxes[key][:objects].each { |o| draw_bounding_box(o) }
glPopMatrix
end
end
def draw_bounding_box(bounding_box)
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY)
glEnableClientState(GL_NORMAL_ARRAY)
glVertexPointer(3, GL_FLOAT, 0, bounding_box[:vertices])
glColorPointer(3, GL_FLOAT, 0, bounding_box[:colors])
glNormalPointer(GL_FLOAT, 0, bounding_box[:normals])
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
glDisable(GL_LIGHTING)
glDrawArrays(GL_TRIANGLES, 0, bounding_box[:vertices_size] / 3)
glEnable(GL_LIGHTING)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_COLOR_ARRAY)
glDisableClientState(GL_NORMAL_ARRAY)
end
end
end

View File

@@ -0,0 +1,164 @@
module CyberarmEngine
class GBuffer
attr_reader :screen_vbo, :vertices, :uvs
def initialize(width:, height:)
@width = width
@height = height
@framebuffer = nil
@buffers = %i[position diffuse normal texcoord]
@textures = {}
@screen_vbo = nil
@ready = false
@vertices = [
-1.0, -1.0, 0,
1.0, -1.0, 0,
-1.0, 1.0, 0,
-1.0, 1.0, 0,
1.0, -1.0, 0,
1.0, 1.0, 0
].freeze
@uvs = [
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1
].freeze
create_framebuffer
create_screen_vbo
end
def create_framebuffer
buffer = " " * 4
glGenFramebuffers(1, buffer)
@framebuffer = buffer.unpack1("L2")
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, @framebuffer)
create_textures
status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
if status != GL_FRAMEBUFFER_COMPLETE
message = ""
message = case status
when GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
"GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"
when GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
"GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"
when GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER
"GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"
when GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER
"GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"
when GL_FRAMEBUFFER_UNSUPPORTED
"GL_FRAMEBUFFER_UNSUPPORTED"
else
"Unknown error!"
end
puts "Incomplete framebuffer: #{status}\nError: #{message}"
else
@ready = true
end
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)
end
def create_textures
@buffers.size.times do |i|
buffer = " " * 4
glGenTextures(1, buffer)
texture_id = buffer.unpack1("L2")
@textures[@buffers[i]] = texture_id
glBindTexture(GL_TEXTURE_2D, texture_id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, @width, @height, 0, GL_RGBA, GL_FLOAT, nil)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, texture_id, 0)
end
buffer = " " * 4
glGenTextures(1, buffer)
texture_id = buffer.unpack1("L2")
@textures[:depth] = texture_id
glBindTexture(GL_TEXTURE_2D, texture_id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, @width, @height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nil)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture_id, 0)
draw_buffers = [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3]
glDrawBuffers(draw_buffers.size, draw_buffers.pack("I*"))
end
def create_screen_vbo
buffer = " " * 4
glGenVertexArrays(1, buffer)
@screen_vbo = buffer.unpack1("L2")
buffer = " " * 4
glGenBuffers(1, buffer)
@positions_buffer_id = buffer.unpack1("L2")
buffer = " " * 4
glGenBuffers(1, buffer)
@uvs_buffer_id = buffer.unpack1("L2")
glBindVertexArray(@screen_vbo)
glBindBuffer(GL_ARRAY_BUFFER, @positions_buffer_id)
glBufferData(GL_ARRAY_BUFFER, @vertices.size * Fiddle::SIZEOF_FLOAT, @vertices.pack("f*"), GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nil)
glBindBuffer(GL_ARRAY_BUFFER, @uvs_buffer_id)
glBufferData(GL_ARRAY_BUFFER, @uvs.size * Fiddle::SIZEOF_FLOAT, @uvs.pack("f*"), GL_STATIC_DRAW)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, nil)
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
glBindVertexArray(0)
end
def bind_for_writing
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, @framebuffer)
end
def bind_for_reading
glBindFramebuffer(GL_READ_FRAMEBUFFER, @framebuffer)
end
def set_read_buffer(buffer)
glReadBuffer(GL_COLOR_ATTACHMENT0 + @textures.keys.index(buffer))
end
def set_read_buffer_depth
glReadBuffer(GL_DEPTH_ATTACHMENT)
end
def unbind_framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0)
end
def texture(type)
@textures[type]
end
def clean_up
glDeleteFramebuffers(1, [@framebuffer].pack("L"))
glDeleteTextures(@textures.values.size, @textures.values.pack("L*"))
glDeleteBuffers(2, [@positions_buffer_id, @uvs_buffer_id].pack("L*"))
glDeleteVertexArrays(1, [@screen_vbo].pack("L"))
gl_error?
end
end
end

View File

@@ -0,0 +1,289 @@
module CyberarmEngine
class OpenGLRenderer
@@immediate_mode_warning = false
attr_accessor :show_wireframe
def initialize(width:, height:, show_wireframe: false)
@width = width
@height = height
@show_wireframe = show_wireframe
@g_buffer = GBuffer.new(width: @width, height: @height)
end
def canvas_size_changed
@g_buffer.unbind_framebuffer
@g_buffer.clean_up
@g_buffer = GBuffer.new(width: @width, height: @height)
end
def render(camera, lights, entities)
glViewport(0, 0, @width, @height)
glEnable(GL_DEPTH_TEST)
if Shader.available?("g_buffer") && Shader.available?("lighting")
@g_buffer.bind_for_writing
gl_error?
glClearColor(0.0, 0.0, 0.0, 0.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
Shader.use("g_buffer") do |shader|
gl_error?
entities.each do |entity|
next unless entity.visible && entity.renderable
shader.uniform_transform("projection", camera.projection_matrix)
shader.uniform_transform("view", camera.view_matrix)
shader.uniform_transform("model", entity.model_matrix)
shader.uniform_vec3("cameraPosition", camera.position)
gl_error?
draw_model(entity.model, shader)
entity.draw
end
end
@g_buffer.unbind_framebuffer
gl_error?
@g_buffer.bind_for_reading
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)
lighting(lights)
gl_error?
post_processing
gl_error?
# render_framebuffer
gl_error?
@g_buffer.unbind_framebuffer
gl_error?
else
unless @@immediate_mode_warning
puts "Shaders are disabled or failed to compile, using immediate mode for rendering..."
end
@@immediate_mode_warning = true
gl_error?
lights.each(&:draw)
camera.draw
glEnable(GL_NORMALIZE)
entities.each do |entity|
next unless entity.visible && entity.renderable
glPushMatrix
glTranslatef(entity.position.x, entity.position.y, entity.position.z)
glScalef(entity.scale.x, entity.scale.y, entity.scale.z)
glRotatef(entity.orientation.x, 1.0, 0, 0)
glRotatef(entity.orientation.y, 0, 1.0, 0)
glRotatef(entity.orientation.z, 0, 0, 1.0)
gl_error?
draw_mesh(entity.model)
entity.draw
glPopMatrix
end
end
gl_error?
end
def copy_g_buffer_to_screen
@g_buffer.set_read_buffer(:position)
glBlitFramebuffer(0, 0, @g_buffer.width, @g_buffer.height,
0, 0, @g_buffer.width / 2, @g_buffer.height / 2,
GL_COLOR_BUFFER_BIT, GL_LINEAR)
@g_buffer.set_read_buffer(:diffuse)
glBlitFramebuffer(0, 0, @g_buffer.width, @g_buffer.height,
0, @g_buffer.height / 2, @g_buffer.width / 2, @g_buffer.height,
GL_COLOR_BUFFER_BIT, GL_LINEAR)
@g_buffer.set_read_buffer(:normal)
glBlitFramebuffer(0, 0, @g_buffer.width, @g_buffer.height,
@g_buffer.width / 2, @g_buffer.height / 2, @g_buffer.width, @g_buffer.height,
GL_COLOR_BUFFER_BIT, GL_LINEAR)
@g_buffer.set_read_buffer(:texcoord)
glBlitFramebuffer(0, 0, @g_buffer.width, @g_buffer.height,
@g_buffer.width / 2, 0, @g_buffer.width, @g_buffer.height / 2,
GL_COLOR_BUFFER_BIT, GL_LINEAR)
end
def lighting(lights)
Shader.use("lighting") do |shader|
glBindVertexArray(@g_buffer.screen_vbo)
glDisable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:diffuse))
shader.uniform_integer("diffuse", 0)
glActiveTexture(GL_TEXTURE1)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:position))
shader.uniform_integer("position", 1)
glActiveTexture(GL_TEXTURE2)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:texcoord))
shader.uniform_integer("texcoord", 2)
glActiveTexture(GL_TEXTURE3)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:normal))
shader.uniform_integer("normal", 3)
glActiveTexture(GL_TEXTURE4)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:depth))
shader.uniform_integer("depth", 4)
lights.each_with_index do |light, _i|
shader.uniform_integer("light[0].type", light.type)
shader.uniform_vec3("light[0].direction", light.direction)
shader.uniform_vec3("light[0].position", light.position)
shader.uniform_vec3("light[0].diffuse", light.diffuse)
shader.uniform_vec3("light[0].ambient", light.ambient)
shader.uniform_vec3("light[0].specular", light.specular)
glDrawArrays(GL_TRIANGLES, 0, @g_buffer.vertices.size)
end
glBindVertexArray(0)
end
end
def post_processing
end
def render_framebuffer
if Shader.available?("lighting")
Shader.use("lighting") do |shader|
glBindVertexArray(@g_buffer.screen_vbo)
glDisable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:diffuse))
shader.uniform_integer("diffuse_texture", 0)
glDrawArrays(GL_TRIANGLES, 0, @g_buffer.vertices.size)
glBindVertexArray(0)
end
end
end
def draw_model(model, shader)
glBindVertexArray(model.vertex_array_id)
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
glEnableVertexAttribArray(2)
if model.has_texture?
glEnableVertexAttribArray(3)
glEnableVertexAttribArray(4)
end
if @show_wireframe
glLineWidth(2)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
Shader.active_shader.uniform_boolean("disableLighting", true)
glDrawArrays(GL_TRIANGLES, 0, model.faces.count * 3)
Shader.active_shader.uniform_boolean("disableLighting", false)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
glLineWidth(1)
end
offset = 0
model.objects.each do |object|
shader.uniform_boolean("hasTexture", object.has_texture?)
if object.has_texture?
glBindTexture(GL_TEXTURE_2D, object.materials.find { |mat| mat.texture_id }.texture_id)
else
glBindTexture(GL_TEXTURE_2D, 0)
end
glDrawArrays(GL_TRIANGLES, offset, object.faces.count * 3)
offset += object.faces.count * 3
end
if model.has_texture?
glDisableVertexAttribArray(4)
glDisableVertexAttribArray(3)
glBindTexture(GL_TEXTURE_2D, 0)
end
glDisableVertexAttribArray(2)
glDisableVertexAttribArray(1)
glDisableVertexAttribArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
end
def draw_mesh(model)
model.objects.each_with_index do |o, _i|
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
glShadeModel(GL_FLAT) unless o.faces.first[4]
glShadeModel(GL_SMOOTH) if o.faces.first[4]
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY)
glEnableClientState(GL_NORMAL_ARRAY)
if o.has_texture?
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, o.materials.find { |mat| mat.texture_id }.texture_id)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glTexCoordPointer(3, GL_FLOAT, 0, o.flattened_uvs)
end
glVertexPointer(4, GL_FLOAT, 0, o.flattened_vertices)
glColorPointer(3, GL_FLOAT, 0, o.flattened_materials)
glNormalPointer(GL_FLOAT, 0, o.flattened_normals)
if @show_wireframe # This is kinda expensive
glDisable(GL_LIGHTING)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
glPolygonOffset(2, 0.5)
glLineWidth(3)
glDrawArrays(GL_TRIANGLES, 0, o.flattened_vertices_size / 4)
glLineWidth(1)
glPolygonOffset(0, 0)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
glEnable(GL_LIGHTING)
glDrawArrays(GL_TRIANGLES, 0, o.flattened_vertices_size / 4)
else
glDrawArrays(GL_TRIANGLES, 0, o.flattened_vertices_size / 4)
end
# glBindBuffer(GL_ARRAY_BUFFER, 0)
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_COLOR_ARRAY)
glDisableClientState(GL_NORMAL_ARRAY)
if o.has_texture?
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
glDisable(GL_TEXTURE_2D)
end
glDisable(GL_COLOR_MATERIAL)
end
end
end
end

View File

@@ -0,0 +1,22 @@
module CyberarmEngine
class Renderer
attr_reader :opengl_renderer, :bounding_box_renderer
def initialize
@bounding_box_renderer = BoundingBoxRenderer.new
@opengl_renderer = OpenGLRenderer.new(width: $window.width, height: $window.height)
end
def draw(camera, lights, entities)
@opengl_renderer.render(camera, lights, entities)
@bounding_box_renderer.render(entities) if @show_bounding_boxes
end
def canvas_size_changed
@opengl_renderer.canvas_size_changed
end
def finalize # cleanup
end
end
end

View File

@@ -22,9 +22,7 @@ module CyberarmEngine
if shader if shader
@@shaders.delete(name) @@shaders.delete(name)
if shader.compiled? glDeleteProgram(shader.program) if shader.compiled?
glDeleteProgram(shader.program)
end
end end
end end
@@ -68,15 +66,15 @@ module CyberarmEngine
# returns currently active {Shader}, if one is active # returns currently active {Shader}, if one is active
# #
# @return [Shader?] # @return [Shader?]
def self.active_shader class << self
@active_shader attr_reader :active_shader
end end
# sets currently active {Shader} # sets currently active {Shader}
# #
# @param instance [Shader] instance of {Shader} to set as active # @param instance [Shader] instance of {Shader} to set as active
def self.active_shader=(instance) class << self
@active_shader = instance attr_writer :active_shader
end end
# stops using currently active {Shader} # stops using currently active {Shader}
@@ -94,7 +92,8 @@ module CyberarmEngine
# #
# @param variable [String] # @param variable [String]
def self.attribute_location(variable) def self.attribute_location(variable)
raise RuntimeError, "No active shader!" unless Shader.active_shader raise "No active shader!" unless Shader.active_shader
Shader.active_shader.attribute_location(variable) Shader.active_shader.attribute_location(variable)
end end
@@ -103,12 +102,14 @@ module CyberarmEngine
# @param variable [String] # @param variable [String]
# @param value # @param value
def self.set_uniform(variable, value) def self.set_uniform(variable, value)
raise RuntimeError, "No active shader!" unless Shader.active_shader raise "No active shader!" unless Shader.active_shader
Shader.active_shader.set_uniform(variable, value) Shader.active_shader.set_uniform(variable, value)
end end
attr_reader :name, :program attr_reader :name, :program
def initialize(name:, includes_dir: nil, vertex: "shaders/default.vert", fragment:)
def initialize(name:, fragment:, includes_dir: nil, vertex: "shaders/default.vert")
raise "Shader name can not be blank" if name.length == 0 raise "Shader name can not be blank" if name.length == 0
@name = name @name = name
@@ -120,7 +121,7 @@ module CyberarmEngine
@error_buffer_size = 1024 * 8 @error_buffer_size = 1024 * 8
@variable_missing = {} @variable_missing = {}
@data = {shaders: {}} @data = { shaders: {} }
unless shader_files_exist?(vertex: vertex, fragment: fragment) unless shader_files_exist?(vertex: vertex, fragment: fragment)
raise ArgumentError, "Shader files not found: #{vertex} or #{fragment}" raise ArgumentError, "Shader files not found: #{vertex} or #{fragment}"
@@ -133,7 +134,7 @@ module CyberarmEngine
compile_shader(type: :fragment) compile_shader(type: :fragment)
link_shaders link_shaders
@data[:shaders].each { |key, id| glDeleteShader(id) } @data[:shaders].each { |_key, id| glDeleteShader(id) }
# Only add shader if it successfully compiles # Only add shader if it successfully compiles
if @compiled if @compiled
@@ -175,7 +176,7 @@ module CyberarmEngine
_size = [processed_source.length].pack("I") _size = [processed_source.length].pack("I")
glShaderSource(_shader, 1, _source, _size) glShaderSource(_shader, 1, _source, _size)
@data[:shaders][type] =_shader @data[:shaders][type] = _shader
end end
# evaluates shader preprocessors # evaluates shader preprocessors
@@ -199,13 +200,17 @@ module CyberarmEngine
lines = source.lines lines = source.lines
lines.each_with_index do |line, i| lines.each_with_index do |line, i|
if line.start_with?(PREPROCESSOR_CHARACTER) next unless line.start_with?(PREPROCESSOR_CHARACTER)
preprocessor = line.strip.split(" ") preprocessor = line.strip.split(" ")
lines.delete(line) lines.delete(line)
case preprocessor.first case preprocessor.first
when "@include" when "@include"
raise ArgumentError, "Shader preprocessor include directory was not given for shader #{@name}" unless @includes_dir unless @includes_dir
raise ArgumentError,
"Shader preprocessor include directory was not given for shader #{@name}"
end
preprocessor[1..preprocessor.length - 1].join.scan(/"([^"]*)"/).flatten.each do |file| preprocessor[1..preprocessor.length - 1].join.scan(/"([^"]*)"/).flatten.each do |file|
source = File.read("#{@includes_dir}/#{file}.glsl") source = File.read("#{@includes_dir}/#{file}.glsl")
@@ -216,7 +221,6 @@ module CyberarmEngine
warn "Unsupported preprocessor #{preprocessor.first} for #{@name}" warn "Unsupported preprocessor #{preprocessor.first} for #{@name}"
end end
end end
end
lines.join lines.join
end end
@@ -230,12 +234,12 @@ module CyberarmEngine
raise ArgumentError, "No shader for #{type.inspect}" unless _shader raise ArgumentError, "No shader for #{type.inspect}" unless _shader
glCompileShader(_shader) glCompileShader(_shader)
buffer = ' ' buffer = " "
glGetShaderiv(_shader, GL_COMPILE_STATUS, buffer) glGetShaderiv(_shader, GL_COMPILE_STATUS, buffer)
compiled = buffer.unpack('L')[0] compiled = buffer.unpack1("L")
if compiled == 0 if compiled == 0
log = ' ' * @error_buffer_size log = " " * @error_buffer_size
glGetShaderInfoLog(_shader, @error_buffer_size, nil, log) glGetShaderInfoLog(_shader, @error_buffer_size, nil, log)
puts "Shader Error: Program \"#{@name}\"" puts "Shader Error: Program \"#{@name}\""
puts " #{type.to_s.capitalize} Shader InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n" puts " #{type.to_s.capitalize} Shader InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n"
@@ -246,7 +250,7 @@ module CyberarmEngine
_compiled = true _compiled = true
end end
return _compiled _compiled
end end
# link compiled OpenGL Shaders in to a OpenGL Program # link compiled OpenGL Shaders in to a OpenGL Program
@@ -261,18 +265,18 @@ module CyberarmEngine
end end
glLinkProgram(@program) glLinkProgram(@program)
buffer = ' ' buffer = " "
glGetProgramiv(@program, GL_LINK_STATUS, buffer) glGetProgramiv(@program, GL_LINK_STATUS, buffer)
linked = buffer.unpack('L')[0] linked = buffer.unpack1("L")
if linked == 0 if linked == 0
log = ' ' * @error_buffer_size log = " " * @error_buffer_size
glGetProgramInfoLog(@program, @error_buffer_size, nil, log) glGetProgramInfoLog(@program, @error_buffer_size, nil, log)
puts "Shader Error: Program \"#{@name}\"" puts "Shader Error: Program \"#{@name}\""
puts " Program InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n" puts " Program InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n"
end end
@compiled = linked == 0 ? false : true @compiled = !(linked == 0)
end end
# Returns the location of a uniform _variable_ # Returns the location of a uniform _variable_
@@ -281,18 +285,22 @@ module CyberarmEngine
# @return [Integer] location of uniform # @return [Integer] location of uniform
def variable(variable) def variable(variable)
loc = glGetUniformLocation(@program, variable) loc = glGetUniformLocation(@program, variable)
if (loc == -1) if loc == -1
puts "Shader Error: Program \"#{@name}\" has no such uniform named \"#{variable}\"", " Is it used in the shader? GLSL may have optimized it out.", " Is it miss spelled?" unless @variable_missing[variable] unless @variable_missing[variable]
puts "Shader Error: Program \"#{@name}\" has no such uniform named \"#{variable}\"",
" Is it used in the shader? GLSL may have optimized it out.", " Is it miss spelled?"
end
@variable_missing[variable] = true @variable_missing[variable] = true
end end
return loc loc
end end
# @see Shader.use Shader.use # @see Shader.use Shader.use
def use(&block) def use(&block)
return unless compiled? return unless compiled?
raise "Another shader is already in use! #{Shader.active_shader.name.inspect}" if Shader.active_shader raise "Another shader is already in use! #{Shader.active_shader.name.inspect}" if Shader.active_shader
Shader.active_shader=self
Shader.active_shader = self
glUseProgram(@program) glUseProgram(@program)
@@ -331,7 +339,7 @@ module CyberarmEngine
# @param location [Integer] # @param location [Integer]
# @return [void] # @return [void]
def uniform_transform(variable, value, location = nil) def uniform_transform(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable) attr_loc = location || attribute_location(variable)
glUniformMatrix4fv(attr_loc, 1, GL_FALSE, value.to_gl.pack("F16")) glUniformMatrix4fv(attr_loc, 1, GL_FALSE, value.to_gl.pack("F16"))
end end
@@ -343,7 +351,7 @@ module CyberarmEngine
# @param location [Integer] # @param location [Integer]
# @return [void] # @return [void]
def uniform_boolean(variable, value, location = nil) def uniform_boolean(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable) attr_loc = location || attribute_location(variable)
glUniform1i(attr_loc, value ? 1 : 0) glUniform1i(attr_loc, value ? 1 : 0)
end end
@@ -354,7 +362,7 @@ module CyberarmEngine
# @param location [Integer] # @param location [Integer]
# @return [void] # @return [void]
def uniform_integer(variable, value, location = nil) def uniform_integer(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable) attr_loc = location || attribute_location(variable)
glUniform1i(attr_loc, value) glUniform1i(attr_loc, value)
end end
@@ -366,7 +374,7 @@ module CyberarmEngine
# @param location [Integer] # @param location [Integer]
# @return [void] # @return [void]
def uniform_float(variable, value, location = nil) def uniform_float(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable) attr_loc = location || attribute_location(variable)
glUniform1f(attr_loc, value) glUniform1f(attr_loc, value)
end end
@@ -378,7 +386,7 @@ module CyberarmEngine
# @param location [Integer] # @param location [Integer]
# @return [void] # @return [void]
def uniform_vec3(variable, value, location = nil) def uniform_vec3(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable) attr_loc = location || attribute_location(variable)
glUniform3f(attr_loc, *value.to_a[0..2]) glUniform3f(attr_loc, *value.to_a[0..2])
end end
@@ -390,7 +398,7 @@ module CyberarmEngine
# @param location [Integer] # @param location [Integer]
# @return [void] # @return [void]
def uniform_vec4(variable, value, location = nil) def uniform_vec4(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable) attr_loc = location || attribute_location(variable)
glUniform4f(attr_loc, *value.to_a) glUniform4f(attr_loc, *value.to_a)
end end

View File

@@ -0,0 +1,69 @@
module CyberarmEngine
class Texture
DEFAULT_TEXTURE = "#{CYBERARM_ENGINE_ROOT_PATH}/assets/textures/default.png".freeze
CACHE = {}
def self.release_textures
CACHE.values.each do |id|
glDeleteTextures(id)
end
end
def self.from_cache(path, retro)
CACHE.dig("#{path}?retro=#{retro}")
end
attr_reader :id
def initialize(path: nil, image: nil, retro: false)
raise "keyword :path or :image must be provided!" if path.nil? && image.nil?
@retro = retro
@path = path
if @path
unless File.exist?(@path)
warn "Missing texture at: #{@path}"
@retro = true # override retro setting
@path = DEFAULT_TEXTURE
end
if texture = Texture.from_cache(@path, @retro)
@id = texture.id
return
end
image = load_image(@path)
@id = create_from_image(image)
else
@id = create_from_image(image)
end
end
def load_image(path)
CACHE["#{path}?retro=#{@retro}"] = self
Gosu::Image.new(path, retro: @retro)
end
def create_from_image(image)
array_of_pixels = image.to_blob
tex_names_buf = " " * 4
glGenTextures(1, tex_names_buf)
texture_id = tex_names_buf.unpack1("L2")
glBindTexture(GL_TEXTURE_2D, texture_id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, array_of_pixels)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) if @retro
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) unless @retro
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glGenerateMipmap(GL_TEXTURE_2D)
gl_error?
texture_id
end
end
end

View File

@@ -42,15 +42,15 @@ module CyberarmEngine
tmin = max(tmin, min(tz1, tz2)) tmin = max(tmin, min(tz1, tz2))
tmax = min(tmax, max(tz1, tz2)) tmax = min(tmax, max(tz1, tz2))
return tmax >= max(tmin, 0.0); tmax >= max(tmin, 0.0)
end end
def min(x, y) def min(x, y)
((x) < (y) ? (x) : (y)) ((x) < (y) ? x : y)
end end
def max(x, y) def max(x, y)
((x) > (y) ? (x) : (y)) ((x) > (y) ? x : y)
end end
end end
end end

View File

@@ -0,0 +1,21 @@
module CyberarmEngine
class Stats
@@hash = {
gui_recalculations_last_frame: 0
}
def self.get(key)
@@hash.dig(key)
end
def self.increment(key, n)
@@hash[key] += n
end
def self.clear
@@hash.each do |key, _value|
@@hash[key] = 0
end
end
end
end

View File

@@ -5,39 +5,40 @@ module CyberarmEngine
attr_accessor :x, :y, :z, :size, :options attr_accessor :x, :y, :z, :size, :options
attr_reader :text, :textobject, :factor_x, :factor_y, :color, :shadow, :shadow_size, :shadow_alpha, :shadow_color attr_reader :text, :textobject, :factor_x, :factor_y, :color, :shadow, :shadow_size, :shadow_alpha, :shadow_color
def initialize(text, options={}) def initialize(text, options = {})
@text = text.to_s || "" @text = text.to_s || ""
@options = options @options = options
@size = options[:size] || 18 @size = options[:size] || 18
@font = options[:font] || "sans-serif"#Gosu.default_font_name @font = options[:font] || Gosu.default_font_name
@x = options[:x] || 0 @x = options[:x] || 0
@y = options[:y] || 0 @y = options[:y] || 0
@z = options[:z] || 1025 @z = options[:z] || 1025
@factor_x = options[:factor_x] || 1 @factor_x = options[:factor_x] || 1
@factor_y = options[:factor_y] || 1 @factor_y = options[:factor_y] || 1
@color = options[:color] || Gosu::Color::WHITE @color = options[:color] || Gosu::Color::WHITE
@alignment= options[:alignment] || nil @mode = options[:mode] || :default
@alignment = options[:alignment] || nil
@shadow = true if options[:shadow] == true @shadow = true if options[:shadow] == true
@shadow = false if options[:shadow] == false @shadow = false if options[:shadow] == false
@shadow = true if options[:shadow] == nil @shadow = true if options[:shadow].nil?
@shadow_size = options[:shadow_size] ? options[:shadow_size] : 1 @shadow_size = options[:shadow_size] || 1
@shadow_alpha= options[:shadow_alpha] ? options[:shadow_alpha] : 30 @shadow_alpha = options[:shadow_alpha] || 30
@shadow_alpha= options[:shadow_alpha] ? options[:shadow_alpha] : 30 @shadow_alpha = options[:shadow_alpha] || 30
@shadow_color= options[:shadow_color] @shadow_color = options[:shadow_color]
@textobject = check_cache(@size, @font) @textobject = check_cache(@size, @font)
if @alignment if @alignment
case @alignment case @alignment
when :left when :left
@x = 0+BUTTON_PADDING @x = 0 + BUTTON_PADDING
when :center when :center
@x = ($window.width/2)-(@textobject.text_width(@text)/2) @x = ($window.width / 2) - (@textobject.text_width(@text) / 2)
when :right when :right
@x = $window.width-BUTTON_PADDING-@textobject.text_width(@text) @x = $window.width - BUTTON_PADDING - @textobject.text_width(@text)
end end
end end
return self self
end end
def check_cache(size, font_name) def check_cache(size, font_name)
@@ -61,7 +62,7 @@ module CyberarmEngine
CACHE[@size][@font] = font CACHE[@size][@font] = font
end end
return font font
end end
def text=(string) def text=(string)
@@ -73,64 +74,76 @@ module CyberarmEngine
@rendered_shadow = nil @rendered_shadow = nil
@factor_x = n @factor_x = n
end end
def factor_y=(n) def factor_y=(n)
@rendered_shadow = nil @rendered_shadow = nil
@factor_y = n @factor_y = n
end end
def color=(color) def color=(color)
@rendered_shadow = nil @rendered_shadow = nil
@color = color @color = color
end end
def shadow=(boolean) def shadow=(boolean)
@rendered_shadow = nil @rendered_shadow = nil
@shadow = boolean @shadow = boolean
end end
def shadow_size=(n) def shadow_size=(n)
@rendered_shadow = nil @rendered_shadow = nil
@shadow_size = n @shadow_size = n
end end
def shadow_alpha=(n) def shadow_alpha=(n)
@rendered_shadow = nil @rendered_shadow = nil
@shadow_alpha = n @shadow_alpha = n
end end
def shadow_color=(n) def shadow_color=(n)
@rendered_shadow = nil @rendered_shadow = nil
@shadow_color = n @shadow_color = n
end end
def width def width(text = @text)
textobject.text_width(@text) textobject.text_width(text)
end end
def height def markup_width(text = @text)
@text.lines.count > 0 ? (@text.lines.count) * textobject.height : @textobject.height textobject.markup_width(text)
end end
def draw def height(text = @text)
text.lines.count > 0 ? text.lines.count * textobject.height : @textobject.height
end
def draw(method = :draw_markup)
if @shadow && !ARGV.join.include?("--no-shadow") if @shadow && !ARGV.join.include?("--no-shadow")
shadow_alpha = @color.alpha <= 30 ? @color.alpha : @shadow_alpha shadow_alpha = @color.alpha <= 30 ? @color.alpha : @shadow_alpha
shadow_color = @shadow_color ? @shadow_color : Gosu::Color.rgba(@color.red, @color.green, @color.blue, shadow_alpha) shadow_color = @shadow_color || Gosu::Color.rgba(@color.red, @color.green, @color.blue,
shadow_alpha)
white = Gosu::Color::WHITE
_x = @shadow_size _x = @shadow_size
_y = @shadow_size _y = @shadow_size
@rendered_shadow ||= Gosu.render((self.width+(shadow_size*2)).ceil, (self.height+(@shadow_size*2)).ceil) do @rendered_shadow ||= Gosu.render((width + (shadow_size * 2)).ceil, (height + (@shadow_size * 2)).ceil) do
@textobject.draw_markup(@text, _x-@shadow_size, _y, @z) @textobject.send(method, @text, _x - @shadow_size, _y, @z, @factor_x, @factor_y, white, :add)
@textobject.draw_markup(@text, _x-@shadow_size, _y-@shadow_size, @z) @textobject.send(method, @text, _x - @shadow_size, _y - @shadow_size, @z, @factor_x, @factor_y, white, :add)
@textobject.draw_markup(@text, _x, _y-@shadow_size, @z, @factor_x) @textobject.send(method, @text, _x, _y - @shadow_size, @z, @factor_x, @factor_y, white, :add)
@textobject.draw_markup(@text, _x+@shadow_size, _y-@shadow_size, @z) @textobject.send(method, @text, _x + @shadow_size, _y - @shadow_size, @z, @factor_x, @factor_y, white, :add)
@textobject.draw_markup(@text, _x, _y+@shadow_size, @z) @textobject.send(method, @text, _x, _y + @shadow_size, @z, @factor_x, @factor_y, white, :add)
@textobject.draw_markup(@text, _x-@shadow_size, _y+@shadow_size, @z) @textobject.send(method, @text, _x - @shadow_size, _y + @shadow_size, @z, @factor_x, @factor_y, white, :add)
@textobject.draw_markup(@text, _x+@shadow_size, _y, @z) @textobject.send(method, @text, _x + @shadow_size, _y, @z, @factor_x, @factor_y, white, :add)
@textobject.draw_markup(@text, _x+@shadow_size, _y+@shadow_size, @z) @textobject.send(method, @text, _x + @shadow_size, _y + @shadow_size, @z, @factor_x, @factor_y, white, :add)
end end
@rendered_shadow.draw(@x-@shadow_size, @y-@shadow_size, @z, @factor_x, @factor_y, shadow_color) @rendered_shadow.draw(@x - @shadow_size, @y - @shadow_size, @z, @factor_x, @factor_y, shadow_color)
end end
@textobject.draw_markup(@text, @x, @y, @z, @factor_x, @factor_y, @color) @textobject.send(method, @text, @x, @y, @z, @factor_x, @factor_y, @color, @mode)
end end
def alpha=(n) def alpha=(n)
@@ -141,6 +154,7 @@ module CyberarmEngine
@color.alpha @color.alpha
end end
def update; end def update
end
end end
end end

View File

@@ -2,11 +2,12 @@ module CyberarmEngine
# Basic 4x4 matrix operations # Basic 4x4 matrix operations
class Transform class Transform
attr_reader :elements attr_reader :elements
def initialize(matrix) def initialize(matrix)
@elements = matrix @elements = matrix
raise "Transform is wrong size! Got #{@elements.size}, expected 16" if 16 != @elements.size raise "Transform is wrong size! Got #{@elements.size}, expected 16" if 16 != @elements.size
raise "Invalid value for matrix, must all be numeric!" if @elements.any? { |e| e.nil? || !e.is_a?(Numeric)} raise "Invalid value for matrix, must all be numeric!" if @elements.any? { |e| e.nil? || !e.is_a?(Numeric) }
end end
def self.identity def self.identity
@@ -30,7 +31,7 @@ module CyberarmEngine
+c, +s, 0, 0, +c, +s, 0, 0,
-s, +c, 0, 0, -s, +c, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 1, 0, 0, 0, 1
] ]
rotate_matrix = Transform.new(matrix, rows: 4, columns: 4) rotate_matrix = Transform.new(matrix, rows: 4, columns: 4)
@@ -44,7 +45,7 @@ module CyberarmEngine
) )
end end
return rotate_matrix rotate_matrix
end end
# 2d translate operation, replicates Gosu's Gosu.translate function # 2d translate operation, replicates Gosu's Gosu.translate function
@@ -54,7 +55,7 @@ module CyberarmEngine
1, 0, 0, 0, 1, 0, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0,
x, y, z, 1, x, y, z, 1
] ]
Transform.new(matrix) Transform.new(matrix)
@@ -67,7 +68,7 @@ module CyberarmEngine
scale_x, 0, 0, 0, scale_x, 0, 0, 0,
0, scale_y, 0, 0, 0, scale_y, 0, 0,
0, 0, scale_z, 0, 0, 0, scale_z, 0,
0, 0, 0, 1, 0, 0, 0, 1
] ]
scale_matrix = Transform.new(matrix) scale_matrix = Transform.new(matrix)
@@ -81,7 +82,7 @@ module CyberarmEngine
) )
end end
return scale_matrix scale_matrix
end end
def self.concat(left, right) def self.concat(left, right)
@@ -107,13 +108,13 @@ module CyberarmEngine
1, 0, 0, x, 1, 0, 0, x,
0, 1, 0, y, 0, 1, 0, y,
0, 0, 1, z, 0, 0, 1, z,
0, 0, 0, 1, 0, 0, 0, 1
] ]
Transform.new(matrix) Transform.new(matrix)
end end
def self.rotate_3d(vector, order = "zyx") def self.rotate_3d(vector, _order = "zyx")
x, y, z = vector.to_a[0..2].map { |axis| axis * Math::PI / 180.0 } x, y, z = vector.to_a[0..2].map { |axis| axis * Math::PI / 180.0 }
rotation_x = Transform.new( rotation_x = Transform.new(
@@ -196,6 +197,29 @@ module CyberarmEngine
) )
end end
def self.orthographic(left, right, bottom, top, near, far)
s = Vector.new(
2 / (right - left.to_f),
2 / (top - bottom.to_f),
-2 / (far - near.to_f)
)
t = Vector.new(
(right + left.to_f) / (right - left.to_f),
(top + bottom.to_f) / (top - bottom.to_f),
(far + near.to_f) / (far - near.to_f)
)
Transform.new(
[
s.x, 0.0, 0.0, t.x,
0.0, s.y, 0.0, t.y,
0.0, 0.0, s.z, t.z,
0.0, 0.0, 0.0, 1.0
]
)
end
def self.view(eye, orientation) def self.view(eye, orientation)
# https://www.3dgep.com/understanding-the-view-matrix/#The_View_Matrix # https://www.3dgep.com/understanding-the-view-matrix/#The_View_Matrix
cosPitch = Math.cos(orientation.z * Math::PI / 180.0) cosPitch = Math.cos(orientation.z * Math::PI / 180.0)
@@ -218,7 +242,6 @@ module CyberarmEngine
end end
def *(other) def *(other)
case other case other
when CyberarmEngine::Vector when CyberarmEngine::Vector
matrix = @elements.clone matrix = @elements.clone
@@ -231,7 +254,7 @@ module CyberarmEngine
Transform.new(matrix) Transform.new(matrix)
when CyberarmEngine::Transform when CyberarmEngine::Transform
return multiply_matrices(other) multiply_matrices(other)
else else
p other.class p other.class
raise TypeError, "Expected CyberarmEngine::Vector or CyberarmEngine::Transform got #{other.class}" raise TypeError, "Expected CyberarmEngine::Vector or CyberarmEngine::Transform got #{other.class}"
@@ -256,7 +279,7 @@ module CyberarmEngine
end end
end end
return Transform.new(matrix) Transform.new(matrix)
end end
# arranges Matrix in column major form # arranges Matrix in column major form

View File

@@ -1,6 +1,7 @@
module CyberarmEngine module CyberarmEngine
class BorderCanvas class BorderCanvas
attr_reader :element, :top, :right, :bottom, :left attr_reader :element, :top, :right, :bottom, :left
def initialize(element:) def initialize(element:)
@element = element @element = element
@@ -25,7 +26,7 @@ module CyberarmEngine
elsif color.is_a?(Array) elsif color.is_a?(Array)
if color.size == 1 if color.size == 1
color=color.first color = color.first
elsif color.size == 2 elsif color.size == 2
@top.background = color.first @top.background = color.first
@@ -61,7 +62,7 @@ module CyberarmEngine
def update def update
# TOP # TOP
@top.x = @element.x# + @element.border_thickness_left @top.x = @element.x # + @element.border_thickness_left
@top.y = @element.y @top.y = @element.y
@top.z = @element.z @top.z = @element.z

View File

@@ -12,49 +12,70 @@ module CyberarmEngine
options[:parent] = element_parent options[:parent] = element_parent
options[:theme] = current_theme options[:theme] = current_theme
add_element( Element::Label.new(text, options, block) ) add_element(Element::Label.new(text, options, block))
end end
def button(text, options = {}, &block) def button(text, options = {}, &block)
options[:parent] = element_parent options[:parent] = element_parent
options[:theme] = current_theme options[:theme] = current_theme
add_element( Element::Button.new(text, options, block) { if block.is_a?(Proc); block.call; end } ) add_element(Element::Button.new(text, options, block) { block.call if block.is_a?(Proc) })
end
def list_box(options = {}, &block)
options[:parent] = element_parent
options[:theme] = current_theme
add_element(Element::ListBox.new(options, block) { block.call if block.is_a?(Proc) })
end end
def edit_line(text, options = {}, &block) def edit_line(text, options = {}, &block)
options[:parent] = element_parent options[:parent] = element_parent
options[:theme] = current_theme options[:theme] = current_theme
add_element( Element::EditLine.new(text, options, block) ) add_element(Element::EditLine.new(text, options, block))
end
def edit_box(text, options = {}, &block)
options[:parent] = element_parent
options[:theme] = current_theme
add_element(Element::EditBox.new(text, options, block))
end end
def toggle_button(options = {}, &block) def toggle_button(options = {}, &block)
options[:parent] = element_parent options[:parent] = element_parent
options[:theme] = current_theme options[:theme] = current_theme
add_element( Element::ToggleButton.new(options, block) ) add_element(Element::ToggleButton.new(options, block))
end end
def check_box(text, options = {}, &block) def check_box(text, options = {}, &block)
options[:parent] = element_parent options[:parent] = element_parent
options[:theme] = current_theme options[:theme] = current_theme
add_element( Element::CheckBox.new(text, options, block) ) add_element(Element::CheckBox.new(text, options, block))
end end
def image(path, options = {}, &block) def image(path, options = {}, &block)
options[:parent] = element_parent options[:parent] = element_parent
options[:theme] = current_theme options[:theme] = current_theme
add_element( Element::Image.new(path, options, block) ) add_element(Element::Image.new(path, options, block))
end end
def progress(options = {}, &block) def progress(options = {}, &block)
options[:parent] = element_parent options[:parent] = element_parent
options[:theme] = current_theme options[:theme] = current_theme
add_element( Element::Progress.new(options, block) ) add_element(Element::Progress.new(options, block))
end
def slider(options = {}, &block)
options[:parent] = element_parent
options[:theme] = current_theme
add_element(Element::Slider.new(options, block))
end end
def background(color = Gosu::Color::NONE) def background(color = Gosu::Color::NONE)
@@ -72,7 +93,7 @@ module CyberarmEngine
private def add_element(element) private def add_element(element)
element_parent.add(element) element_parent.add(element)
return element element
end end
private def element_parent private def element_parent
@@ -93,7 +114,7 @@ module CyberarmEngine
$__current_container__ = old_parent $__current_container__ = old_parent
return _container _container
end end
end end
end end

View File

@@ -4,7 +4,7 @@ module CyberarmEngine
include Event include Event
include Common include Common
attr_accessor :x, :y, :z, :enabled attr_accessor :x, :y, :z, :enabled, :tip
attr_reader :parent, :options, :style, :event_handler, :background_canvas, :border_canvas attr_reader :parent, :options, :style, :event_handler, :background_canvas, :border_canvas
def initialize(options = {}, block = nil) def initialize(options = {}, block = nil)
@@ -16,9 +16,13 @@ module CyberarmEngine
@focus = false @focus = false
@enabled = true @enabled = true
@visible = true @visible = true
@tip = @options[:tip] || ""
@style = Style.new(options) @style = Style.new(options)
@root ||= nil
@gui_state ||= nil
@x = @style.x @x = @style.x
@y = @style.y @y = @style.y
@z = @style.z @z = @style.z
@@ -26,9 +30,6 @@ module CyberarmEngine
@width = 0 @width = 0
@height = 0 @height = 0
@fixed_x = @x if @x != 0
@fixed_y = @y if @y != 0
@style.width = default(:width) || nil @style.width = default(:width) || nil
@style.height = default(:height) || nil @style.height = default(:height) || nil
@@ -41,6 +42,7 @@ module CyberarmEngine
end end
def stylize def stylize
set_static_position
set_border_thickness(@style.border_thickness) set_border_thickness(@style.border_thickness)
set_padding(@style.padding) set_padding(@style.padding)
@@ -51,6 +53,11 @@ module CyberarmEngine
set_border_color(@style.border_color) set_border_color(@style.border_color)
end end
def set_static_position
@x = @style.x if @style.x != 0
@y = @style.y if @style.y != 0
end
def set_background(background) def set_background(background)
@style.background = background @style.background = background
@style.background_canvas.background = background @style.background_canvas.background = background
@@ -95,7 +102,7 @@ module CyberarmEngine
end end
def default_events def default_events
[:left, :middle, :right].each do |button| %i[left middle right].each do |button|
event(:"#{button}_mouse_button") event(:"#{button}_mouse_button")
event(:"released_#{button}_mouse_button") event(:"released_#{button}_mouse_button")
event(:"clicked_#{button}_mouse_button") event(:"clicked_#{button}_mouse_button")
@@ -110,6 +117,8 @@ module CyberarmEngine
event(:leave) event(:leave)
event(:blur) event(:blur)
event(:changed)
end end
def enabled? def enabled?
@@ -126,22 +135,27 @@ module CyberarmEngine
end end
def show def show
bool = visible?
@visible = true @visible = true
root.gui_state.request_recalculate root.gui_state.request_recalculate unless bool
end end
def hide def hide
bool = visible?
@visible = false @visible = false
root.gui_state.request_recalculate root.gui_state.request_recalculate if bool
end end
def draw def draw
return unless @visible return unless visible?
@style.background_canvas.draw @style.background_canvas.draw
@style.border_canvas.draw @style.border_canvas.draw
Gosu.clip_to(@x, @y, width, height) do
render render
end end
end
def update def update
end end
@@ -152,6 +166,10 @@ module CyberarmEngine
def button_up(id) def button_up(id)
end end
def draggable?(_button)
false
end
def render def render
end end
@@ -208,21 +226,20 @@ module CyberarmEngine
(@style.border_thickness_top + @style.padding_top) + (@style.padding_bottom + @style.border_thickness_bottom) (@style.border_thickness_top + @style.padding_top) + (@style.padding_bottom + @style.border_thickness_bottom)
end end
private def dimensional_size(size, dimension) def dimensional_size(size, dimension)
raise "dimension must be either :width or :height" unless dimension == :width || dimension == :height raise "dimension must be either :width or :height" unless %i[width height].include?(dimension)
if size && size.is_a?(Numeric) if size && size.is_a?(Numeric)
if size.between?(0.0, 1.0) if size.between?(0.0, 1.0)
((@parent.send(:"content_#{dimension}") - self.send(:"noncontent_#{dimension}") - 1) * size).round ((@parent.send(:"content_#{dimension}") - send(:"noncontent_#{dimension}")) * size).round
else else
size size
end end
else
nil
end end
end end
def background=(_background) def background=(_background)
@style.background_canvas.background=(_background) @style.background_canvas.background = (_background)
update_background update_background
end end
@@ -239,6 +256,8 @@ module CyberarmEngine
end end
def root def root
return self if is_root?
unless @root && @root.parent.nil? unless @root && @root.parent.nil?
@root = parent @root = parent
@@ -269,8 +288,12 @@ module CyberarmEngine
raise "#{self.class}#value was not overridden!" raise "#{self.class}#value was not overridden!"
end end
def value=(value) def value=(_value)
raise "#{self.class}#value= was not overridden!" raise "#{self.class}#value= was not overridden!"
end end
def to_s
"#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} value=#{value.is_a?(String) ? "\"#{value}\"" : value}"
end
end end
end end

View File

@@ -1,21 +1,40 @@
module CyberarmEngine module CyberarmEngine
class Element class Element
class Button < Label class Button < Label
def initialize(text, options = {}, block = nil) def initialize(text_or_image, options = {}, block = nil)
super(text, options, block) @image = nil
@scale_x = 1
@scale_y = 1
@image = text_or_image if text_or_image.is_a?(Gosu::Image)
super(text_or_image, options, block)
@style.background_canvas.background = default(:background) @style.background_canvas.background = default(:background)
end end
def render def render
if @image
draw_image
else
draw_text draw_text
end end
end
def draw_image
@image.draw(
@style.border_thickness_left + @style.padding_left + @x,
@style.border_thickness_top + @style.padding_top + @y,
@z + 2,
@scale_x, @scale_y, @text.color
)
end
def draw_text def draw_text
@text.draw @text.draw
end end
def enter(sender) def enter(_sender)
@focus = false unless window.button_down?(Gosu::MsLeft) @focus = false unless window.button_down?(Gosu::MsLeft)
if @focus if @focus
@@ -26,41 +45,92 @@ module CyberarmEngine
@text.color = default(:hover, :color) @text.color = default(:hover, :color)
end end
return :handled :handled
end end
def left_mouse_button(sender, x, y) def left_mouse_button(_sender, _x, _y)
@focus = true @focus = true
@style.background_canvas.background = default(:active, :background) @style.background_canvas.background = default(:active, :background)
window.current_state.focus = self window.current_state.focus = self
@text.color = default(:active, :color) @text.color = default(:active, :color)
return :handled :handled
end end
def released_left_mouse_button(sender,x, y) def released_left_mouse_button(sender, _x, _y)
enter(sender) enter(sender)
return :handled :handled
end end
def clicked_left_mouse_button(sender, x, y) def clicked_left_mouse_button(_sender, _x, _y)
@block.call(self) if @block @block.call(self) if @block
return :handled :handled
end end
def leave(sender) def leave(_sender)
@style.background_canvas.background = default(:background) @style.background_canvas.background = default(:background)
@text.color = default(:color) @text.color = default(:color)
return :handled :handled
end end
def blur(sender) def blur(_sender)
@focus = false @focus = false
return :handled :handled
end
def recalculate
if @image
@width = 0
@height = 0
_width = dimensional_size(@style.image_width, :width)
_height = dimensional_size(@style.image_height, :height)
if _width && _height
@scale_x = _width.to_f / @image.width
@scale_y = _height.to_f / @image.height
elsif _width
@scale_x = _width.to_f / @image.width
@scale_y = @scale_x
elsif _height
@scale_y = _height.to_f / @image.height
@scale_x = @scale_y
else
@scale_x = 1
@scale_y = 1
end
@width = _width || @image.width.round * @scale_x
@height = _height || @image.height.round * @scale_y
update_background
else
super
end
end
def value
@image || super
end
def value=(value)
if value.is_a?(Gosu::Image)
@image = value
else
super
end
old_width = width
old_height = height
recalculate
root.gui_state.request_recalculate if old_width != width || old_height != height
publish(:changed, self.value)
end end
end end
end end

View File

@@ -2,13 +2,32 @@ module CyberarmEngine
class Element class Element
class CheckBox < Flow class CheckBox < Flow
def initialize(text, options, block = nil) def initialize(text, options, block = nil)
super({}, block = nil) super(options, block)
options[:toggled] = options[:checked] options[:toggled] = options[:checked]
@toggle_button = ToggleButton.new(options) @toggle_button = ToggleButton.new(options)
@label = Label.new(text, options) @label = Label.new(text, options)
define_label_singletons @label.subscribe(:holding_left_mouse_button) do |sender, x, y|
@toggle_button.left_mouse_button(sender, x, y)
end
@label.subscribe(:released_left_mouse_button) do |sender, x, y|
@toggle_button.released_left_mouse_button(sender, x, y)
end
@label.subscribe(:clicked_left_mouse_button) do |sender, x, y|
@toggle_button.clicked_left_mouse_button(sender, x, y)
publish(:changed, @toggle_button.value)
end
@label.subscribe(:enter) do |sender|
@toggle_button.enter(sender)
end
@label.subscribe(:leave) do |sender|
@toggle_button.leave(sender)
end
add(@toggle_button) add(@toggle_button)
add(@label) add(@label)
@@ -24,35 +43,8 @@ module CyberarmEngine
end end
def value=(bool) def value=(bool)
@toggle_button.vlaue = bool @toggle_button.value = bool
end publish(:changed, @toggle_button.value)
def define_label_singletons
@label.define_singleton_method(:_toggle_button) do |button|
@_toggle_button = button
end
@label._toggle_button(@toggle_button)
@label.define_singleton_method(:holding_left_mouse_button) do |sender, x, y|
@_toggle_button.left_mouse_button(sender, x, y)
end
@label.define_singleton_method(:released_left_mouse_button) do |sender, x, y|
@_toggle_button.released_left_mouse_button(sender, x, y)
end
@label.define_singleton_method(:clicked_left_mouse_button) do |sender, x, y|
@_toggle_button.clicked_left_mouse_button(sender, x, y)
end
@label.define_singleton_method(:enter) do |sender|
@_toggle_button.enter(sender)
end
@label.define_singleton_method(:leave) do |sender|
@_toggle_button.leave(sender)
end
end end
end end
end end

View File

@@ -4,14 +4,14 @@ module CyberarmEngine
include Common include Common
attr_accessor :stroke_color, :fill_color attr_accessor :stroke_color, :fill_color
attr_reader :children, :gui_state attr_reader :children, :gui_state, :scroll_x, :scroll_y
attr_reader :scroll_x, :scroll_y
def initialize(options = {}, block = nil) def initialize(options = {}, block = nil)
@gui_state = options.delete(:gui_state) @gui_state = options.delete(:gui_state)
super super
@scroll_x, @scroll_y = 0, 0 @scroll_x = 0
@scroll_y = 0
@scroll_speed = 10 @scroll_speed = 10
@text_color = options[:color] @text_color = options[:color]
@@ -22,13 +22,13 @@ module CyberarmEngine
def build def build
@block.call(self) if @block @block.call(self) if @block
recalculate root.gui_state.request_recalculate
end end
def add(element) def add(element)
@children << element @children << element
recalculate root.gui_state.request_recalculate
end end
def clear(&block) def clear(&block)
@@ -41,7 +41,6 @@ module CyberarmEngine
$__current_container__ = old_container $__current_container__ = old_container
recalculate
root.gui_state.request_recalculate root.gui_state.request_recalculate
end end
@@ -49,6 +48,31 @@ module CyberarmEngine
Gosu.clip_to(@x, @y, width, height) do Gosu.clip_to(@x, @y, width, height) do
@children.each(&:draw) @children.each(&:draw)
end end
if false # DEBUG
Gosu.flush
Gosu.draw_line(
x, y, Gosu::Color::RED,
x + outer_width, y, Gosu::Color::RED,
Float::INFINITY
)
Gosu.draw_line(
x + outer_width, y, Gosu::Color::RED,
x + outer_width, y + outer_height, Gosu::Color::RED,
Float::INFINITY
)
Gosu.draw_line(
x + outer_width, y + outer_height, Gosu::Color::RED,
x, y + outer_height, Gosu::Color::RED,
Float::INFINITY
)
Gosu.draw_line(
x, outer_height, Gosu::Color::RED,
x, y, Gosu::Color::RED,
Float::INFINITY
)
end
end end
def update def update
@@ -57,6 +81,8 @@ module CyberarmEngine
def hit_element?(x, y) def hit_element?(x, y)
@children.reverse_each do |child| @children.reverse_each do |child|
next unless child.visible?
case child case child
when Container when Container
if element = child.hit_element?(x, y) if element = child.hit_element?(x, y)
@@ -73,6 +99,9 @@ module CyberarmEngine
def recalculate def recalculate
@current_position = Vector.new(@style.margin_left + @style.padding_left, @style.margin_top + @style.padding_top) @current_position = Vector.new(@style.margin_left + @style.padding_left, @style.margin_top + @style.padding_top)
return unless visible? return unless visible?
Stats.increment(:gui_recalculations_last_frame, 1)
stylize stylize
layout layout
@@ -81,24 +110,26 @@ module CyberarmEngine
@width = @style.width = window.width @width = @style.width = window.width
@height = @style.height = window.height @height = @style.height = window.height
else else
@width, @height = 0, 0 @width = 0
@height = 0
_width = dimensional_size(@style.width, :width) _width = dimensional_size(@style.width, :width)
_height= dimensional_size(@style.height,:height) _height = dimensional_size(@style.height, :height)
@width = _width ? _width : (@children.map {|c| c.x + c.outer_width }.max || 0).round @width = _width || (@children.map { |c| c.x + c.outer_width }.max || 0).round
@height = _height ? _height : (@children.map {|c| c.y + c.outer_height}.max || 0).round @height = _height || (@children.map { |c| c.y + c.outer_height }.max || 0).round
end end
# Move child to parent after positioning # Move child to parent after positioning
@children.each do |child| @children.each do |child|
child.x += @x child.x += (@x + @style.border_thickness_left) - style.margin_left
child.y += @y child.y += (@y + @style.border_thickness_top) - style.margin_top
child.stylize child.stylize
child.recalculate child.recalculate
child.reposition # TODO: Implement top,bottom,left,center, and right positioning child.reposition # TODO: Implement top,bottom,left,center, and right positioning
Stats.increment(:gui_recalculations_last_frame, 1)
end end
update_background update_background
@@ -109,10 +140,16 @@ module CyberarmEngine
end end
def max_width def max_width
@max_width ? @max_width : window.width - (@parent ? @parent.style.margin_right + @style.margin_right : @style.margin_right) _width = dimensional_size(@style.width, :width)
if _width
outer_width
else
window.width - (@parent ? @parent.style.margin_right + @style.margin_right : @style.margin_right)
end
end end
def fits_on_line?(element) # Flow def fits_on_line?(element) # Flow
p [@options[:id], @width] if @options[:id]
@current_position.x + element.outer_width <= max_width && @current_position.x + element.outer_width <= max_width &&
@current_position.x + element.outer_width <= window.width @current_position.x + element.outer_width <= window.width
end end
@@ -121,20 +158,18 @@ module CyberarmEngine
element.x = element.style.margin_left + @current_position.x element.x = element.style.margin_left + @current_position.x
element.y = element.style.margin_top + @current_position.y element.y = element.style.margin_top + @current_position.y
element.recalculate
@current_position.x += element.outer_width @current_position.x += element.outer_width
@current_position.x = @style.margin_left if @current_position.x >= max_width @current_position.x = @style.margin_left if @current_position.x >= max_width
end end
def tallest_neighbor(querier, y_position) # Flow def tallest_neighbor(querier, _y_position) # Flow
response = querier response = querier
@children.each do |child| @children.each do |child|
response = child if child.outer_height > response.outer_height response = child if child.outer_height > response.outer_height
break if child == querier break if child == querier
end end
return response response
end end
def position_on_next_line(child) # Flow def position_on_next_line(child) # Flow
@@ -144,8 +179,6 @@ module CyberarmEngine
child.x = child.style.margin_left + @current_position.x child.x = child.style.margin_left + @current_position.x
child.y = child.style.margin_top + @current_position.y child.y = child.style.margin_top + @current_position.y
child.recalculate
@current_position.x += child.outer_width @current_position.x += child.outer_width
end end
@@ -153,8 +186,6 @@ module CyberarmEngine
element.x = element.style.margin_left + @current_position.x element.x = element.style.margin_left + @current_position.x
element.y = element.style.margin_top + @current_position.y element.y = element.style.margin_top + @current_position.y
element.recalculate
@current_position.y += element.outer_height @current_position.y += element.outer_height
end end
@@ -169,7 +200,26 @@ module CyberarmEngine
# end # end
def value def value
@children.map {|c| c.class}.join(", ") @children.map { |c| c.class }.join(", ")
end
def to_s
"#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} children=#{@children.size}"
end
def write_tree(indent = "", _index = 0)
puts self
indent += " "
@children.each_with_index do |child, i|
print "#{indent}#{i}: "
if child.is_a?(Container)
child.write_tree(indent)
else
puts child
end
end
end end
end end
end end

View File

@@ -0,0 +1,179 @@
module CyberarmEngine
class Element
class EditBox < EditLine
def initialize(*args)
super(*args)
@active_line = 0
@repeatable_keys = [
{
key: Gosu::KB_UP,
down: false,
repeat_delay: 50,
last_repeat: 0,
action: proc { move(:up) }
},
{
key: Gosu::KB_DOWN,
down: false,
repeat_delay: 50,
last_repeat: 0,
action: proc { move(:down) }
}
]
end
def update
super
caret_stay_left_of_last_newline
calculate_active_line
@repeatable_keys.each do |key|
if key[:down] && (Gosu.milliseconds > key[:last_repeat] + key[:repeat_delay])
key[:action].call
key[:last_repeat] = Gosu.milliseconds
end
end
end
def draw_caret
Gosu.draw_rect(caret_position, @text.y + @active_line * @text.textobject.height, @caret_width, @caret_height,
@caret_color, @z)
end
def draw_selection
selection_width = caret_position - selection_start_position
Gosu.draw_rect(selection_start_position, @text.y, selection_width, @text.textobject.height,
default(:selection_color), @z)
end
def text_input_position_for(_method)
line = @text_input.text[0...@text_input.caret_pos].lines.last
_x = @text.x + @offset_x
if @type == :password
_x + @text.width(default(:password_character) * line.length)
else
_x + @text.width(line)
end
end
def set_position(int)
int = 0 if int < 0
@text_input.selection_start = @text_input.caret_pos = int
end
def calculate_active_line
sub_text = @text_input.text[0...@text_input.caret_pos]
@active_line = sub_text.lines.size - 1
end
def caret_stay_left_of_last_newline
@text_input.text += "\n" unless @text_input.text.end_with?("\n")
eof = @text_input.text.chomp.length
set_position(eof) if @text_input.caret_pos > eof
end
def caret_position_under_mouse(mouse_x, mouse_y)
active_line = row_at(mouse_y)
right_offset = column_at(mouse_x, mouse_y)
buffer = @text_input.text.lines[0..active_line].join if active_line != 0
buffer = @text_input.text.lines.first if active_line == 0
line = buffer.lines.last
if buffer.chars.last == "\n"
(buffer.length - line.length) + right_offset - 1
else
(buffer.length - line.length) + right_offset
end
end
def move_caret_to_mouse(mouse_x, mouse_y)
set_position(caret_position_under_mouse(mouse_x, mouse_y))
end
def row_at(y)
((y - @text.y) / @text.textobject.height).round
end
def column_at(x, y)
row = row_at(y)
buffer = @text_input.text.lines[0..row].join if row != 0
buffer = @text_input.text.lines.first if row == 0
line = @text_input.text.lines[row]
line ||= ""
column = 0
line.length.times do |_i|
break if @text.textobject.text_width(line[0...column]) >= (x - @text.x).clamp(0.0, Float::INFINITY)
column += 1
end
column
end
def button_down(id)
super
@repeatable_keys.detect do |key|
next unless key[:key] == id
key[:down] = true
key[:last_repeat] = Gosu.milliseconds + key[:repeat_delay]
return true
end
case id
when Gosu::KB_ENTER, Gosu::KB_RETURN
caret_pos = @text_input.caret_pos
@text_input.text = @text_input.text.insert(@text_input.caret_pos, "\n")
@text_input.caret_pos = @text_input.selection_start = caret_pos + 1
end
end
def button_up(id)
super
@repeatable_keys.detect do |key|
if key[:key] == id
key[:down] = false
return true
end
end
end
def move(direction)
pos = @text_input.caret_pos
line = nil
case direction
when :up
return if @active_line == 0
when :down
return if @active_line == @text_input.text.chomp.lines
text = @text_input.text.chomp.lines[0..@active_line].join("\n")
pos = text.length
end
set_position(pos)
end
def drag_update(_sender, x, y, _button)
int = caret_position_under_mouse(x, y)
int = 0 if int < 0
@text_input.caret_pos = int
:handled
end
end
end
end

View File

@@ -2,12 +2,13 @@ module CyberarmEngine
class Element class Element
class EditLine < Button class EditLine < Button
def initialize(text, options = {}, block = nil) def initialize(text, options = {}, block = nil)
@filter = options.delete(:filter)
super(text, options, block) super(text, options, block)
@type = default(:type) @type = default(:type)
@caret_width = default(:caret_width) @caret_width = default(:caret_width)
@caret_height= @text.height @caret_height = @text.textobject.height
@caret_color = default(:caret_color) @caret_color = default(:caret_color)
@caret_interval = default(:caret_interval) @caret_interval = default(:caret_interval)
@caret_last_interval = Gosu.milliseconds @caret_last_interval = Gosu.milliseconds
@@ -15,15 +16,27 @@ module CyberarmEngine
@text_input = Gosu::TextInput.new @text_input = Gosu::TextInput.new
@text_input.text = text @text_input.text = text
@last_text_value = text
if @filter && @filter.respond_to?(:call)
@text_input.instance_variable_set(:@filter, @filter)
def @text_input.filter(text_in)
@filter.call(text_in)
end
end
@offset_x = 0 @offset_x = 0
@offset_y = 0
return self event(:begin_drag)
event(:drag_update)
event(:end_drag)
end end
def render def render
Gosu.clip_to(@text.x, @text.y, @style.width, @text.height) do Gosu.clip_to(@text.x, @text.y, @width, @height) do
Gosu.translate(-@offset_x, 0) do Gosu.translate(-@offset_x, -@offset_y) do
draw_selection draw_selection
draw_caret if @focus && @show_caret draw_caret if @focus && @show_caret
draw_text draw_text
@@ -31,6 +44,10 @@ module CyberarmEngine
end end
end end
def draw_text
@text.draw(:draw_text)
end
def draw_caret def draw_caret
Gosu.draw_rect(caret_position, @text.y, @caret_width, @caret_height, @caret_color, @z) Gosu.draw_rect(caret_position, @text.y, @caret_width, @caret_height, @caret_color, @z)
end end
@@ -42,10 +59,16 @@ module CyberarmEngine
end end
def update def update
if @type == :password @text.text = if @type == :password
@text.text = default(:password_character) * @text_input.text.length default(:password_character) * @text_input.text.length
else else
@text.text = @text_input.text @text_input.text
end
if @last_text_value != value
@last_text_value = value
publish(:changed, value)
end end
if Gosu.milliseconds >= @caret_last_interval + @caret_interval if Gosu.milliseconds >= @caret_last_interval + @caret_interval
@@ -57,15 +80,60 @@ module CyberarmEngine
keep_caret_visible keep_caret_visible
end end
def move_caret_to_mouse(mouse_x) def button_down(id)
1.upto(@text.text.length) do |i| handle_keyboard_shortcuts(id)
if mouse_x < @text.x + @text.textobject.text_width(@text.text[0...i]) end
@text_input.caret_pos = @text_input.selection_start = i - 1;
return def handle_keyboard_shortcuts(id)
return unless @focus && @enabled
if Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
case id
when Gosu::KB_A
@text_input.selection_start = 0
@text_input.caret_pos = @text_input.text.length
when Gosu::KB_C
if @text_input.selection_start < @text_input.caret_pos
Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos])
else
Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start])
end
when Gosu::KB_X
chars = @text_input.text.chars
if @text_input.selection_start < @text_input.caret_pos
Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos])
chars.slice!(@text_input.selection_start, @text_input.caret_pos)
else
Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start])
chars.slice!(@text_input.caret_pos, @text_input.selection_start)
end
@text_input.text = chars.join
when Gosu::KB_V
if instance_of?(EditLine) # EditLine assumes a single line of text
@text_input.text = @text_input.text.insert(@text_input.caret_pos,
Clipboard.paste.encode("UTF-8").gsub("\n", ""))
else
@text_input.text = @text_input.text.insert(@text_input.caret_pos, Clipboard.paste.encode("UTF-8"))
end
end
end end
end end
@text_input.caret_pos = @text_input.selection_start = @text_input.text.length def caret_position_under_mouse(mouse_x)
1.upto(@text.text.length) do |i|
return i - 1 if mouse_x < @text.x - @offset_x + @text.width(@text.text[0...i])
end
@text_input.text.length
end
def move_caret_to_mouse(mouse_x, _mouse_y)
@text_input.caret_pos = @text_input.selection_start = caret_position_under_mouse(mouse_x)
end end
def keep_caret_visible def keep_caret_visible
@@ -74,25 +142,21 @@ module CyberarmEngine
@last_text ||= "/\\" @last_text ||= "/\\"
@last_pos ||= -1 @last_pos ||= -1
puts "caret pos: #{caret_pos}, width: #{@width}, offset: #{@offset_x}" if (@last_text != @text.text) || (@last_pos != caret_pos)
@last_text = @text.text @last_text = @text.text
@last_pos = caret_pos @last_pos = caret_pos
if caret_pos.between?(@offset_x, @width + @offset_x) if caret_pos.between?(@offset_x, @width + @offset_x)
# Do nothing # Do nothing
elsif caret_pos < @offset_x elsif caret_pos < @offset_x
if caret_pos > @width @offset_x = if caret_pos > @width
@offset_x = caret_pos + @width caret_pos + @width
else else
@offset_x = 0 0
end end
elsif caret_pos > @width elsif caret_pos > @width
@offset_x = caret_pos - @width @offset_x = caret_pos - @width
puts "triggered"
else else
# Reset to Zero # Reset to Zero
@@ -100,47 +164,6 @@ module CyberarmEngine
end end
end end
def left_mouse_button(sender, x, y)
super
window.text_input = @text_input
@caret_last_interval = Gosu.milliseconds
@show_caret = true
move_caret_to_mouse(x)
return :handled
end
def enter(sender)
if @focus
@style.background_canvas.background = default(:active, :background)
@text.color = default(:active, :color)
else
@style.background_canvas.background = default(:hover, :background)
@text.color = default(:hover, :color)
end
return :handled
end
def leave(sender)
unless @focus
super
end
return :handled
end
def blur(sender)
@focus = false
@style.background_canvas.background = default(:background)
@text.color = default(:color)
window.text_input = nil
return :handled
end
def caret_position def caret_position
text_input_position_for(:caret_pos) text_input_position_for(:caret_pos)
end end
@@ -151,12 +174,73 @@ module CyberarmEngine
def text_input_position_for(method) def text_input_position_for(method)
if @type == :password if @type == :password
@text.x + @text.textobject.text_width(default(:password_character) * @text_input.text[0..@text_input.send(method)].length) @text.x + @text.width(default(:password_character) * @text_input.text[0...@text_input.send(method)].length)
else else
@text.x + @text.textobject.text_width(@text_input.text[0..@text_input.send(method)]) @text.x + @text.width(@text_input.text[0...@text_input.send(method)])
end end
end end
def left_mouse_button(sender, x, y)
super
window.text_input = @text_input
@caret_last_interval = Gosu.milliseconds
@show_caret = true
move_caret_to_mouse(x, y)
:handled
end
def enter(_sender)
if @focus
@style.background_canvas.background = default(:active, :background)
@text.color = default(:active, :color)
else
@style.background_canvas.background = default(:hover, :background)
@text.color = default(:hover, :color)
end
:handled
end
def leave(sender)
super unless @focus
:handled
end
def blur(_sender)
@focus = false
@style.background_canvas.background = default(:background)
@text.color = default(:color)
window.text_input = nil
:handled
end
def draggable?(button)
button == :left
end
def begin_drag(_sender, x, _y, _button)
@drag_start = x
@offset_drag_start = @offset_x
@drag_caret_position = @text_input.caret_pos
:handled
end
def drag_update(_sender, x, _y, _button)
@text_input.caret_pos = caret_position_under_mouse(x)
:handled
end
def end_drag(_sender, _x, _y, _button)
:handled
end
def recalculate def recalculate
super super

View File

@@ -1,8 +1,6 @@
module CyberarmEngine module CyberarmEngine
class Element class Element
class Flow < Container class Flow < Container
include Common
def layout def layout
@children.each do |child| @children.each do |child|
if fits_on_line?(child) if fits_on_line?(child)

View File

@@ -6,7 +6,8 @@ module CyberarmEngine
@path = path @path = path
@image = Gosu::Image.new(path, retro: @options[:image_retro]) @image = Gosu::Image.new(path, retro: @options[:image_retro])
@scale_x, @scale_y = 1, 1 @scale_x = 1
@scale_y = 1
end end
def render def render
@@ -14,18 +15,19 @@ module CyberarmEngine
@style.border_thickness_left + @style.padding_left + @x, @style.border_thickness_left + @style.padding_left + @x,
@style.border_thickness_top + @style.padding_top + @y, @style.border_thickness_top + @style.padding_top + @y,
@z + 2, @z + 2,
@scale_x, @scale_y) # TODO: Add color support? @scale_x, @scale_y, @style.color
)
end end
def clicked_left_mouse_button(sender, x, y) def clicked_left_mouse_button(_sender, _x, _y)
@block.call(self) if @block @block.call(self) if @block
return :handled :handled
end end
def recalculate def recalculate
_width = dimensional_size(@style.width, :width) _width = dimensional_size(@style.width, :width)
_height= dimensional_size(@style.height,:height) _height = dimensional_size(@style.height, :height)
if _width && _height if _width && _height
@scale_x = _width.to_f / @image.width @scale_x = _width.to_f / @image.width
@@ -37,11 +39,12 @@ module CyberarmEngine
@scale_y = _height.to_f / @image.height @scale_y = _height.to_f / @image.height
@scale_x = @scale_y @scale_x = @scale_y
else else
@scale_x, @scale_y = 1, 1 @scale_x = 1
@scale_y = 1
end end
@width = _width ? _width : @image.width.round * @scale_x @width = _width || @image.width.round * @scale_x
@height= _height ? _height : @image.height.round * @scale_y @height = _height || @image.height.round * @scale_y
end end
def value def value

View File

@@ -4,46 +4,127 @@ module CyberarmEngine
def initialize(text, options = {}, block = nil) def initialize(text, options = {}, block = nil)
super(options, block) super(options, block)
@text = Text.new(text, font: @options[:font], z: @z, color: @options[:color], size: @options[:text_size], shadow: @options[:text_shadow]) @text = Text.new(
text, font: @options[:font], z: @z, color: @options[:color],
size: @options[:text_size], shadow: @options[:text_shadow],
shadow_size: @options[:text_shadow_size],
shadow_color: @options[:text_shadow_color]
)
@raw_text = text
end end
def render def render
@text.draw @text.draw
end end
def clicked_left_mouse_button(sender, x, y) def clicked_left_mouse_button(_sender, _x, _y)
@block.call(self) if @block @block&.call(self)
return :handled # return :handled
end end
def recalculate def recalculate
@width, @height = 0, 0 @width = 0
@height = 0
_width = dimensional_size(@style.width, :width) _width = dimensional_size(@style.width, :width)
_height= dimensional_size(@style.height,:height) _height = dimensional_size(@style.height, :height)
@width = _width ? _width : @text.width.round handle_text_wrapping(_width)
@height= _height ? _height : @text.height.round
@width = _width || @text.width.round
@height = _height || @text.height.round
@text.x = @style.border_thickness_left + @style.padding_left + @x
@text.y = @style.border_thickness_top + @style.padding_top + @y @text.y = @style.border_thickness_top + @style.padding_top + @y
@text.z = @z + 3 @text.z = @z + 3
if (text_alignment = @options[:text_align])
case text_alignment
when :left
@text.x = @style.border_thickness_left + @style.padding_left + @x
when :center
@text.x = if @text.width <= outer_width
@x + outer_width / 2 - @text.width / 2
else # Act as left aligned
@style.border_thickness_left + @style.padding_left + @x
end
when :right
@text.x = @x + outer_width - (@text.width + @style.border_thickness_right + @style.padding_right)
end
end
update_background update_background
end end
def handle_text_wrapping(max_width)
max_width ||= @parent&.width
max_width ||= @x - (window.width + noncontent_width)
wrap_behavior = style.text_wrap
copy = @raw_text.to_s.dup
if max_width >= line_width(copy[0]) && line_width(copy) > max_width && wrap_behavior != :none
breaks = []
line_start = 0
line_end = copy.length
while line_start != copy.length
if line_width(copy[line_start...line_end]) > max_width
line_end = ((line_end - line_start) / 2.0)
elsif line_end < copy.length && line_width(copy[line_start...line_end + 1]) < max_width
# To small, grow!
# TODO: find a more efficient way
line_end += 1
else # FOUND IT!
entering_line_end = line_end.floor
max_reach = line_end.floor - line_start < 63 ? line_end.floor - line_start : 63
reach = 0
if wrap_behavior == :word_wrap
max_reach.times do |i|
reach = i
break if copy[line_end.floor - i].to_s.match(/[[:punct:]]| /)
end
puts "Max width: #{max_width}/#{line_width(@raw_text)} Reach: {#{reach}/#{max_reach}} Line Start: #{line_start}/#{line_end.floor} (#{copy.length}|#{@raw_text.length}) [#{entering_line_end}] '#{copy}' {#{copy[line_start...line_end]}}"
line_end = line_end.floor - reach + 1 if reach != max_reach # Add +1 to walk in front of punctuation
end
breaks << line_end.floor
line_start = line_end.floor
line_end = copy.length
break if entering_line_end == copy.length || reach == max_reach
end
end
breaks.each_with_index do |pos, index|
copy.insert(pos + index, "\n") if pos + index >= 0 && pos + index < copy.length
end
end
@text.text = copy
end
def line_width(text)
(@x + @text.textobject.markup_width(text) + noncontent_width)
end
def value def value
@text.text @raw_text
end end
def value=(value) def value=(value)
@text.text = value @raw_text = value.to_s.chomp
old_width, old_height = width, height old_width = width
old_height = height
recalculate recalculate
root.gui_state.request_recalculate if old_width != width || old_height != height root.gui_state.request_recalculate if old_width != width || old_height != height
publish(:changed, self.value)
end end
end end
end end

View File

@@ -0,0 +1,68 @@
module CyberarmEngine
class Element
class ListBox < Button
attr_accessor :items
attr_reader :choose
def initialize(options = {}, block = nil)
@items = options[:items] || []
@choose = options[:choose] || @items.first
super(@choose, options, block)
@style.background_canvas.background = default(:background)
# TODO: "Clean Up" into own class?
@menu = Stack.new(parent: parent, width: @options[:width], theme: @options[:theme])
@menu.define_singleton_method(:recalculate_menu) do
@x = @__list_box.x
@y = @__list_box.y + @__list_box.height
end
@menu.instance_variable_set(:"@__list_box", self)
def @menu.recalculate
super
recalculate_menu
end
end
def choose=(item)
valid = @items.detect { |i| i == item }
return unless valid # TODO: Throw an error?
@choose = item
self.value = item.to_s
recalculate
end
def released_left_mouse_button(_sender, _x, _y)
show_menu
:handled
end
def show_menu
@menu.clear
@items.each do |item|
[@block]
block = proc { self.choose = item; @block.call(item) if @block }
b = Button.new(item,
{ parent: @menu, width: 1.0, theme: @options[:theme], margin: 0, border_color: 0x00ffffff }, block)
@menu.add(b)
end
recalculate
root.gui_state.show_menu(@menu)
end
def recalculate
super
@menu.recalculate
end
end
end
end

View File

@@ -5,7 +5,7 @@ module CyberarmEngine
super(options, block) super(options, block)
@fraction_background = Background.new(background: @style.fraction_background) @fraction_background = Background.new(background: @style.fraction_background)
self.value = options[:fraction] ? options[:fraction] : 0.0 self.value = options[:fraction] || 0.0
end end
def render def render
@@ -14,9 +14,9 @@ module CyberarmEngine
def recalculate def recalculate
_width = dimensional_size(@style.width, :width) _width = dimensional_size(@style.width, :width)
_height= dimensional_size(@style.height,:height) _height = dimensional_size(@style.height, :height)
@width = _width @width = _width
@height= _height @height = _height
update_background update_background
end end
@@ -43,7 +43,8 @@ module CyberarmEngine
@fraction = decimal.clamp(0.0, 1.0) @fraction = decimal.clamp(0.0, 1.0)
update_background update_background
return @fraction publish(:changed, @fraction)
@fraction
end end
end end
end end

View File

@@ -0,0 +1,6 @@
module CyberarmEngine
class Element
class Radio < Element
end
end
end

View File

@@ -0,0 +1,104 @@
module CyberarmEngine
class Element
class Slider < Container
class Handle < Button
def initialize(*args)
super(*args)
event(:begin_drag)
event(:drag_update)
event(:end_drag)
subscribe :begin_drag do |_sender, x, y, _button|
@drag_start_pos = Vector.new(x, y)
:handled
end
subscribe :drag_update do |_sender, x, y, _button|
@parent.handle_dragged_to(x, y)
:handled
end
subscribe :end_drag do
@drag_start_pos = nil
:handled
end
end
def draggable?(button)
button == :left
end
end
attr_reader :range, :step_size, :value
def initialize(options = {}, block = nil)
super(options, block)
@range = @options[:range] || (0.0..1.0)
@step_size = @options[:step] || 0.1
@value = @options[:value] || (@range.first + @range.last) / 2
@handle = Handle.new("", parent: self, width: 8, height: 1.0) { close }
add(@handle)
end
def recalculate
_width = dimensional_size(@style.width, :width)
_height = dimensional_size(@style.height, :height)
@width = _width
@height = _height
position_handle
@handle.recalculate
@handle.update_background
update_background
end
def position_handle
@handle.x = @x + @style.padding_left + @style.border_thickness_left +
((content_width - @handle.outer_width) * (@value - @range.min) / (@range.max - @range.min).to_f)
@handle.y = @y + @style.border_thickness_top + @style.padding_top
end
def draw
super
@handle.draw
end
def update
super
@tip = value.to_s
@handle.tip = @tip
end
def holding_left_mouse_button(_sender, x, y)
handle_dragged_to(x, y)
:handled
end
def handle_dragged_to(x, _y)
@ratio = ((x - @handle.width / 2) - @x) / (content_width - @handle.outer_width)
self.value = @ratio.clamp(0.0, 1.0) * (@range.max - @range.min) + @range.min
end
def value=(n)
@value = n
position_handle
@handle.recalculate
publish(:changed, @value)
end
end
end
end

View File

@@ -1,8 +1,6 @@
module CyberarmEngine module CyberarmEngine
class Element class Element
class Stack < Container class Stack < Container
include Common
def layout def layout
@children.each do |child| @children.each do |child|
move_to_next_line(child) move_to_next_line(child)

View File

@@ -1,55 +1,64 @@
module CyberarmEngine module CyberarmEngine
class Element class Element
class ToggleButton < Button class ToggleButton < Button
attr_reader :toggled attr_reader :toggled, :value
def initialize(options, block = nil) def initialize(options, block = nil)
super(options[:checkmark], options, block) if options.dig(:theme, :ToggleButton, :checkmark_image)
@toggled = options[:toggled] || false options[:theme][:ToggleButton][:image_width] ||= options[:theme][:Label][:text_size]
if @toggled super(get_image(options.dig(:theme, :ToggleButton, :checkmark_image)), options, block)
@text.text = @options[:checkmark]
@_image = @image
else else
@text.text = "" super(options[:checkmark], options, block)
end end
return self @value = options[:checked] || false
if @value
@image = @_image if @_image
@raw_text = @options[:checkmark]
else
@image = nil
@raw_text = ""
end
end end
def toggled=(boolean) def clicked_left_mouse_button(_sender, _x, _y)
@toggled = !boolean self.value = !@value
toggle
end
def clicked_left_mouse_button(sender, x, y)
toggle
@block.call(self) if @block @block.call(self) if @block
return :handled :handled
end
def toggle
if @toggled
@toggled = false
@text.text = ""
else
@toggled = true
@text.text = @options[:checkmark]
end
end end
def recalculate def recalculate
super super
return if @image
_width = dimensional_size(@style.width, :width) _width = dimensional_size(@style.width, :width)
_height= dimensional_size(@style.height,:height) _height = dimensional_size(@style.height, :height)
@width = _width ? _width : @text.textobject.text_width(@options[:checkmark])
@height = _height ? _height : @text.height @width = _width || @text.textobject.text_width(@options[:checkmark])
@height = _height || @text.height
update_background update_background
end end
def value def value=(boolean)
@toggled @value = boolean
if boolean
@image = @_image if @_image
@raw_text = @options[:checkmark]
else
@image = nil
@raw_text = ""
end
recalculate
publish(:changed, @value)
end end
end end
end end

View File

@@ -15,20 +15,18 @@ module CyberarmEngine
return unless enabled? return unless enabled?
if respond_to?(event) return :handled if respond_to?(event) && (send(event, self, *args) == :handled)
return :handled if send(event, self, *args) == :handled
end
@event_handler[event].reverse_each do |handler| @event_handler[event].reverse_each do |handler|
return :handled if handler.call(self, *args) == :handled return :handled if handler.call(self, *args) == :handled
end end
parent.publish(event, *args) if parent parent.publish(event, *args) if parent
return nil nil
end end
def event(event) def event(event)
@event_handler ||= Hash.new @event_handler ||= {}
@event_handler[event] ||= [] @event_handler[event] ||= []
end end
end end
@@ -37,7 +35,9 @@ module CyberarmEngine
attr_reader :publisher, :event, :handler attr_reader :publisher, :event, :handler
def initialize(publisher, event, handler) def initialize(publisher, event, handler)
@publisher, @event, @handler = publisher, event, handler @publisher = publisher
@event = event
@handler = handler
end end
def unsubscribe def unsubscribe

View File

@@ -17,17 +17,25 @@ module CyberarmEngine
@active_width = window.width @active_width = window.width
@active_height = window.height @active_height = window.height
@menu = nil
@focus = nil @focus = nil
@mouse_over = nil @mouse_over = nil
@mouse_down_on = {} @mouse_down_on = {}
@mouse_down_position = {} @mouse_down_position = {}
@last_mouse_pos = nil
@dragging_element = nil
@pending_recalculate_request = false @pending_recalculate_request = false
@tip = CyberarmEngine::Text.new("", size: 22, z: Float::INFINITY)
@menu = nil
@min_drag_distance = 0
@mouse_pos = Vector.new
end end
# throws :blur event to focused element and sets GuiState focused element # throws :blur event to focused element and sets GuiState focused element
# Does NOT throw :focus event at element or set element as focused # Does NOT throw :focus event at element or set element as focused
def focus=(element) def focus=(element)
@focus.publish(:blur) if @focus and element && @focus != element @focus.publish(:blur) if @focus && element && @focus != element
@focus = element @focus = element
end end
@@ -35,15 +43,36 @@ module CyberarmEngine
@focus @focus
end end
def draw
super
if @menu
Gosu.flush
@menu.draw
end
if @tip.text.length.positive?
Gosu.flush
Gosu.draw_rect(@tip.x - 2, @tip.y - 2, @tip.width + 4, @tip.height + 4, 0xff020202, Float::INFINITY)
@tip.draw
end
end
def update def update
if @pending_recalculate_request if @pending_recalculate_request
@root_container.recalculate @root_container.recalculate
@root_container.recalculate
@root_container.recalculate
@pending_recalculate_request = false @pending_recalculate_request = false
end end
@menu&.update
super super
new_mouse_over = @root_container.hit_element?(window.mouse_x, window.mouse_y) new_mouse_over = @menu.hit_element?(window.mouse_x, window.mouse_y) if @menu
new_mouse_over ||= @root_container.hit_element?(window.mouse_x, window.mouse_y)
if new_mouse_over if new_mouse_over
new_mouse_over.publish(:enter) if new_mouse_over != @mouse_over new_mouse_over.publish(:enter) if new_mouse_over != @mouse_over
new_mouse_over.publish(:hover) new_mouse_over.publish(:hover)
@@ -56,12 +85,31 @@ module CyberarmEngine
redirect_holding_mouse_button(:middle) if @mouse_over && Gosu.button_down?(Gosu::MsMiddle) redirect_holding_mouse_button(:middle) if @mouse_over && Gosu.button_down?(Gosu::MsMiddle)
redirect_holding_mouse_button(:right) if @mouse_over && Gosu.button_down?(Gosu::MsRight) redirect_holding_mouse_button(:right) if @mouse_over && Gosu.button_down?(Gosu::MsRight)
if Vector.new(window.mouse_x, window.mouse_y) == @last_mouse_pos
if @mouse_over && (Gosu.milliseconds - @mouse_moved_at) > tool_tip_delay
@tip.text = @mouse_over.tip if @mouse_over
@tip.x = window.mouse_x - @tip.width / 2
@tip.y = window.mouse_y - @tip.height - 4
else
@tip.text = ""
end
else
@mouse_moved_at = Gosu.milliseconds
end
@last_mouse_pos = Vector.new(window.mouse_x, window.mouse_y)
@mouse_pos = @last_mouse_pos.clone
request_recalculate if @active_width != window.width || @active_height != window.height request_recalculate if @active_width != window.width || @active_height != window.height
@active_width = window.width @active_width = window.width
@active_height = window.height @active_height = window.height
end end
def tool_tip_delay
500 # ms
end
def button_down(id) def button_down(id)
super super
@@ -72,7 +120,11 @@ module CyberarmEngine
redirect_mouse_button(:middle) redirect_mouse_button(:middle)
when Gosu::MsRight when Gosu::MsRight
redirect_mouse_button(:right) redirect_mouse_button(:right)
when Gosu::KbF5
request_recalculate
end end
@focus.button_down(id) if @focus.respond_to?(:button_down)
end end
def button_up(id) def button_up(id)
@@ -90,9 +142,13 @@ module CyberarmEngine
when Gosu::MsWheelDown when Gosu::MsWheelDown
redirect_mouse_wheel(:down) redirect_mouse_wheel(:down)
end end
@focus.button_up(id) if @focus.respond_to?(:button_up)
end end
def redirect_mouse_button(button) def redirect_mouse_button(button)
hide_menu unless @menu && (@menu == @mouse_over) || (@mouse_over&.parent == @menu)
if @focus && @mouse_over != @focus if @focus && @mouse_over != @focus
@focus.publish(:blur) @focus.publish(:blur)
@focus = nil @focus = nil
@@ -110,9 +166,19 @@ module CyberarmEngine
end end
def redirect_released_mouse_button(button) def redirect_released_mouse_button(button)
hide_menu if @menu && (@menu == @mouse_over) || (@mouse_over&.parent == @menu)
if @mouse_over if @mouse_over
@mouse_over.publish(:"released_#{button}_mouse_button", window.mouse_x, window.mouse_y) @mouse_over.publish(:"released_#{button}_mouse_button", window.mouse_x, window.mouse_y)
@mouse_over.publish(:"clicked_#{button}_mouse_button", window.mouse_x, window.mouse_y) if @mouse_over == @mouse_down_on[button] if @mouse_over == @mouse_down_on[button]
@mouse_over.publish(:"clicked_#{button}_mouse_button", window.mouse_x,
window.mouse_y)
end
end
if @dragging_element
@dragging_element.publish(:end_drag, window.mouse_x, window.mouse_y, button)
@dragging_element = nil
end end
@mouse_down_position[button] = nil @mouse_down_position[button] = nil
@@ -120,7 +186,16 @@ module CyberarmEngine
end end
def redirect_holding_mouse_button(button) def redirect_holding_mouse_button(button)
@mouse_over.publish(:"holding_#{button}_mouse_button", window.mouse_x, window.mouse_y) if @mouse_over if !@dragging_element && @mouse_down_on[button] && @mouse_down_on[button].draggable?(button) && @mouse_pos.distance(@mouse_down_position[button]) > @min_drag_distance
@dragging_element = @mouse_down_on[button]
@dragging_element.publish(:begin_drag, window.mouse_x, window.mouse_y, button)
end
if @dragging_element
@dragging_element.publish(:drag_update, window.mouse_x, window.mouse_y, button) if @dragging_element
elsif @mouse_over
@mouse_over.publish(:"holding_#{button}_mouse_button", window.mouse_x, window.mouse_y)
end
end end
def redirect_mouse_wheel(button) def redirect_mouse_wheel(button)
@@ -131,5 +206,13 @@ module CyberarmEngine
def request_recalculate def request_recalculate
@pending_recalculate_request = true @pending_recalculate_request = true
end end
def show_menu(list_box)
@menu = list_box
end
def hide_menu
@menu = nil
end
end end
end end

View File

@@ -1,11 +1,11 @@
module Gosu module Gosu
class Color class Color
def _dump(level) def _dump(_level)
[ [
"%02X" % self.alpha, "%02X" % alpha,
"%02X" % self.red, "%02X" % red,
"%02X" % self.green, "%02X" % green,
"%02X" % self.blue "%02X" % blue
].join ].join
end end
@@ -21,13 +21,14 @@ module CyberarmEngine
@hash = Marshal.load(Marshal.dump(hash)) @hash = Marshal.load(Marshal.dump(hash))
end end
def method_missing(method, *args, &block) def method_missing(method, *args)
if method.to_s.end_with?("=") if method.to_s.end_with?("=")
raise "Did not expect more than 1 argument" if args.size > 1 raise "Did not expect more than 1 argument" if args.size > 1
return @hash[method.to_s.sub("=", "").to_sym] = args.first
@hash[method.to_s.sub("=", "").to_sym] = args.first
elsif args.size == 0 elsif args.size == 0
return @hash[method] @hash[method]
else else
raise ArgumentError, "Did not expect arguments" raise ArgumentError, "Did not expect arguments"

View File

@@ -11,17 +11,21 @@ module CyberarmEngine
def theme_defaults(options) def theme_defaults(options)
raise "Error" unless self.class.ancestors.include?(CyberarmEngine::Element) raise "Error" unless self.class.ancestors.include?(CyberarmEngine::Element)
_theme = THEME _theme = THEME
_theme = _theme.merge(options[:theme]) if options[:theme] _theme = deep_merge(_theme, options[:theme]) if options[:theme]
_theme.delete(:theme) if options[:theme] _theme.delete(:theme) if options[:theme]
hash = {} hash = {}
class_names = self.class.ancestors class_names = self.class.ancestors
class_names = class_names[0..class_names.index(CyberarmEngine::Element)].map! {|c| c.to_s.split("::").last.to_sym}.reverse! class_names = class_names[0..class_names.index(CyberarmEngine::Element)].map! do |c|
c.to_s.split("::").last.to_sym
end.reverse!
class_names.each do |klass| class_names.each do |klass|
next unless data = _theme.dig(klass) next unless data = _theme.dig(klass)
data.each do |key, value|
data.each do |_key, _value|
hash.merge!(data) hash.merge!(data)
end end
end end
@@ -32,7 +36,7 @@ module CyberarmEngine
# Derived from Rails Hash#deep_merge! # Derived from Rails Hash#deep_merge!
# Enables passing partial themes through Element options without issue # Enables passing partial themes through Element options without issue
def deep_merge(original, intergrate, &block) def deep_merge(original, intergrate, &block)
hash = original.merge(intergrate) do |key, this_val, other_val| original.merge(intergrate) do |key, this_val, other_val|
if this_val.is_a?(Hash) && other_val.is_a?(Hash) if this_val.is_a?(Hash) && other_val.is_a?(Hash)
deep_merge(this_val, other_val, &block) deep_merge(this_val, other_val, &block)
elsif block_given? elsif block_given?
@@ -41,8 +45,6 @@ module CyberarmEngine
other_val other_val
end end
end end
return hash
end end
THEME = { THEME = {
@@ -59,7 +61,7 @@ module CyberarmEngine
padding: 0, padding: 0,
border_thickness: 0, border_thickness: 0,
border_color: Gosu::Color::NONE, border_color: Gosu::Color::NONE,
border_radius: 0, border_radius: 0
}, },
Button: { # < Label Button: { # < Label
@@ -69,10 +71,12 @@ module CyberarmEngine
border_color: ["ffd59674".hex, "ffff8746".hex], border_color: ["ffd59674".hex, "ffff8746".hex],
border_radius: 0, border_radius: 0,
background: ["ffc75e61".to_i(16), "ffe26623".to_i(16)], background: ["ffc75e61".to_i(16), "ffe26623".to_i(16)],
text_align: :center,
text_wrap: :none,
hover: { hover: {
color: Gosu::Color.rgb(200,200,200), color: Gosu::Color.rgb(200, 200, 200),
background: ["ffB23E41".to_i(16), "ffFF7C00".to_i(16)], background: ["ffB23E41".to_i(16), "ffFF7C00".to_i(16)]
}, },
active: { active: {
@@ -88,16 +92,20 @@ module CyberarmEngine
caret_width: 2, caret_width: 2,
caret_color: Gosu::Color::WHITE, caret_color: Gosu::Color::WHITE,
caret_interval: 500, caret_interval: 500,
selection_color: Gosu::Color::GREEN, selection_color: Gosu::Color.rgba(255, 128, 50, 200),
text_align: :left
}, },
Image: { # < Element Image: { # < Element
color: Gosu::Color::WHITE,
retro: false retro: false
}, },
Label: { # < Element Label: { # < Element
text_size: 28, text_size: 28,
text_wrap: :none, # :word_wrap, :break_word, :none
text_shadow: false, text_shadow: false,
text_align: :left,
font: "Arial", font: "Arial",
margin: 0, margin: 0,
padding: 2 padding: 2
@@ -114,6 +122,15 @@ module CyberarmEngine
fraction_background: [0xffc75e61, 0xffe26623], fraction_background: [0xffc75e61, 0xffe26623],
border_thickness: 1, border_thickness: 1,
border_color: [0xffd59674, 0xffff8746] border_color: [0xffd59674, 0xffff8746]
},
Slider: { # < Element
width: 250,
height: 36,
background: 0xff111111,
fraction_background: [0xffc75e61, 0xffe26623],
border_thickness: 1,
border_color: [0xffd59674, 0xffff8746]
} }
}.freeze }.freeze
end end

View File

@@ -60,22 +60,15 @@ module CyberarmEngine
Vector.new(0, 0, -1) Vector.new(0, 0, -1)
end end
attr_accessor :x, :y, :z, :weight
def initialize(x = 0, y = 0, z = 0, weight = 0) def initialize(x = 0, y = 0, z = 0, weight = 0)
@x, @y, @z, @weight = x, y, z, weight @x = x
@y = y
@z = z
@weight = weight
end end
def x; @x; end
def x=(n); @x = n; end
def y; @y; end
def y=(n); @y = n; end
def z; @z; end
def z=(n); @z = n; end
def weight; @weight; end
def weight=(n); @weight = n; end
alias w weight alias w weight
alias w= weight= alias w= weight=
@@ -137,6 +130,17 @@ module CyberarmEngine
operator("*", other) operator("*", other)
end end
def multiply_transform(transform)
e = transform.elements
x = @x * e[0] + @y * e[1] + @z * e[2] + 1 * e[3]
y = @x * e[4] + @y * e[5] + @z * e[6] + 1 * e[7]
z = @x * e[8] + @y * e[9] + @z * e[10] + 1 * e[11]
w = @x * e[12] + @y * e[13] + @z * e[14] + 1 * e[15]
Vector.new(x / 1, y / 1, z / 1, w / 1)
end
# Divides Vector and Numeric or Vector and Vector, excluding {weight} # Divides Vector and Numeric or Vector and Vector, excluding {weight}
# @return [CyberarmEngine::Vector] # @return [CyberarmEngine::Vector]
def /(other) def /(other)
@@ -161,20 +165,20 @@ module CyberarmEngine
def dot(other) def dot(other)
product = 0 product = 0
a = self.to_a a = to_a
b = other.to_a b = other.to_a
3.times do |i| 3.times do |i|
product = product + (a[i] * b[i]) product += (a[i] * b[i])
end end
return product product
end end
# cross product of {Vector} # cross product of {Vector}
# @return [CyberarmEngine::Vector] # @return [CyberarmEngine::Vector]
def cross(other) def cross(other)
a = self.to_a a = to_a
b = other.to_a b = other.to_a
Vector.new( Vector.new(
@@ -187,7 +191,7 @@ module CyberarmEngine
# returns degrees # returns degrees
# @return [Float] # @return [Float]
def angle(other) def angle(other)
Math.acos( self.normalized.dot(other.normalized) ) * 180 / Math::PI Math.acos(normalized.dot(other.normalized)) * 180 / Math::PI
end end
# returns magnitude of Vector, ignoring #weight # returns magnitude of Vector, ignoring #weight
@@ -209,7 +213,6 @@ module CyberarmEngine
self / Vector.new(mag, mag, mag) self / Vector.new(mag, mag, mag)
end end
# returns a direction {Vector} # returns a direction {Vector}
# #
# z is pitch # z is pitch
@@ -254,19 +257,19 @@ module CyberarmEngine
# 2D distance using X and Y # 2D distance using X and Y
# @return [Float] # @return [Float]
def distance(other) def distance(other)
Math.sqrt((@x-other.x)**2 + (@y-other.y)**2) Math.sqrt((@x - other.x)**2 + (@y - other.y)**2)
end end
# 2D distance using X and Z # 2D distance using X and Z
# @return [Float] # @return [Float]
def gl_distance2d(other) def gl_distance2d(other)
Math.sqrt((@x-other.x)**2 + (@z-other.z)**2) Math.sqrt((@x - other.x)**2 + (@z - other.z)**2)
end end
# 3D distance using X, Y, and Z # 3D distance using X, Y, and Z
# @return [Float] # @return [Float]
def distance3d(other) def distance3d(other)
Math.sqrt((@x-other.x)**2 + (@y-other.y)**2 + (@z-other.z)**2) Math.sqrt((@x - other.x)**2 + (@y - other.y)**2 + (@z - other.z)**2)
end end
# Converts {Vector} to Array # Converts {Vector} to Array
@@ -284,7 +287,7 @@ module CyberarmEngine
# Converts {Vector} to Hash # Converts {Vector} to Hash
# @return [Hash] # @return [Hash]
def to_h def to_h
{x: @x, y: @y, z: @z, weight: @weight} { x: @x, y: @y, z: @z, weight: @weight }
end end
end end
end end

View File

@@ -1,4 +1,4 @@
module CyberarmEngine module CyberarmEngine
NAME = "InDev" NAME = "InDev".freeze
VERSION = "0.13.1" VERSION = "0.15.0".freeze
end end

View File

@@ -1,10 +1,11 @@
module CyberarmEngine module CyberarmEngine
class Engine < Gosu::Window class Window < Gosu::Window
IMAGES = {} IMAGES = {}
SAMPLES= {} SAMPLES = {}
SONGS = {} SONGS = {}
attr_accessor :show_cursor attr_accessor :show_cursor
attr_writer :exit_on_opengl_error
attr_reader :last_frame_time attr_reader :last_frame_time
def self.now def self.now
@@ -12,19 +13,20 @@ module CyberarmEngine
end end
def self.dt def self.dt
$window.last_frame_time/1000.0 $window.last_frame_time / 1000.0
end end
def initialize(width: 800, height: 600, fullscreen: false, update_interval: 1000.0/60, resizable: false) def initialize(width: 800, height: 600, fullscreen: false, update_interval: 1000.0 / 60, resizable: false, borderless: false)
@show_cursor = false @show_cursor = false
super(width, height, fullscreen: fullscreen, update_interval: update_interval, resizable: resizable) super(width, height, fullscreen: fullscreen, update_interval: update_interval, resizable: resizable, borderless: borderless)
$window = self $window = self
@last_frame_time = Gosu.milliseconds-1 @last_frame_time = Gosu.milliseconds - 1
@current_frame_time = Gosu.milliseconds @current_frame_time = Gosu.milliseconds
self.caption = "CyberarmEngine #{CyberarmEngine::VERSION} #{Gosu.language}" self.caption = "CyberarmEngine #{CyberarmEngine::VERSION} #{Gosu.language}"
@states = [] @states = []
@exit_on_opengl_error = false
setup if defined?(setup) setup if defined?(setup)
end end
@@ -34,8 +36,10 @@ module CyberarmEngine
end end
def update def update
Stats.clear
current_state.update if current_state current_state.update if current_state
@last_frame_time = Gosu.milliseconds-@current_frame_time @last_frame_time = Gosu.milliseconds - @current_frame_time
@current_frame_time = Gosu.milliseconds @current_frame_time = Gosu.milliseconds
end end
@@ -44,7 +48,15 @@ module CyberarmEngine
end end
def dt def dt
@last_frame_time/1000.0 @last_frame_time / 1000.0
end
def aspect_ratio
width / height.to_f
end
def exit_on_opengl_error?
@exit_on_opengl_error
end end
def button_down(id) def button_down(id)
@@ -57,8 +69,8 @@ module CyberarmEngine
current_state.button_up(id) if current_state current_state.button_up(id) if current_state
end end
def push_state(klass, options={}) def push_state(klass, options = {})
options = {setup: true}.merge(options) options = { setup: true }.merge(options)
if klass.instance_of?(klass.class) && defined?(klass.options) if klass.instance_of?(klass.class) && defined?(klass.options)
@states << klass @states << klass
@@ -66,12 +78,12 @@ module CyberarmEngine
else else
@states << klass.new(options) if child_of?(klass, GameState) @states << klass.new(options) if child_of?(klass, GameState)
@states << klass.new if child_of?(klass, Element::Container) @states << klass.new if child_of?(klass, Element::Container)
current_state.setup if current_state.class == klass && options[:setup] current_state.setup if current_state.instance_of?(klass) && options[:setup]
end end
end end
private def child_of?(input, klass) private def child_of?(input, klass)
input.ancestors.detect {|c| c == klass} input.ancestors.detect { |c| c == klass }
end end
def current_state def current_state
@@ -79,10 +91,8 @@ module CyberarmEngine
end end
def previous_state def previous_state
if @states.size > 1 && state = @states[@states.size-2] if @states.size > 1 && state = @states[@states.size - 2]
return state state
else
return nil
end end
end end
@@ -91,10 +101,11 @@ module CyberarmEngine
end end
# Sourced from https://gist.github.com/ippa/662583 # Sourced from https://gist.github.com/ippa/662583
def draw_circle(cx,cy,r, z = 9999,color = Gosu::Color::GREEN, step = 10) def draw_circle(cx, cy, r, z = 9999, color = Gosu::Color::GREEN, step = 10)
0.step(360, step) do |a1| 0.step(360, step) do |a1|
a2 = a1 + step a2 = a1 + step
draw_line(cx + Gosu.offset_x(a1, r), cy + Gosu.offset_y(a1, r), color, cx + Gosu.offset_x(a2, r), cy + Gosu.offset_y(a2, r), color, z) draw_line(cx + Gosu.offset_x(a1, r), cy + Gosu.offset_y(a1, r), color, cx + Gosu.offset_x(a2, r),
cy + Gosu.offset_y(a2, r), color, z)
end end
end end
end end

View File

@@ -1,4 +1,4 @@
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "cyberarm_engine" require "cyberarm_engine"
require "minitest/autorun" require "minitest/autorun"