Compare commits

14 Commits

Author SHA1 Message Date
5d1c195917 Added support for rendering multiple lights, standardized shaders to use snake case for variables and camel case for functions, stubbed PBR material shader include. 2023-07-29 14:09:23 -05:00
9a6e1df032 Remove need to do a full gui recalc 3 or more times for the layout to work (current implementation slows things down a bit, but seems more reliable then brute forcing 3x+) 2023-06-18 19:42:38 -05:00
b1b8fc8556 Cache scroll width/height 2023-06-18 14:18:15 -05:00
a60b09a110 Further scrolling improvements (should be smoother/more consistent with varied frame time) 2023-06-18 13:50:32 -05:00
81a632942e Round scroll position to prevent rendering issues caused by floats 2023-06-18 12:38:12 -05:00
5ef8023aca Improved scrolling 2023-06-18 12:26:08 -05:00
2e690d7d33 Misc bug fixes and improvements 2023-06-16 14:40:16 -05:00
a9f9e20235 Remove named arguments from stats struct due to mruby lacking support 2023-04-28 22:29:53 -05:00
25c36d3788 Add CyberarmEngine::Stats::StatsPlotter for rendering frame timings graph along with labeled sub-timings 2023-04-20 21:54:44 -05:00
c26ddeef4d Refactored CyberarmEngine::Stats to track data for last N frames 2023-04-20 16:08:59 -05:00
5e3e06b74e Improve Vector arithmetic performance by 2x 2023-04-16 20:26:29 -05:00
72037efc73 Fixed Slider element not working properly under mruby (attempts to slide would jump between min/max values) 2023-03-27 17:13:51 -05:00
1462f89e24 Initial support for using cyberarm_engine as a mruby mrbgem: Remove/disable usages of defined?, update old gosu mouse and keyboard constants, and replace one usage of window.button_down? with proper Gosu.button_down? 2023-03-24 18:21:41 -05:00
98948c891a Request repaint when hiding menu 2023-03-17 11:05:17 -05:00
25 changed files with 567 additions and 176 deletions

View File

@@ -5,25 +5,25 @@ layout (location = 1) out vec4 fragColor;
layout (location = 2) out vec3 fragNormal;
layout (location = 3) out vec3 fragUV;
in vec3 outPosition, outColor, outNormal, outUV, outFragPos, outCameraPos;
in vec3 out_position, out_color, out_normal, out_uv, out_frag_pos, out_camera_pos;
out vec4 outputFragColor;
flat in int outHasTexture;
flat in int out_has_texture;
uniform sampler2D diffuse_texture;
void main() {
vec3 result;
if (outHasTexture == 0) {
result = outColor;
if (out_has_texture == 0) {
result = out_color;
} else {
result = texture(diffuse_texture, outUV.xy).xyz + 0.25;
result = texture(diffuse_texture, out_uv.xy).xyz + 0.25;
}
fragPosition = outPosition;
fragPosition = out_position;
fragColor = vec4(result, 1.0);
fragNormal = outNormal;
fragUV = outUV;
fragNormal = out_normal;
fragUV = out_uv;
float gamma = 2.2;
outputFragColor.rgb = pow(fragColor.rgb, vec3(1.0 / gamma));

View File

@@ -1,22 +1,23 @@
#version 330 core
out vec4 FragColor;
out vec4 frag_color;
@include "light_struct"
const int DIRECTIONAL = 0;
const int POINT = 1;
const int SPOT = 2;
in vec2 outTexCoords;
flat in Light outLight[1];
flat in Light out_lights[7];
in vec2 out_tex_coords;
flat in int out_light_count;
uniform sampler2D diffuse, position, texcoord, normal, depth;
vec4 directionalLight(Light light) {
vec3 norm = normalize(texture(normal, outTexCoords).rgb);
vec3 diffuse_color = texture(diffuse, outTexCoords).rgb;
vec3 fragPos = texture(position, outTexCoords).rgb;
vec3 norm = normalize(texture(normal, out_tex_coords).rgb);
vec3 diffuse_color = texture(diffuse, out_tex_coords).rgb;
vec3 frag_pos = texture(position, out_tex_coords).rgb;
vec3 lightDir = normalize(light.position - fragPos);
vec3 lightDir = normalize(light.position - frag_pos);
float diff = max(dot(norm, lightDir), 0);
vec3 _ambient = light.ambient;
@@ -59,5 +60,10 @@ vec4 calculateLighting(Light light) {
}
void main() {
FragColor = texture(diffuse, outTexCoords) * calculateLighting(outLight[0]);
}
frag_color = vec4(0.0);
for(int i = 0; i < out_light_count; i++)
{
frag_color += texture(diffuse, out_tex_coords) * calculateLighting(out_lights[i]);
}
}

View File

@@ -0,0 +1,16 @@
struct Material {
vec3 color;
vec3 roughness;
vec3 metalic;
vec3 specular;
bool use_color_texture;
bool use_roughness_texture;
bool use_metalic_texture;
bool use_specular_texture;
sampler2D color_tex;
sampler2D roughness_tex;
sampler2D metalic_tex;
sampler2D specular_tex;
};

View File

@@ -1,28 +1,28 @@
# version 330 core
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec3 inNormal;
layout(location = 3) in vec3 inUV;
layout(location = 0) in vec3 in_position;
layout(location = 1) in vec3 in_color;
layout(location = 2) in vec3 in_normal;
layout(location = 3) in vec3 in_uv;
uniform mat4 projection, view, model;
uniform int hasTexture;
uniform vec3 cameraPos;
uniform int has_texture;
uniform vec3 camera_pos;
out vec3 outPosition, outColor, outNormal, outUV;
out vec3 outFragPos, outViewPos, outCameraPos;
flat out int outHasTexture;
out vec3 out_position, out_color, out_normal, out_uv;
out vec3 out_frag_pos, out_view_pos, out_camera_pos;
flat out int out_has_texture;
void main() {
// projection * view * model * position
outPosition = inPosition;
outColor = inColor;
outNormal= normalize(transpose(inverse(mat3(model))) * inNormal);
outUV = inUV;
outHasTexture = hasTexture;
outCameraPos = cameraPos;
out_position = in_position;
out_color = in_color;
out_normal= normalize(transpose(inverse(mat3(model))) * in_normal);
out_uv = in_uv;
out_has_texture = has_texture;
out_camera_pos = camera_pos;
outFragPos = vec3(model * vec4(inPosition, 1.0));
out_frag_pos = vec3(model * vec4(in_position, 1.0));
gl_Position = projection * view * model * vec4(inPosition, 1.0);
gl_Position = projection * view * model * vec4(in_position, 1.0);
}

View File

@@ -1,17 +1,24 @@
#version 330 core
@include "light_struct"
layout (location = 0) in vec3 inPosition;
layout (location = 1) in vec2 inTexCoords;
layout (location = 0) in vec3 in_position;
layout (location = 1) in vec2 in_tex_coords;
uniform sampler2D diffuse, position, texcoord, normal, depth;
uniform Light light[1];
uniform int light_count;
uniform Light lights[7];
out vec2 outTexCoords;
flat out Light outLight[1];
out vec2 out_tex_coords;
flat out int out_light_count;
flat out Light out_lights[7];
void main() {
gl_Position = vec4(inPosition.x, inPosition.y, inPosition.z, 1.0);
outTexCoords = inTexCoords;
outLight = light;
}
gl_Position = vec4(in_position.x, in_position.y, in_position.z, 1.0);
out_tex_coords = in_tex_coords;
out_light_count = light_count;
for(int i = 0; i < light_count; i++)
{
out_lights[i] = lights[i];
}
}

View File

@@ -66,6 +66,6 @@ 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)
require_relative "cyberarm_engine/model/parsers/collada_parser" if RUBY_ENGINE != "mruby" && defined?(Nokogiri)
require_relative "cyberarm_engine/builtin/intro_state"

View File

@@ -18,7 +18,7 @@ module CyberarmEngine
@gosu_logo = generate_proxy("Gosu", "Game Library", 0xff_111111)
@ruby_logo = generate_proxy("Ruby", "Programming Language", 0xff_880000)
@opengl_logo = generate_proxy("OpenGL", "Graphics API", 0xff_5586a4) if defined?(OpenGL)
@opengl_logo = generate_proxy("OpenGL", "Graphics API", 0xff_5586a4) if RUBY_ENGINE != "mruby" && defined?(OpenGL)
base_time = Gosu.milliseconds

View File

@@ -52,7 +52,7 @@ module CyberarmEngine
end
def lighten(color, amount = 25)
if defined?(color.alpha)
if color.respond_to?(:alpha)
Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha)
else
Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount)
@@ -60,7 +60,7 @@ module CyberarmEngine
end
def darken(color, amount = 25)
if defined?(color.alpha)
if color.respond_to?(:alpha)
Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha)
else
Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount)

View File

@@ -103,7 +103,7 @@ module CyberarmEngine
def button_down(id)
case id
when Gosu::KbEnter, Gosu::KbReturn
when Gosu::KB_ENTER, Gosu::KB_RETURN
return unless @text_input.text.length.positive?
@history.text += "\n<c=999999>> #{@text_input.text}</c>"
@@ -113,12 +113,12 @@ module CyberarmEngine
handle_command
@text_input.text = ""
when Gosu::KbUp
when Gosu::KB_UP
@command_history_index -= 1
@command_history_index = 0 if @command_history_index.negative?
@text_input.text = @command_history[@command_history_index]
when Gosu::KbDown
when Gosu::KB_DOWN
@command_history_index += 1
if @command_history_index > @command_history.size - 1
@text_input.text = "" unless @command_history_index > @command_history.size
@@ -127,7 +127,7 @@ module CyberarmEngine
@text_input.text = @command_history[@command_history_index]
end
when Gosu::KbTab
when Gosu::KB_TAB
split = @text_input.text.split(" ")
if !@text_input.text.end_with?(" ") && split.size == 1
@@ -142,7 +142,7 @@ module CyberarmEngine
cmd.autocomplete(self)
end
when Gosu::KbBacktick
when Gosu::KB_BACKTICK
# Remove backtick character from input
@text_input.text = if @text_input.text.size > 1
@text_input.text[0..@text_input.text.size - 2]
@@ -151,7 +151,7 @@ module CyberarmEngine
end
# Copy
when Gosu::KbC
when Gosu::KB_C
if control_down? && shift_down?
@memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end
p @memory
@@ -160,7 +160,7 @@ module CyberarmEngine
end
# Paste
when Gosu::KbV
when Gosu::KB_V
if control_down? && shift_down?
string = @text_input.text.chars.insert(caret_pos, @memory).join
_caret_pos = caret_pos
@@ -170,7 +170,7 @@ module CyberarmEngine
end
# Cut
when Gosu::KbX
when Gosu::KB_X
if control_down? && shift_down?
@memory = @text_input.text[caret_start..caret_end - 1] if caret_start != caret_end
string = @text_input.text.chars
@@ -182,7 +182,7 @@ module CyberarmEngine
end
# Delete word to left of caret
when Gosu::KbW
when Gosu::KB_W
if control_down?
split = @text_input.text.split(" ")
split.delete(split.last)
@@ -190,7 +190,7 @@ module CyberarmEngine
end
# Clear history
when Gosu::KbL
when Gosu::KB_L
@history.text = "" if control_down?
end
end

View File

@@ -42,7 +42,7 @@ module CyberarmEngine
@radius = if options[:radius]
options[:radius]
else
defined?(@image.width) ? ((@image.width + @image.height) / 4) * scale : 1
@image.respond_to?(:width) ? ((@image.width + @image.height) / 4) * scale : 1
end
end
end

View File

@@ -49,8 +49,9 @@ module CyberarmEngine
@objects.each { |o| @vertex_count += o.vertices.size }
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
# start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
# build_collision_tree
# puts " Building mesh collision tree took #{((Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_time) / 1000.0).round(2)} seconds"
end
def parse(parser)
@@ -150,24 +151,24 @@ module CyberarmEngine
glBindBuffer(GL_ARRAY_BUFFER, @positions_buffer_id)
gl_error?
# inPosition
# in_position
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nil)
gl_error?
# colors
glBindBuffer(GL_ARRAY_BUFFER, @colors_buffer_id)
# inColor
# in_color
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nil)
gl_error?
# normals
glBindBuffer(GL_ARRAY_BUFFER, @normals_buffer_id)
# inNormal
# in_normal
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, nil)
gl_error?
if has_texture?
# uvs
glBindBuffer(GL_ARRAY_BUFFER, @uvs_buffer_id)
# inUV
# in_uv
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, nil)
gl_error?
end
@@ -177,7 +178,7 @@ module CyberarmEngine
end
def build_collision_tree
@aabb_tree = AABBTree.new
@aabb_tree = IMICFPS::AABBTree.new
@faces.each do |face|
box = BoundingBox.new

View File

@@ -44,7 +44,7 @@ module CyberarmEngine
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)
shader.uniform_vector3("camera_position", camera.position)
gl_error?
draw_model(entity.model, shader)
@@ -154,15 +154,21 @@ module CyberarmEngine
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)
# FIXME: Try to figure out how to up this to 32 and/or beyond
# (currently fails with more then 7 lights passed in to shader)
lights.each_slice(7).each do |light_group|
light_group.each_with_index do |light, _i|
shader.uniform_integer("light_count", light_group.size)
glDrawArrays(GL_TRIANGLES, 0, @g_buffer.vertices.size)
shader.uniform_integer("lights[#{_i}].type", light.type)
shader.uniform_vector3("lights[#{_i}].direction", light.direction)
shader.uniform_vector3("lights[#{_i}].position", light.position)
shader.uniform_vector3("lights[#{_i}].diffuse", light.diffuse)
shader.uniform_vector3("lights[#{_i}].ambient", light.ambient)
shader.uniform_vector3("lights[#{_i}].specular", light.specular)
glDrawArrays(GL_TRIANGLES, 0, @g_buffer.vertices.size)
end
end
glBindVertexArray(0)
@@ -215,7 +221,7 @@ module CyberarmEngine
offset = 0
model.objects.each do |object|
shader.uniform_boolean("hasTexture", object.has_texture?)
shader.uniform_boolean("has_texture", object.has_texture?)
if object.has_texture?
glBindTexture(GL_TEXTURE_2D, object.materials.find { |mat| mat.texture_id }.texture_id)

View File

@@ -8,8 +8,19 @@ module CyberarmEngine
end
def draw(camera, lights, entities)
Stats.frame.start_timing(:opengl_renderer)
Stats.frame.start_timing(:opengl_model_renderer)
@opengl_renderer.render(camera, lights, entities)
@bounding_box_renderer.render(entities) if @show_bounding_boxes
Stats.frame.end_timing(:opengl_model_renderer)
if @show_bounding_boxes
Stats.frame.start_timing(:opengl_boundingbox_renderer)
@bounding_box_renderer.render(entities)
Stats.frame.end_timing(:opengl_boundingbox_renderer)
end
Stats.frame.end_timing(:opengl_renderer)
end
def canvas_size_changed

View File

@@ -385,7 +385,7 @@ module CyberarmEngine
# @param value [Vector]
# @param location [Integer]
# @return [void]
def uniform_vec3(variable, value, location = nil)
def uniform_vector3(variable, value, location = nil)
attr_loc = location || attribute_location(variable)
glUniform3f(attr_loc, *value.to_a[0..2])
@@ -397,7 +397,7 @@ module CyberarmEngine
# @param value [Vector]
# @param location [Integer]
# @return [void]
def uniform_vec4(variable, value, location = nil)
def uniform_vector4(variable, value, location = nil)
attr_loc = location || attribute_location(variable)
glUniform4f(attr_loc, *value.to_a)

View File

@@ -1,20 +1,191 @@
module CyberarmEngine
class Stats
@@hash = {
gui_recalculations_last_frame: 0
}
@frames = []
@frame_index = -1
@max_frame_history = 1024
def self.get(key)
@@hash.dig(key)
def self.new_frame
if @frames.size < @max_frame_history
@frames << Frame.new
else
@frames[@frame_index] = Frame.new
end
end
def self.increment(key, n)
@@hash[key] += n
def self.frame
@frames[@frame_index]
end
def self.clear
@@hash.each do |key, _value|
@@hash[key] = 0
def self.end_frame
frame&.complete
@frame_index += 1
@frame_index %= @max_frame_history
end
def self.frames
if @frames.size < @max_frame_history
@frames
else
@frames.rotate(@frame_index - (@max_frame_history - (@frames.size - 1)))
end
end
def self.frame_index
@frame_index
end
def self.max_frame_history
@max_frame_history
end
class Frame
Timing = Struct.new(:start_time, :end_time, :duration)
attr_reader :frame_timing, :counters, :timings, :multitimings
def initialize
@frame_timing = Timing.new(Gosu.milliseconds, -1, -1)
@attempted_multitiming = false
@counters = {
gui_recalculations: 0
}
@timings = {}
@multitimings = {}
end
def increment(key, number = 1)
@counters[key] ||= 0
@counters[key] += number
end
def start_timing(key)
raise "key must be a symbol!" unless key.is_a?(Symbol)
if @timings[key]
# FIXME: Make it not spammy...
# warn "Only one timing per key per frame. (Timing for #{key.inspect} already exists!)"
@attempted_multitiming = true
@multitimings[key] = true
return
end
@timings[key] = Timing.new(Gosu.milliseconds, -1, -1)
end
def end_timing(key)
timing = @timings[key]
# FIXME: Make it not spammy...
# warn "Timing #{key.inspect} already ended!" if timing.end_time != -1
timing.end_time = Gosu.milliseconds
timing.duration = timing.end_time - timing.start_time
end
def complete
@frame_timing.end_time = Gosu.milliseconds
@frame_timing.duration = @frame_timing.end_time - @frame_timing.start_time
# Lock data structures
@frame_timing.freeze
@counters.freeze
@timings.freeze
@multitimings.freeze
end
def complete?
@frame_timing.duration != -1
end
def attempted_multitiming?
@attempted_multitiming
end
end
class StatsPlotter
attr_reader :position
def initialize(x, y, z = Float::INFINITY, width = 128, height = 128)
@position = Vector.new(x, y, z)
@width = width
@height = height
@padding = 2
@text_size = 16
@max_timing_label = CyberarmEngine::Text.new("", x: x + @padding + 1, y: y + @padding, z: z, size: @text_size, border: true)
@avg_timing_label = CyberarmEngine::Text.new("", x: x + @padding + 1, y: y + @padding + @height / 2 - @text_size / 2, z: z, size: @text_size, border: true)
@min_timing_label = CyberarmEngine::Text.new("", x: x + @padding + 1, y: y + @height - (@text_size + @padding / 2), z: z, size: @text_size, border: true)
@timings_label = CyberarmEngine::Text.new("", x: x + @padding + @width + @padding, y: y + @padding, z: z, size: @text_size, border: true)
@frame_stats = []
@graphs = {
frame_timings: []
}
end
def calculate_graphs
calculate_frame_timings_graph
end
def calculate_frame_timings_graph
@graphs[:frame_timings].clear
samples = @width - @padding
nodes = Array.new(samples.ceil) { [] }
slice = 0
@frame_stats.each_slice((CyberarmEngine::Stats.max_frame_history / samples.to_f).ceil) do |bucket|
bucket.each do |frame|
nodes[slice] << frame.frame_timing.duration
end
slice += 1
end
nodes.each_with_index do |cluster, i|
break if cluster.empty?
@graphs[:frame_timings] << CyberarmEngine::Vector.new(@position.x + @padding + 1 * i, (@position.y + @height - @padding) - cluster.max)
end
end
def draw
@frame_stats = CyberarmEngine::Stats.frames.select(&:complete?)
return if @frame_stats.empty?
calculate_graphs
@max_timing_label.text = "Max: #{@frame_stats.map { |f| f.frame_timing.duration }.max.to_s.rjust(3, " ")}ms"
@avg_timing_label.text = "Avg: #{(@frame_stats.map { |f| f.frame_timing.duration }.sum / @frame_stats.size).to_s.rjust(3, " ")}ms"
@min_timing_label.text = "Min: #{@frame_stats.map { |f| f.frame_timing.duration }.min.to_s.rjust(3, " ")}ms"
Gosu.draw_rect(@position.x, @position.y, @width, @height, 0xaa_222222, @position.z)
Gosu.draw_rect(@position.x + @padding, @position.y + @padding, @width - @padding * 2, @height - @padding * 2, 0xaa_222222, @position.z)
draw_graphs
@max_timing_label.draw
@avg_timing_label.draw
@min_timing_label.draw
# TODO: Make this optional
draw_timings
end
def draw_graphs
Gosu.draw_path(@graphs[:frame_timings], Gosu::Color::WHITE, Float::INFINITY)
end
def draw_timings
frame = @frame_stats.last
@timings_label.text = "#{frame.attempted_multitiming? ? "<c=d00>Attempted Multitiming!\nTimings may be inaccurate for:\n#{frame.multitimings.map { |m, _| m}.join("\n") }</c>\n\n" : ''}#{frame.timings.map { |t, v| "#{t}: #{v.duration}ms" }.join("\n")}"
Gosu.draw_rect(@timings_label.x - @padding, @timings_label.y - @padding, @timings_label.width + @padding * 2, @timings_label.height + @padding * 2, 0xdd_222222, @position.z)
@timings_label.draw
end
end
end

View File

@@ -81,6 +81,7 @@ module CyberarmEngine
@size = size
@font = font_name
invalidate_cache!
@textobject = check_cache(size, font_name)
end
end
@@ -149,6 +150,8 @@ module CyberarmEngine
end
def markup_width(text = @text)
text = text.to_s
spacing = 0
spacing += @border_size if @border
spacing += @shadow_size if @shadow

View File

@@ -196,7 +196,7 @@ module CyberarmEngine
end
def enter(_sender)
@focus = false unless window.button_down?(Gosu::MsLeft)
@focus = false unless Gosu.button_down?(Gosu::MS_LEFT)
if !@enabled
update_styles(:disabled)
@@ -316,7 +316,8 @@ module CyberarmEngine
end
def debug_draw
return if defined?(GUI_DEBUG_ONLY_ELEMENT) && self.class == GUI_DEBUG_ONLY_ELEMENT
# FIXME
return# if const_defined?(GUI_DEBUG_ONLY_ELEMENT) && self.class == GUI_DEBUG_ONLY_ELEMENT
Gosu.draw_line(
x, y, @debug_color,
@@ -410,10 +411,14 @@ module CyberarmEngine
end
def scroll_width
@children.sum(&:outer_width)
return @cached_scroll_width if @cached_scroll_width && is_a?(Container)
@cached_scroll_width = @children.sum(&:outer_width)
end
def scroll_height
return @cached_scroll_height if @cached_scroll_height && is_a?(Container)
if is_a?(CyberarmEngine::Element::Flow)
return 0 if @children.size.zero?
@@ -434,18 +439,18 @@ module CyberarmEngine
pairs_ << a_ unless pairs_.last == a_
pairs_.sum { |pair| + @style.padding_top + @style.border_thickness_top + pair.map(&:outer_height).max } + @style.padding_bottom + @style.border_thickness_bottom
@cached_scroll_height = pairs_.sum { |pair| + @style.padding_top + @style.border_thickness_top + pair.map(&:outer_height).max } + @style.padding_bottom + @style.border_thickness_bottom
else
@style.padding_top + @style.border_thickness_top + @children.sum(&:outer_height) + @style.padding_bottom + @style.border_thickness_bottom
@cached_scroll_height = @style.padding_top + @style.border_thickness_top + @children.sum(&:outer_height) + @style.padding_bottom + @style.border_thickness_bottom
end
end
def max_scroll_width
scroll_width - outer_width
(scroll_width - outer_width).positive? ? scroll_width - outer_width : scroll_width
end
def max_scroll_height
scroll_height - outer_height
(scroll_height - outer_height).positive? ? scroll_height - outer_height : scroll_height
end
def dimensional_size(size, dimension)
@@ -461,15 +466,13 @@ module CyberarmEngine
if @parent && @style.fill &&
(dimension == :width && @parent.is_a?(Flow) ||
dimension == :height && @parent.is_a?(Stack))
return space_available_width - noncontent_width if dimension == :width && @parent.is_a?(Flow)
return space_available_height - noncontent_height if dimension == :height && @parent.is_a?(Stack)
# Handle min_width/height and max_width/height
else
return @style.send(:"min_#{dimension}") if @style.send(:"min_#{dimension}") && new_size.to_f < @style.send(:"min_#{dimension}")
return @style.send(:"max_#{dimension}") if @style.send(:"max_#{dimension}") && new_size.to_f > @style.send(:"max_#{dimension}")
new_size = space_available_width - noncontent_width if dimension == :width && @parent.is_a?(Flow)
new_size = space_available_height - noncontent_height if dimension == :height && @parent.is_a?(Stack)
end
return @style.send(:"min_#{dimension}") if @style.send(:"min_#{dimension}") && new_size.to_f < @style.send(:"min_#{dimension}")
return @style.send(:"max_#{dimension}") if @style.send(:"max_#{dimension}") && new_size.to_f > @style.send(:"max_#{dimension}")
new_size
end
@@ -575,6 +578,20 @@ module CyberarmEngine
@gui_state != nil
end
def child_of?(element)
return element == self if is_root?
return false unless element.is_a?(Container)
return true if element.children.find { |child| child == self }
element.children.find { |child| child.child_of?(element) if child.is_a?(Container) }
end
# def parent_of?(element)
# return true if element.children.find { |child| child == self }
# element.children.find { |child| child.parent}
# end
def focus(_)
warn "#{self.class}#focus was not overridden!"

View File

@@ -20,7 +20,10 @@ module CyberarmEngine
@gui_state = options.delete(:gui_state)
super
@last_scroll_position = Vector.new(0, 0)
@scroll_position = Vector.new(0, 0)
@scroll_target_position = Vector.new(0, 0)
@scroll_chunk = 120
@scroll_speed = 40
@text_color = options[:color]
@@ -33,17 +36,17 @@ module CyberarmEngine
def build
@block.call(self) if @block
root.gui_state.request_recalculate
root.gui_state.request_recalculate_for(self)
end
def add(element)
@children << element
root.gui_state.request_recalculate
root.gui_state.request_recalculate_for(self)
end
def remove(element)
root.gui_state.request_recalculate if @children.delete(element)
root.gui_state.request_recalculate_for(self) if @children.delete(element)
end
def clear(&block)
@@ -56,7 +59,7 @@ module CyberarmEngine
CyberarmEngine::Element::Container.current_container = old_container
root.gui_state.request_recalculate
root.gui_state.request_recalculate_for(self)
end
def append(&block)
@@ -67,7 +70,7 @@ module CyberarmEngine
CyberarmEngine::Element::Container.current_container = old_container
root.gui_state.request_recalculate
root.gui_state.request_recalculate_for(self)
end
def render
@@ -77,7 +80,9 @@ module CyberarmEngine
content_width + 1,
content_height + 1
) do
@children.each(&:draw)
Gosu.translate(@scroll_position.x, @scroll_position.y) do
@children.each(&:draw)
end
end
end
@@ -90,35 +95,74 @@ module CyberarmEngine
end
def update
update_scroll
@children.each(&:update)
end
def hit_element?(x, y)
return unless hit?(x, y)
# Offset child hit point by scroll position/offset
child_x = x - @scroll_position.x
child_y = y - @scroll_position.y
@children.reverse_each do |child|
next unless child.visible?
case child
when Container
if element = child.hit_element?(x, y)
if element = child.hit_element?(child_x, child_y)
return element
end
else
return child if child.hit?(x, y)
return child if child.hit?(child_x, child_y)
end
end
self if hit?(x, y)
end
def update_child_element_visibity(child)
child.element_visible = child.x >= (@x - @scroll_position.x) - child.width && child.x <= (@x - @scroll_position.x) + width &&
child.y >= (@y - @scroll_position.y) - child.height && child.y <= (@y - @scroll_position.y) + height
end
def update_scroll
dt = window.dt > 1 ? 1.0 : window.dt
scroll_x_diff = (@scroll_target_position.x - @scroll_position.x)
scroll_y_diff = (@scroll_target_position.y - @scroll_position.y)
@scroll_position.x += (scroll_x_diff * @scroll_speed * 0.25 * dt).round
@scroll_position.y += (scroll_y_diff * @scroll_speed * 0.25 * dt).round
@scroll_position.x = @scroll_target_position.x if scroll_x_diff.abs < 1.0
@scroll_position.y = @scroll_target_position.y if scroll_y_diff.abs < 1.0
# Scrolled PAST top
if @scroll_position.y > 0
@scroll_target_position.y = 0
# Scrolled PAST bottom
elsif @scroll_position.y.abs > max_scroll_height
@scroll_target_position.y = -max_scroll_height
end
if @last_scroll_position != @scroll_position
@children.each { |child| update_child_element_visibity(child) }
root.gui_state.request_repaint
end
@last_scroll_position.x = @scroll_position.x
@last_scroll_position.y = @scroll_position.y
end
def recalculate
@current_position = Vector.new(@style.margin_left + @style.padding_left, @style.margin_top + @style.padding_top)
@current_position += @scroll_position
return unless visible?
Stats.increment(:gui_recalculations_last_frame, 1)
Stats.frame.increment(:gui_recalculations)
stylize
@@ -129,6 +173,9 @@ module CyberarmEngine
old_width = @width
old_height = @height
@cached_scroll_width = nil
@cached_scroll_height = nil
if is_root?
@width = @style.width = window.width
@height = @style.height = window.height
@@ -176,16 +223,28 @@ module CyberarmEngine
child.recalculate
child.reposition # TODO: Implement top,bottom,left,center, and right positioning
Stats.increment(:gui_recalculations_last_frame, 1)
Stats.frame.increment(:gui_recalculations)
child.element_visible = child.x >= @x - child.width && child.x <= @x + width &&
child.y >= @y - child.height && child.y <= @y + height
update_child_element_visibity(child)
end
# puts "TOOK: #{Gosu.milliseconds - s}ms to recalculate #{self.class}:0x#{self.object_id.to_s(16)}"
update_background
# Fixes resized container scrolled past bottom
self.scroll_top = -@scroll_position.y
@scroll_target_position.y = @scroll_position.y
# NOTE: Experiment for removing need to explicitly call gui_state#recalculate at least 3 times for layout to layout...
if old_width != @width || old_height != @height
if @parent
root.gui_state.request_recalculate_for(@parent)
else
root.gui_state.request_recalculate
end
end
root.gui_state.request_repaint if @width != old_width || @height != old_height
end
@@ -246,12 +305,13 @@ module CyberarmEngine
def mouse_wheel_up(sender, x, y)
return unless @style.scroll
if @scroll_position.y < 0
@scroll_position.y += @scroll_speed
@scroll_position.y = 0 if @scroll_position.y > 0
root.gui_state.request_recalculate_for(self)
root.gui_state.request_repaint
# Allow overscrolling UP, only if one can scroll DOWN
if height < scroll_height
if @scroll_target_position.y > 0
@scroll_target_position.y = @scroll_chunk
else
@scroll_target_position.y += @scroll_chunk
end
return :handled
end
@@ -262,15 +322,13 @@ module CyberarmEngine
return unless height < scroll_height
if @scroll_position.y.abs < max_scroll_height
@scroll_position.y -= @scroll_speed
@scroll_position.y = -max_scroll_height if @scroll_position.y.abs > max_scroll_height
root.gui_state.request_recalculate_for(self)
root.gui_state.request_repaint
return :handled
if @scroll_target_position.y > 0
@scroll_target_position.y = -@scroll_chunk
else
@scroll_target_position.y -= @scroll_chunk
end
return :handled
end
def scroll_top

View File

@@ -33,7 +33,8 @@ module CyberarmEngine
end
end
attr_reader :range, :step_size, :value
attr_reader :step_size, :value
attr_accessor :range, :step_size
def initialize(options = {}, block = nil)
super(options, block)
@@ -76,7 +77,7 @@ module CyberarmEngine
def update
super
@tip = value.to_s
@tip = format("%.2f", value.to_f)
@handle.tip = @tip
end
@@ -87,7 +88,7 @@ module CyberarmEngine
end
def handle_dragged_to(x, _y)
@ratio = ((x - @handle.width / 2) - @x) / (content_width - @handle.outer_width)
@ratio = ((x - @handle.width / 2.0) - @x) / (content_width - @handle.outer_width.to_f)
self.value = @ratio.clamp(0.0, 1.0) * (@range.max - @range.min) + @range.min
end

View File

@@ -18,6 +18,15 @@ module CyberarmEngine
@raw_text = text
end
def update
super
if @text.textobject.name != safe_style_fetch(:font)
set_font
root.gui_state.request_recalculate
end
end
def render
# Gosu.clip_to is too expensive to always use so check if we actually need it.
if @text.width > width || @text.height > height
@@ -156,7 +165,7 @@ module CyberarmEngine
end
def line_width(text)
(@text.textobject.markup_width(text) + noncontent_width)
(@text.textobject.markup_width(text.to_s) + noncontent_width)
end
def value

View File

@@ -63,7 +63,8 @@ module CyberarmEngine
@tip.draw
end
if defined?(GUI_DEBUG)
# FIXME
if false# defined?(GUI_DEBUG)
Gosu.flush
@root_container.debug_draw
@@ -78,16 +79,22 @@ module CyberarmEngine
def update
if @pending_recalculate_request
@root_container.recalculate
@root_container.recalculate
Stats.frame.start_timing(:gui_recalculate)
@root_container.recalculate
@pending_recalculate_request = false
Stats.frame.end_timing(:gui_recalculate)
end
Stats.frame.start_timing(:gui_element_recalculate_requests)
# puts "PENDING REQUESTS: #{@pending_element_recalculate_requests.size}" if @pending_element_recalculate_requests.size.positive?
@pending_element_recalculate_requests.each(&:recalculate)
@pending_element_recalculate_requests.clear
Stats.frame.end_timing(:gui_element_recalculate_requests)
if @pending_focus_request
@pending_focus_request = false
@@ -121,9 +128,9 @@ module CyberarmEngine
@mouse_over.publish(:leave) if @mouse_over && new_mouse_over != @mouse_over
@mouse_over = new_mouse_over
redirect_holding_mouse_button(:left) if @mouse_over && Gosu.button_down?(Gosu::MsLeft)
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(:left) if @mouse_over && Gosu.button_down?(Gosu::MS_LEFT)
redirect_holding_mouse_button(:middle) if @mouse_over && Gosu.button_down?(Gosu::MS_MIDDLE)
redirect_holding_mouse_button(:right) if @mouse_over && Gosu.button_down?(Gosu::MS_RIGHT)
if Vector.new(window.mouse_x, window.mouse_y) == @last_mouse_pos
if @mouse_over && (Gosu.milliseconds - @mouse_moved_at) > tool_tip_delay
@@ -151,13 +158,13 @@ module CyberarmEngine
super
case id
when Gosu::MsLeft
when Gosu::MS_LEFT
redirect_mouse_button(:left)
when Gosu::MsMiddle
when Gosu::MS_MIDDLE
redirect_mouse_button(:middle)
when Gosu::MsRight
when Gosu::MS_RIGHT
redirect_mouse_button(:right)
when Gosu::KbF5
when Gosu::KB_F5
request_recalculate
end
@@ -168,15 +175,15 @@ module CyberarmEngine
super
case id
when Gosu::MsLeft
when Gosu::MS_LEFT
redirect_released_mouse_button(:left)
when Gosu::MsMiddle
when Gosu::MS_MIDDLE
redirect_released_mouse_button(:middle)
when Gosu::MsRight
when Gosu::MS_RIGHT
redirect_released_mouse_button(:right)
when Gosu::MsWheelUp
when Gosu::MS_WHEEL_UP
redirect_mouse_wheel(:up)
when Gosu::MsWheelDown
when Gosu::MS_WHEEL_DOWN
redirect_mouse_wheel(:down)
end
@@ -276,6 +283,8 @@ module CyberarmEngine
def hide_menu
return unless @menu
request_repaint
@hid_menu_for = @menu.parent
@menu = nil
end

View File

@@ -20,7 +20,8 @@ module CyberarmEngine
attr_reader :hash
def initialize(hash = {})
h = Marshal.load(Marshal.dump(hash))
h = hash
# h = Marshal.load(Marshal.dump(hash))
h[:default] = {}

View File

@@ -95,39 +95,58 @@ module CyberarmEngine
Vector.new(@x, @y)
end
# Performs math operation, excluding {weight}
private def operator(function, other)
if other.is_a?(Numeric)
Vector.new(
@x.send(:"#{function}", other),
@y.send(:"#{function}", other),
@z.send(:"#{function}", other)
)
else
Vector.new(
@x.send(:"#{function}", other.x),
@y.send(:"#{function}", other.y),
@z.send(:"#{function}", other.z)
)
end
end
# Adds Vector and Numeric or Vector and Vector, excluding {weight}
# @return [CyberarmEngine::Vector]
def +(other)
operator("+", other)
if other.is_a?(Numeric)
Vector.new(
@x + other,
@y + other,
@z + other
)
else
Vector.new(
@x + other.x,
@y + other.y,
@z + other.z
)
end
end
# Subtracts Vector and Numeric or Vector and Vector, excluding {weight}
# @return [CyberarmEngine::Vector]
def -(other)
operator("-", other)
if other.is_a?(Numeric)
Vector.new(
@x - other,
@y - other,
@z - other
)
else
Vector.new(
@x - other.x,
@y - other.y,
@z - other.z
)
end
end
# Multiplies Vector and Numeric or Vector and Vector, excluding {weight}
# @return [CyberarmEngine::Vector]
def *(other)
operator("*", other)
if other.is_a?(Numeric)
Vector.new(
@x * other,
@y * other,
@z * other
)
else
Vector.new(
@x * other.x,
@y * other.y,
@z * other.z
)
end
end
def multiply_transform(transform)

View File

@@ -6,16 +6,16 @@ module CyberarmEngine
SAMPLES = {}
SONGS = {}
attr_accessor :show_cursor
attr_accessor :show_cursor, :show_stats_plotter
attr_writer :exit_on_opengl_error
attr_reader :last_frame_time, :states
attr_reader :last_frame_time, :delta_time, :states
def self.now
Gosu.milliseconds
end
def self.dt
instance.last_frame_time / 1000.0
instance.dt
end
def self.instance=(window)
@@ -31,32 +31,57 @@ module CyberarmEngine
def initialize(width: 800, height: 600, fullscreen: false, update_interval: 1000.0 / 60, resizable: false, borderless: false)
@show_cursor = false
@has_focus = false
@show_stats_plotter = false
super(width, height, fullscreen: fullscreen, update_interval: update_interval, resizable: resizable, borderless: borderless)
Window.instance = self
@last_frame_time = Gosu.milliseconds - 1
@current_frame_time = Gosu.milliseconds
self.caption = "CyberarmEngine #{CyberarmEngine::VERSION} #{Gosu.language}"
@delta_time = @last_frame_time
self.caption = "CyberarmEngine #{CyberarmEngine::VERSION} #{Gosu.user_languages.join(', ')}"
@states = []
@exit_on_opengl_error = false
preload_default_shaders if respond_to?(:preload_default_shaders)
@stats_plotter = Stats::StatsPlotter.new(2, 28) # FIXME: Make positioning easy
setup if defined?(setup)
setup
end
def setup
end
def draw
Stats.frame.start_timing(:draw)
current_state&.draw
Stats.frame.start_timing(:engine_stats_renderer)
@stats_plotter&.draw if @show_stats_plotter
Stats.frame.end_timing(:engine_stats_renderer)
Stats.frame.end_timing(:draw)
Stats.frame.start_timing(:interframe_sleep)
end
def update
Stats.clear
# Gosu calls update() then (optionally) draw(),
# so always end last frame and start next frame when update() is called.
Stats.frame&.end_timing(:interframe_sleep)
Stats.end_frame
current_state&.update
Stats.new_frame
@delta_time = (Gosu.milliseconds - @current_frame_time) * 0.001
@last_frame_time = Gosu.milliseconds - @current_frame_time
@current_frame_time = Gosu.milliseconds
Stats.frame.start_timing(:update)
current_state&.update
Stats.frame.end_timing(:update)
Stats.frame.start_timing(:interframe_sleep) unless needs_redraw?
end
def needs_cursor?
@@ -120,7 +145,7 @@ module CyberarmEngine
def push_state(klass, options = {})
options = { setup: true }.merge(options)
if klass.instance_of?(klass.class) && defined?(klass.options)
if klass.instance_of?(klass.class) && klass.respond_to?(:options)
@states << klass
klass.setup if options[:setup]
klass.post_setup if options[:setup]
@@ -133,6 +158,8 @@ module CyberarmEngine
end
private def child_of?(input, klass)
return false unless input
input.ancestors.detect { |c| c == klass }
end

29
mrbgem.rake Normal file
View File

@@ -0,0 +1,29 @@
MRuby::Gem::Specification.new("mruby-cyberarm_engine") do |spec|
spec.license = "MIT"
spec.authors = "cyberarm"
spec.summary = " Yet another framework for building games with Gosu"
lib_rbfiles = []
# Dir.glob("#{File.expand_path("..", __FILE__)}/lib/**/*.rb").reject do |f|
# File.basename(f.downcase, ".rb") == "cyberarm_engine" ||
# File.basename(f.downcase, ".rb") == "opengl" ||
# f.downcase.include?("/opengl/")
# end.reverse!
local_path = File.expand_path("..", __FILE__)
File.read("#{local_path}/lib/cyberarm_engine.rb").each_line do |line|
line = line.strip
next unless line.start_with?("require_relative")
file = line.split("require_relative").last.strip.gsub("\"", "")
next if file.include?(" if ")
lib_rbfiles << "#{local_path}/lib/#{file}.rb"
end
pp lib_rbfiles
spec.rbfiles = lib_rbfiles
end