gamebox 0.4.0.rc5 → 0.4.0.rc11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +205 -127
- data/bin/gamebox +49 -3
- data/bin/gb +87 -0
- data/gamebox.gemspec +4 -3
- data/lib/gamebox.rb +1 -1
- data/lib/gamebox/actors/collidable_debugger.rb +4 -4
- data/lib/gamebox/actors/icon.rb +7 -0
- data/lib/gamebox/actors/label.rb +41 -42
- data/lib/gamebox/behaviors/animated.rb +6 -0
- data/lib/gamebox/behaviors/audible.rb +1 -2
- data/lib/gamebox/behaviors/collidable.rb +1 -1
- data/lib/gamebox/behaviors/graphical.rb +8 -4
- data/lib/gamebox/behaviors/physical.rb +6 -1
- data/lib/gamebox/behaviors/positioned.rb +4 -11
- data/lib/gamebox/behaviors/projectile.rb +8 -0
- data/lib/gamebox/behaviors/visible.rb +3 -3
- data/lib/gamebox/core/aabb_tree.rb +1 -1
- data/lib/gamebox/core/actor.rb +37 -50
- data/lib/gamebox/core/actor_definition.rb +41 -0
- data/lib/gamebox/core/actor_view.rb +6 -21
- data/lib/gamebox/core/actor_view_definition.rb +19 -0
- data/lib/gamebox/core/actor_view_factory.rb +9 -3
- data/lib/gamebox/core/behavior.rb +8 -27
- data/lib/gamebox/core/behavior_definition.rb +24 -0
- data/lib/gamebox/core/config_manager.rb +45 -30
- data/lib/gamebox/core/configuration.rb +5 -0
- data/lib/gamebox/core/core.rb +4 -0
- data/lib/gamebox/core/debug_helpers.rb +46 -0
- data/lib/gamebox/core/director.rb +32 -5
- data/lib/gamebox/core/event_symbols.rb +214 -0
- data/lib/gamebox/core/game.rb +1 -1
- data/lib/gamebox/core/input_manager.rb +1 -4
- data/lib/gamebox/core/input_mapper.rb +85 -0
- data/lib/gamebox/core/physics.rb +7 -3
- data/lib/gamebox/core/physics_manager.rb +5 -1
- data/lib/gamebox/core/renderer.rb +72 -0
- data/lib/gamebox/core/stage.rb +25 -81
- data/lib/gamebox/core/stage_definition.rb +60 -0
- data/lib/gamebox/core/stage_factory.rb +56 -0
- data/lib/gamebox/core/stage_manager.rb +5 -11
- data/lib/gamebox/core/timer_manager.rb +6 -2
- data/lib/gamebox/core/viewport.rb +12 -5
- data/lib/gamebox/core/wrapped_screen.rb +8 -5
- data/lib/gamebox/gamebox_application.rb +21 -19
- data/lib/gamebox/lib/array_ext.rb +9 -0
- data/lib/gamebox/lib/observable_attributes.rb +24 -0
- data/lib/gamebox/lib/vector2.rb +432 -0
- data/lib/gamebox/post_setup_handlers/file_watcher.rb +37 -0
- data/lib/gamebox/post_setup_handlers/gamebox_debug_helpers.rb +13 -0
- data/lib/gamebox/post_setup_handlers/pry_remote_server.rb +29 -0
- data/lib/gamebox/spec/helper.rb +165 -17
- data/lib/gamebox/tasks/gamebox_tasks.rake +27 -12
- data/lib/gamebox/version.rb +1 -1
- data/lib/gamebox/views/graphical_actor_view.rb +4 -5
- data/script/perf_aabb.rb +13 -8
- data/spec/acceptance/animation_spec.rb +1 -3
- data/spec/acceptance/basic_actor_lifecycle_spec.rb +1 -1
- data/spec/acceptance/fps_actor_spec.rb +8 -12
- data/spec/acceptance/input_mapper_spec.rb +17 -24
- data/spec/acceptance/update_ordering_spec.rb +64 -0
- data/spec/actors/label_spec.rb +90 -5
- data/spec/behaviors/animated_spec.rb +1 -1
- data/spec/behaviors/collidable_spec.rb +7 -15
- data/spec/behaviors/positioned_spec.rb +12 -5
- data/spec/core/actor_spec.rb +31 -3
- data/spec/core/actor_view_spec.rb +1 -1
- data/spec/core/behavior_spec.rb +3 -0
- data/spec/core/configuration_spec.rb +49 -2
- data/spec/core/input_mapper_spec.rb +7 -0
- data/spec/core/renderer_spec.rb +89 -0
- data/spec/core/stage_definition_spec.rb +41 -0
- data/spec/core/stage_manager_spec.rb +11 -11
- data/spec/core/stage_spec.rb +38 -78
- data/spec/core/viewport_spec.rb +5 -2
- data/spec/core/wrapped_screen_spec.rb +18 -12
- data/spec/views/graphical_actor_view_spec.rb +33 -62
- data/templates/actor_template.erb +11 -0
- data/templates/app/README.md +1 -0
- data/templates/app/src/actors/{player.rb → player_actor.rb} +3 -1
- data/templates/app/src/behaviors/.gitkeep +0 -0
- data/templates/app/src/stages/demo_stage.rb +14 -0
- data/templates/behavior_template.erb +13 -0
- data/templates/stage_template.erb +13 -0
- metadata +60 -21
- data/component_generators/actor_generator.rb +0 -17
- data/lib/gamebox/actors/emitter.rb +0 -12
- data/lib/gamebox/behaviors/emitting.rb +0 -48
- data/lib/gamebox/behaviors/input_mapper.rb +0 -11
- data/lib/gamebox/lib/ftor.rb +0 -372
- data/spec/actors/emitter_spec.rb +0 -5
- data/templates/app/NEXT_STEPS.txt +0 -1
- data/templates/app/README.rdoc +0 -24
- data/templates/app/src/demo_stage.rb +0 -7
@@ -1,7 +1,7 @@
|
|
1
1
|
class StageManager
|
2
2
|
|
3
|
-
construct_with :input_manager, :config_manager,
|
4
|
-
:this_object_context
|
3
|
+
construct_with :input_manager, :config_manager,
|
4
|
+
:stage_factory, :this_object_context
|
5
5
|
|
6
6
|
attr_reader :stage_names, :stage_opts
|
7
7
|
|
@@ -78,13 +78,13 @@ class StageManager
|
|
78
78
|
@stage = stage_name
|
79
79
|
@stage_args = args
|
80
80
|
@stages[@stage] = create_stage(@stage, @stage_opts[@stage_names.index(@stage)])
|
81
|
-
@stages[@stage].
|
81
|
+
@stages[@stage].curtain_up *args
|
82
82
|
end
|
83
83
|
|
84
84
|
def shutdown_current_stage(*args)
|
85
85
|
if @stage and @stages and @stages[@stage]
|
86
86
|
current_stage = @stages[@stage]
|
87
|
-
current_stage.
|
87
|
+
current_stage.curtain_down *args
|
88
88
|
input_manager.clear_hooks(current_stage)
|
89
89
|
@stages.delete @stage
|
90
90
|
@stage = nil
|
@@ -93,13 +93,7 @@ class StageManager
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def create_stage(name, opts)
|
96
|
-
stage_instance =
|
97
|
-
this_object_context.in_subcontext do |stage_context|
|
98
|
-
name_or_klass = opts[:class] || name
|
99
|
-
stage_instance = stage_context["#{name_or_klass}_stage"]
|
100
|
-
stage_context[:stage] = stage_instance
|
101
|
-
end
|
102
|
-
stage_instance.configure(backstage, opts)
|
96
|
+
stage_instance = stage_factory.build(name, opts)
|
103
97
|
|
104
98
|
stage_instance.when :next_stage do |*args|
|
105
99
|
next_stage *args
|
@@ -3,6 +3,7 @@ class TimerManager
|
|
3
3
|
def initialize
|
4
4
|
@timers ||= {}
|
5
5
|
@dead_timers = []
|
6
|
+
@callbacks = []
|
6
7
|
end
|
7
8
|
|
8
9
|
# add block to be executed every interval_ms millis
|
@@ -23,18 +24,21 @@ class TimerManager
|
|
23
24
|
|
24
25
|
# update each timers counts, call any blocks that are over their interval
|
25
26
|
def update(time_delta)
|
26
|
-
|
27
|
+
@callbacks.clear
|
28
|
+
@dead_timers.clear
|
29
|
+
|
27
30
|
@timers.each do |name, timer_hash|
|
28
31
|
timer_hash[:count] += time_delta
|
29
32
|
if timer_hash[:count] > timer_hash[:interval_ms]
|
30
33
|
timer_hash[:count] -= timer_hash[:interval_ms]
|
31
|
-
timer_hash[:callback]
|
34
|
+
@callbacks << timer_hash[:callback]
|
32
35
|
@dead_timers << name unless timer_hash[:recurring]
|
33
36
|
end
|
34
37
|
end
|
35
38
|
@dead_timers.each do |name|
|
36
39
|
remove_timer name
|
37
40
|
end
|
41
|
+
@callbacks.each &:call
|
38
42
|
end
|
39
43
|
|
40
44
|
def pause
|
@@ -3,9 +3,11 @@
|
|
3
3
|
class Viewport
|
4
4
|
extend Publisher
|
5
5
|
can_fire :scrolled
|
6
|
+
|
7
|
+
construct_with :config_manager
|
6
8
|
|
7
9
|
attr_accessor :x_offset, :y_offset, :follow_target, :width,
|
8
|
-
:height, :x_offset_range, :y_offset_range, :boundary
|
10
|
+
:height, :x_offset_range, :y_offset_range, :boundary, :rotation
|
9
11
|
|
10
12
|
attr_reader :speed
|
11
13
|
|
@@ -13,13 +15,18 @@ class Viewport
|
|
13
15
|
"xoff:#{@x_offset} yoff:#{@y_offset}"
|
14
16
|
end
|
15
17
|
|
16
|
-
def initialize
|
18
|
+
def initialize
|
19
|
+
res = config_manager[:screen_resolution]
|
20
|
+
@width = res[0]
|
21
|
+
@height = res[1]
|
22
|
+
reset
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset
|
26
|
+
@rotation = 0
|
17
27
|
@speed = 1
|
18
28
|
@x_offset = 0
|
19
29
|
@y_offset = 0
|
20
|
-
|
21
|
-
@width = width
|
22
|
-
@height = height
|
23
30
|
end
|
24
31
|
|
25
32
|
def scroll(x_delta,y_delta)
|
@@ -4,16 +4,15 @@ class WrappedScreen
|
|
4
4
|
def initialize
|
5
5
|
width, height = *config_manager[:screen_resolution]
|
6
6
|
fullscreen = config_manager[:fullscreen]
|
7
|
-
needs_cursor = config_manager[:needs_cursor]
|
8
7
|
@screen = HookedGosuWindow.new width, height, fullscreen
|
9
8
|
@screen.tap do |screen|
|
10
|
-
screen.caption =
|
11
|
-
screen.needs_cursor =
|
9
|
+
screen.caption = Gamebox.configuration.game_name
|
10
|
+
screen.needs_cursor = Gamebox.configuration.needs_cursor?
|
12
11
|
end
|
13
12
|
end
|
14
13
|
|
15
|
-
def method_missing(name,*args)
|
16
|
-
@screen.send name, *args
|
14
|
+
def method_missing(name,*args, &blk)
|
15
|
+
@screen.send name, *args, &blk
|
17
16
|
end
|
18
17
|
|
19
18
|
def width
|
@@ -129,5 +128,9 @@ class WrappedScreen
|
|
129
128
|
def draw_image(image, x, y, z, x_scale = 1, y_scale = 1, color = 0xffffffff, mode = :default)
|
130
129
|
image.draw x, y, z, x_scale, y_scale, color, mode
|
131
130
|
end
|
131
|
+
|
132
|
+
def draw_rotated_image(image, x, y, z, angle, center_x = 0.5, center_y = 0.5, x_scale = 1, y_scale = 1, color = 0xffffffff, mode = :default)
|
133
|
+
image.draw_rot x, y, z, angle, center_x, center_y, x_scale, y_scale, color, mode
|
134
|
+
end
|
132
135
|
end
|
133
136
|
|
@@ -3,12 +3,13 @@ $: << "#{File.dirname(__FILE__)}/../config"
|
|
3
3
|
|
4
4
|
begin
|
5
5
|
# optional file
|
6
|
-
require "environment"
|
6
|
+
require "environment"
|
7
7
|
rescue LoadError => err
|
8
8
|
end
|
9
9
|
|
10
10
|
class GameboxApp
|
11
11
|
attr_reader :context, :game
|
12
|
+
|
12
13
|
def self.run(argv,env)
|
13
14
|
GameboxApp.new.start argv, env
|
14
15
|
end
|
@@ -17,19 +18,16 @@ class GameboxApp
|
|
17
18
|
@context = Conject.default_object_context
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
setup_debug_server if @config_manager[:debug_server] || ARGV.include?("-debug-server")
|
21
|
+
def start(argv,env)
|
22
|
+
setup(argv,env)
|
23
|
+
main_loop
|
24
|
+
shutdown
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
32
|
-
end
|
27
|
+
def setup(argv,env)
|
28
|
+
@game = @context[:game]
|
29
|
+
@game.configure
|
30
|
+
self.class.post_setup_handlers.each { |handler| handler.setup(argv, env, @context[:config_manager]) }
|
33
31
|
end
|
34
32
|
|
35
33
|
def main_loop
|
@@ -38,18 +36,22 @@ class GameboxApp
|
|
38
36
|
@input_manager.show
|
39
37
|
end
|
40
38
|
|
41
|
-
def shutdown
|
42
|
-
end
|
43
|
-
|
44
|
-
def start(argv,env)
|
45
|
-
setup
|
39
|
+
def shutdown ; end
|
46
40
|
|
47
|
-
|
41
|
+
def self.register_post_setup_handler(handler)
|
42
|
+
post_setup_handlers.push handler
|
43
|
+
end
|
48
44
|
|
49
|
-
|
45
|
+
def self.post_setup_handlers
|
46
|
+
@post_setup_handlers ||= [ ]
|
50
47
|
end
|
48
|
+
|
51
49
|
end
|
52
50
|
|
51
|
+
GameboxApp.register_post_setup_handler PostSetupHandlers::FileWatcher
|
52
|
+
GameboxApp.register_post_setup_handler PostSetupHandlers::GameboxAppAddDebugHelpers
|
53
|
+
GameboxApp.register_post_setup_handler PostSetupHandlers::PryRemoteServer
|
54
|
+
|
53
55
|
if $0 == __FILE__
|
54
56
|
GameboxApp.run ARGV, ENV
|
55
57
|
end
|
@@ -1,5 +1,29 @@
|
|
1
1
|
module ObservableAttributes
|
2
2
|
|
3
|
+
# atomically update all attributes
|
4
|
+
# it updates all of the values; then triggers the events
|
5
|
+
# my_actor.update_attributes(x: 5, y: 7)
|
6
|
+
# will emit both x_changed and y_changed after setting _both_ values
|
7
|
+
def update_attributes(attributes)
|
8
|
+
# TODO put this in kvo gem; there is too much internal knowlege of where
|
9
|
+
# kvo stores stuff
|
10
|
+
#
|
11
|
+
# self.kvo_set('x', new_val, silent: true)
|
12
|
+
# self.changed('x', old_val, new_val)
|
13
|
+
old_values = attributes.keys.inject({}) do |hash, attr_name|
|
14
|
+
hash[attr_name] = self.send(attr_name)
|
15
|
+
hash
|
16
|
+
end
|
17
|
+
|
18
|
+
attributes.each do |name, val|
|
19
|
+
self.instance_variable_set("@kvo_#{name}", val)
|
20
|
+
end
|
21
|
+
|
22
|
+
attributes.each do |name, val|
|
23
|
+
fire "#{name}_changed".to_sym, old_values[name], val
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
3
27
|
def has_attributes(*names)
|
4
28
|
if names.first.is_a? Hash
|
5
29
|
names.first.each do |name, default|
|
@@ -0,0 +1,432 @@
|
|
1
|
+
#++
|
2
|
+
# Copyright (C) 2008-2010 John Croisant
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights to
|
7
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
8
|
+
# of the Software, and to permit persons to whom the Software is furnished to do
|
9
|
+
# so, subject to the following conditions: #
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software. #
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
20
|
+
# SOFTWARE.
|
21
|
+
|
22
|
+
# The Vector2 class implements two-dimensional vectors.
|
23
|
+
# It is used to represent positions, movements, and velocities
|
24
|
+
# in 2D space.
|
25
|
+
#
|
26
|
+
class Vector2
|
27
|
+
include Enumerable
|
28
|
+
|
29
|
+
RAD_TO_DEG = 180.0 / Math::PI
|
30
|
+
DEG_TO_RAD = Math::PI / 180.0
|
31
|
+
|
32
|
+
class << self
|
33
|
+
|
34
|
+
alias :[] :new
|
35
|
+
|
36
|
+
|
37
|
+
# Creates a new Vector2 from an angle in radians and a
|
38
|
+
# magnitude. Use #new_polar_deg for degrees.
|
39
|
+
#
|
40
|
+
def new_polar( angle_rad, magnitude )
|
41
|
+
self.new( Math::cos(angle_rad)*magnitude,
|
42
|
+
Math::sin(angle_rad)*magnitude )
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Creates a new Vector2 from an angle in degrees and a
|
47
|
+
# magnitude. Use #new_polar for radians.
|
48
|
+
#
|
49
|
+
def new_polar_deg( angle_deg, magnitude )
|
50
|
+
self.new_polar( angle_deg * DEG_TO_RAD, magnitude )
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# call-seq:
|
55
|
+
# Vector2.many( [x1,y1], [x2,y2], ... )
|
56
|
+
#
|
57
|
+
# Converts multiple [x,y] Arrays to Vector2s.
|
58
|
+
# Returns the resulting vectors in an Array.
|
59
|
+
#
|
60
|
+
def many( *pairs )
|
61
|
+
pairs.collect { |pair| self.new(*pair) }
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Creates a new Vector2 with the given x and y values.
|
68
|
+
def initialize( x, y )
|
69
|
+
@x, @y = x.to_f, y.to_f
|
70
|
+
end
|
71
|
+
|
72
|
+
attr_reader :x, :y
|
73
|
+
|
74
|
+
def x=( value )
|
75
|
+
raise "can't modify frozen object" if frozen?
|
76
|
+
@x = value.to_f
|
77
|
+
@hash = nil
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def y=( value )
|
82
|
+
raise "can't modify frozen object" if frozen?
|
83
|
+
@y = value.to_f
|
84
|
+
@hash = nil
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# Sets this vector's x and y components.
|
90
|
+
def set!( x, y )
|
91
|
+
raise "can't modify frozen object" if frozen?
|
92
|
+
@x = x.to_f
|
93
|
+
@y = y.to_f
|
94
|
+
@hash = nil
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Sets this vector's angle (in radians) and magnitude.
|
100
|
+
# Use #set_polar_deg! for degrees.
|
101
|
+
#
|
102
|
+
def set_polar!( angle_rad, mag )
|
103
|
+
raise "can't modify frozen object" if frozen?
|
104
|
+
@x = Math::cos(angle_rad) * mag
|
105
|
+
@y = Math::sin(angle_rad) * mag
|
106
|
+
@hash = nil
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
# Sets this vector's angle (in degrees) and magnitude.
|
112
|
+
# Use #set_polar! for radians.
|
113
|
+
#
|
114
|
+
def set_polar_deg!( angle_deg, mag )
|
115
|
+
set_polar!( angle_deg * DEG_TO_RAD, mag )
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# Adds the given vector to this one and return the
|
121
|
+
# resulting vector.
|
122
|
+
#
|
123
|
+
def +( vector )
|
124
|
+
self.class.new( @x + vector.at(0), @y + vector.at(1) )
|
125
|
+
end
|
126
|
+
|
127
|
+
# call-seq:
|
128
|
+
# move!( [x,y] )
|
129
|
+
# move!( x,y )
|
130
|
+
#
|
131
|
+
# Moves the vector by the given x and y amounts.
|
132
|
+
def move!( x, y=nil )
|
133
|
+
raise "can't modify frozen object" if frozen?
|
134
|
+
if y.nil?
|
135
|
+
a = x.to_ary
|
136
|
+
@x += a[0]
|
137
|
+
@y += a[1]
|
138
|
+
else
|
139
|
+
@x += x
|
140
|
+
@y += y
|
141
|
+
end
|
142
|
+
@hash = nil
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
# call-seq:
|
147
|
+
# move( [x,y] )
|
148
|
+
# move( x,y )
|
149
|
+
#
|
150
|
+
# Like #move!, but returns a new vector.
|
151
|
+
def move( x, y=nil )
|
152
|
+
self.dup.move!(x, y)
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
# Subtracts the given vector from this one and return
|
157
|
+
# the resulting vector.
|
158
|
+
#
|
159
|
+
def -( vector )
|
160
|
+
self.class.new( @x - vector.at(0), @y - vector.at(1) )
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# Returns the opposite of this vector, i.e. Vector2[-x, -y].
|
165
|
+
def -@
|
166
|
+
self.class.new( -@x, -@y )
|
167
|
+
end
|
168
|
+
|
169
|
+
# Reverses the vector's direction, i.e. Vector2[-x, -y].
|
170
|
+
def reverse!
|
171
|
+
raise "can't modify frozen object" if frozen?
|
172
|
+
@x, @y = -@x, -@y
|
173
|
+
@hash = nil
|
174
|
+
self
|
175
|
+
end
|
176
|
+
|
177
|
+
# Like #reverse!, but returns a new vector.
|
178
|
+
def reverse
|
179
|
+
self.dup.reverse!
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
# Multiplies this vector by the given scalar (Numeric),
|
184
|
+
# and return the resulting vector.
|
185
|
+
#
|
186
|
+
def *( scalar )
|
187
|
+
self.class.new( @x * scalar, @y * scalar )
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
# True if the given vector's x and y components are
|
192
|
+
# equal to this vector's components (within a small margin
|
193
|
+
# of error to compensate for floating point imprecision).
|
194
|
+
#
|
195
|
+
def ==( vector )
|
196
|
+
_nearly_equal?(@x, vector.at(0)) and _nearly_equal?(@y, vector.at(1))
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
# Returns a component of this vector as if it were an
|
201
|
+
# [x,y] Array.
|
202
|
+
#
|
203
|
+
def []( index )
|
204
|
+
[@x, @y][index]
|
205
|
+
end
|
206
|
+
|
207
|
+
alias :at :[]
|
208
|
+
|
209
|
+
|
210
|
+
def hash # :nodoc:
|
211
|
+
@hash ||= (((@x - @x.modulo(1E-10)).hash << 2) +
|
212
|
+
((@y - @y.modulo(1E-10)).hash << 1) +
|
213
|
+
self.class.hash)
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
# Iterates over this vector as if it were an [x,y] Array.
|
218
|
+
#
|
219
|
+
def each( &block )
|
220
|
+
[@x, @y].each( &block )
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
# Returns the angle of this vector, relative to the positive
|
225
|
+
# X axis, in radians. Use #angle_deg for degrees.
|
226
|
+
#
|
227
|
+
def angle
|
228
|
+
Math.atan2( @y, @x )
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
# Sets the vector's angle in radians. The vector keeps the same
|
233
|
+
# magnitude as before.
|
234
|
+
#
|
235
|
+
def angle=( angle_rad )
|
236
|
+
raise "can't modify frozen object" if frozen?
|
237
|
+
m = magnitude
|
238
|
+
@x = Math::cos(angle_rad) * m
|
239
|
+
@y = Math::sin(angle_rad) * m
|
240
|
+
@hash = nil
|
241
|
+
self
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
# Returns the angle of this vector relative to the other vector,
|
246
|
+
# in radians. Use #angle_deg_with for degrees.
|
247
|
+
#
|
248
|
+
def angle_with( vector )
|
249
|
+
Math.acos( udot(vector) )
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
# Returns the angle of this vector, relative to the positive
|
254
|
+
# X axis, in degrees. Use #angle for radians.
|
255
|
+
#
|
256
|
+
def angle_deg
|
257
|
+
angle * RAD_TO_DEG
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
# Sets the vector's angle in degrees. The vector keeps the same
|
262
|
+
# magnitude as before.
|
263
|
+
#
|
264
|
+
def angle_deg=( angle_deg )
|
265
|
+
self.angle = angle_deg * DEG_TO_RAD
|
266
|
+
self
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
# Returns the angle of this vector relative to the other vector,
|
271
|
+
# in degrees. Use #angle_with for radians.
|
272
|
+
#
|
273
|
+
def angle_deg_with( vector )
|
274
|
+
angle_with(vector) * RAD_TO_DEG
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
# Returns the dot product between this vector and the other vector.
|
279
|
+
def dot( vector )
|
280
|
+
(@x * vector.at(0)) + (@y * vector.at(1))
|
281
|
+
end
|
282
|
+
|
283
|
+
|
284
|
+
# Returns the magnitude (distance) of this vector.
|
285
|
+
def magnitude
|
286
|
+
Math.hypot( @x, @y )
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
# Sets the vector's magnitude (distance). The vector keeps the
|
291
|
+
# same angle as before.
|
292
|
+
#
|
293
|
+
def magnitude=( mag )
|
294
|
+
raise "can't modify frozen object" if frozen?
|
295
|
+
angle_rad = angle
|
296
|
+
@x = Math::cos(angle_rad) * mag
|
297
|
+
@y = Math::sin(angle_rad) * mag
|
298
|
+
@hash = nil
|
299
|
+
self
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
# Returns a copy of this vector, but rotated 90 degrees
|
304
|
+
# counter-clockwise.
|
305
|
+
#
|
306
|
+
def perp
|
307
|
+
self.class.new( -@y, @x )
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
# Sets this vector to the vector projection (aka vector resolute)
|
312
|
+
# of this vector onto the other vector. See also #projected_onto.
|
313
|
+
#
|
314
|
+
def project_onto!( vector )
|
315
|
+
raise "can't modify frozen object" if frozen?
|
316
|
+
@x, @y = *(vector * vector.dot(self) * (1/vector.magnitude**2))
|
317
|
+
@hash = nil
|
318
|
+
self
|
319
|
+
end
|
320
|
+
|
321
|
+
# Like #project_onto!, but returns a new vector.
|
322
|
+
def projected_onto( vector )
|
323
|
+
dup.project_onto!( vector )
|
324
|
+
end
|
325
|
+
|
326
|
+
|
327
|
+
# Rotates the vector the given number of radians.
|
328
|
+
# Use #rotate_deg! for degrees.
|
329
|
+
#
|
330
|
+
def rotate!( angle_rad )
|
331
|
+
self.angle += angle_rad
|
332
|
+
self
|
333
|
+
end
|
334
|
+
|
335
|
+
# Like #rotate!, but returns a new vector.
|
336
|
+
def rotate( angle_rad )
|
337
|
+
dup.rotate!( angle_rad )
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
# Rotates the vector the given number of degrees.
|
342
|
+
# Use #rotate for radians.
|
343
|
+
#
|
344
|
+
def rotate_deg!( angle_deg )
|
345
|
+
self.angle_deg += angle_deg
|
346
|
+
self
|
347
|
+
end
|
348
|
+
|
349
|
+
# Like #rotate_deg!, but returns a new vector.
|
350
|
+
def rotate_deg( angle_deg )
|
351
|
+
dup.rotate_deg!( angle_deg )
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
# call-seq:
|
356
|
+
# scale!( scale )
|
357
|
+
# scale!( scale_x, scale_y )
|
358
|
+
#
|
359
|
+
# Multiplies this vector's x and y values.
|
360
|
+
#
|
361
|
+
# If one number is given, the vector will be equal to
|
362
|
+
# Vector2[x*scale, y*scale]. If two numbers are given, it will be
|
363
|
+
# equal to Vector2[x*scale_x, y*scale_y].
|
364
|
+
#
|
365
|
+
# Example:
|
366
|
+
#
|
367
|
+
# v = Vector2[1.5,2.5]
|
368
|
+
# v.scale!( 2 ) # => Vector2[3,5]
|
369
|
+
# v.scale!( 3, 4 ) # => Vector2[9,20]
|
370
|
+
#
|
371
|
+
def scale!( scale_x, scale_y = scale_x )
|
372
|
+
raise "can't modify frozen object" if frozen?
|
373
|
+
@x, @y = @x * scale_x, @y * scale_y
|
374
|
+
@hash = nil
|
375
|
+
self
|
376
|
+
end
|
377
|
+
|
378
|
+
# Like #scale!, but returns a new vector.
|
379
|
+
def scale( scale_x, scale_y = scale_x )
|
380
|
+
dup.scale!(scale_x, scale_y)
|
381
|
+
end
|
382
|
+
|
383
|
+
|
384
|
+
# Returns this vector as an [x,y] Array.
|
385
|
+
def to_ary
|
386
|
+
[@x, @y]
|
387
|
+
end
|
388
|
+
|
389
|
+
alias :to_a :to_ary
|
390
|
+
|
391
|
+
|
392
|
+
def to_s
|
393
|
+
"Vector2[#{@x}, #{@y}]"
|
394
|
+
end
|
395
|
+
|
396
|
+
alias :inspect :to_s
|
397
|
+
|
398
|
+
|
399
|
+
# Returns the dot product of this vector's #unit and the other
|
400
|
+
# vector's #unit.
|
401
|
+
#
|
402
|
+
def udot( vector )
|
403
|
+
unit.dot( vector.unit )
|
404
|
+
end
|
405
|
+
|
406
|
+
|
407
|
+
# Sets this vector's magnitude to 1.
|
408
|
+
def unit!
|
409
|
+
raise "can't modify frozen object" if frozen?
|
410
|
+
scale = 1/magnitude
|
411
|
+
@x, @y = @x * scale, @y * scale
|
412
|
+
@hash = nil
|
413
|
+
self
|
414
|
+
end
|
415
|
+
|
416
|
+
alias :normalize! :unit!
|
417
|
+
|
418
|
+
# Like #unit!, but returns a new vector.
|
419
|
+
def unit
|
420
|
+
self.dup.unit!
|
421
|
+
end
|
422
|
+
|
423
|
+
alias :normalized :unit
|
424
|
+
|
425
|
+
|
426
|
+
private
|
427
|
+
|
428
|
+
def _nearly_equal?( a, b, threshold=1E-10 ) # :nodoc:
|
429
|
+
(a - b).abs <= threshold
|
430
|
+
end
|
431
|
+
|
432
|
+
end
|