gamebox 0.4.0.rc5 → 0.4.0.rc11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/README.md +205 -127
  2. data/bin/gamebox +49 -3
  3. data/bin/gb +87 -0
  4. data/gamebox.gemspec +4 -3
  5. data/lib/gamebox.rb +1 -1
  6. data/lib/gamebox/actors/collidable_debugger.rb +4 -4
  7. data/lib/gamebox/actors/icon.rb +7 -0
  8. data/lib/gamebox/actors/label.rb +41 -42
  9. data/lib/gamebox/behaviors/animated.rb +6 -0
  10. data/lib/gamebox/behaviors/audible.rb +1 -2
  11. data/lib/gamebox/behaviors/collidable.rb +1 -1
  12. data/lib/gamebox/behaviors/graphical.rb +8 -4
  13. data/lib/gamebox/behaviors/physical.rb +6 -1
  14. data/lib/gamebox/behaviors/positioned.rb +4 -11
  15. data/lib/gamebox/behaviors/projectile.rb +8 -0
  16. data/lib/gamebox/behaviors/visible.rb +3 -3
  17. data/lib/gamebox/core/aabb_tree.rb +1 -1
  18. data/lib/gamebox/core/actor.rb +37 -50
  19. data/lib/gamebox/core/actor_definition.rb +41 -0
  20. data/lib/gamebox/core/actor_view.rb +6 -21
  21. data/lib/gamebox/core/actor_view_definition.rb +19 -0
  22. data/lib/gamebox/core/actor_view_factory.rb +9 -3
  23. data/lib/gamebox/core/behavior.rb +8 -27
  24. data/lib/gamebox/core/behavior_definition.rb +24 -0
  25. data/lib/gamebox/core/config_manager.rb +45 -30
  26. data/lib/gamebox/core/configuration.rb +5 -0
  27. data/lib/gamebox/core/core.rb +4 -0
  28. data/lib/gamebox/core/debug_helpers.rb +46 -0
  29. data/lib/gamebox/core/director.rb +32 -5
  30. data/lib/gamebox/core/event_symbols.rb +214 -0
  31. data/lib/gamebox/core/game.rb +1 -1
  32. data/lib/gamebox/core/input_manager.rb +1 -4
  33. data/lib/gamebox/core/input_mapper.rb +85 -0
  34. data/lib/gamebox/core/physics.rb +7 -3
  35. data/lib/gamebox/core/physics_manager.rb +5 -1
  36. data/lib/gamebox/core/renderer.rb +72 -0
  37. data/lib/gamebox/core/stage.rb +25 -81
  38. data/lib/gamebox/core/stage_definition.rb +60 -0
  39. data/lib/gamebox/core/stage_factory.rb +56 -0
  40. data/lib/gamebox/core/stage_manager.rb +5 -11
  41. data/lib/gamebox/core/timer_manager.rb +6 -2
  42. data/lib/gamebox/core/viewport.rb +12 -5
  43. data/lib/gamebox/core/wrapped_screen.rb +8 -5
  44. data/lib/gamebox/gamebox_application.rb +21 -19
  45. data/lib/gamebox/lib/array_ext.rb +9 -0
  46. data/lib/gamebox/lib/observable_attributes.rb +24 -0
  47. data/lib/gamebox/lib/vector2.rb +432 -0
  48. data/lib/gamebox/post_setup_handlers/file_watcher.rb +37 -0
  49. data/lib/gamebox/post_setup_handlers/gamebox_debug_helpers.rb +13 -0
  50. data/lib/gamebox/post_setup_handlers/pry_remote_server.rb +29 -0
  51. data/lib/gamebox/spec/helper.rb +165 -17
  52. data/lib/gamebox/tasks/gamebox_tasks.rake +27 -12
  53. data/lib/gamebox/version.rb +1 -1
  54. data/lib/gamebox/views/graphical_actor_view.rb +4 -5
  55. data/script/perf_aabb.rb +13 -8
  56. data/spec/acceptance/animation_spec.rb +1 -3
  57. data/spec/acceptance/basic_actor_lifecycle_spec.rb +1 -1
  58. data/spec/acceptance/fps_actor_spec.rb +8 -12
  59. data/spec/acceptance/input_mapper_spec.rb +17 -24
  60. data/spec/acceptance/update_ordering_spec.rb +64 -0
  61. data/spec/actors/label_spec.rb +90 -5
  62. data/spec/behaviors/animated_spec.rb +1 -1
  63. data/spec/behaviors/collidable_spec.rb +7 -15
  64. data/spec/behaviors/positioned_spec.rb +12 -5
  65. data/spec/core/actor_spec.rb +31 -3
  66. data/spec/core/actor_view_spec.rb +1 -1
  67. data/spec/core/behavior_spec.rb +3 -0
  68. data/spec/core/configuration_spec.rb +49 -2
  69. data/spec/core/input_mapper_spec.rb +7 -0
  70. data/spec/core/renderer_spec.rb +89 -0
  71. data/spec/core/stage_definition_spec.rb +41 -0
  72. data/spec/core/stage_manager_spec.rb +11 -11
  73. data/spec/core/stage_spec.rb +38 -78
  74. data/spec/core/viewport_spec.rb +5 -2
  75. data/spec/core/wrapped_screen_spec.rb +18 -12
  76. data/spec/views/graphical_actor_view_spec.rb +33 -62
  77. data/templates/actor_template.erb +11 -0
  78. data/templates/app/README.md +1 -0
  79. data/templates/app/src/actors/{player.rb → player_actor.rb} +3 -1
  80. data/templates/app/src/behaviors/.gitkeep +0 -0
  81. data/templates/app/src/stages/demo_stage.rb +14 -0
  82. data/templates/behavior_template.erb +13 -0
  83. data/templates/stage_template.erb +13 -0
  84. metadata +60 -21
  85. data/component_generators/actor_generator.rb +0 -17
  86. data/lib/gamebox/actors/emitter.rb +0 -12
  87. data/lib/gamebox/behaviors/emitting.rb +0 -48
  88. data/lib/gamebox/behaviors/input_mapper.rb +0 -11
  89. data/lib/gamebox/lib/ftor.rb +0 -372
  90. data/spec/actors/emitter_spec.rb +0 -5
  91. data/templates/app/NEXT_STEPS.txt +0 -1
  92. data/templates/app/README.rdoc +0 -24
  93. 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, :backstage,
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].curtain_raising *args
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.curtain_dropping *args
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 = nil
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
- # TODO handle overwriting the same timer name...
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].call
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(width, height)
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 = config_manager[:title]
11
- screen.needs_cursor = config_manager[: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 setup
21
- @game = @context[:game]
22
- @game.configure
23
- @config_manager = @context[:config_manager]
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 setup_debug_server
28
- Thread.new do
29
- loop do
30
- binding.remote_pry
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
- main_loop
41
+ def self.register_post_setup_handler(handler)
42
+ post_setup_handlers.push handler
43
+ end
48
44
 
49
- shutdown
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
@@ -0,0 +1,9 @@
1
+ class Array
2
+ def self.wrap(thing)
3
+ if thing.is_a? Array
4
+ thing
5
+ else
6
+ [thing]
7
+ end
8
+ end
9
+ 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