gosling 2.3.0 → 2.3.2

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/lib/gosling/rect.rb CHANGED
@@ -1,41 +1,41 @@
1
- require_relative 'polygon.rb'
2
-
3
- module Gosling
4
- ##
5
- # A Rect is a Polygon with exactly four vertices, defined by a width and height, with sides at right angles to one
6
- # another. The width and height can be modified at runtime; all vertices will be updated automatically.
7
- #
8
- class Rect < Polygon
9
- attr_reader :width, :height
10
-
11
- ##
12
- # Creates a new Rect with a width and height of 1.
13
- #
14
- def initialize(window)
15
- super(window)
16
- @width = 1
17
- @height = 1
18
- rebuild_vertices
19
- end
20
-
21
- def width=(val)
22
- raise ArgumentError.new("set_width() expects a positive, non-zero number") if val <= 0
23
- @width = val
24
- rebuild_vertices
25
- end
26
-
27
- def height=(val)
28
- raise ArgumentError.new("set_height() expects a positive, non-zero number") if val <= 0
29
- @height = val
30
- rebuild_vertices
31
- end
32
-
33
- private
34
-
35
- def rebuild_vertices
36
- set_vertices_rect(@width, @height)
37
- end
38
-
39
- private :set_vertices
40
- end
41
- end
1
+ require_relative 'polygon.rb'
2
+
3
+ module Gosling
4
+ ##
5
+ # A Rect is a Polygon with exactly four vertices, defined by a width and height, with sides at right angles to one
6
+ # another. The width and height can be modified at runtime; all vertices will be updated automatically.
7
+ #
8
+ class Rect < Polygon
9
+ attr_reader :width, :height
10
+
11
+ ##
12
+ # Creates a new Rect with a width and height of 1.
13
+ #
14
+ def initialize(window)
15
+ super(window)
16
+ @width = 1
17
+ @height = 1
18
+ rebuild_vertices
19
+ end
20
+
21
+ def width=(val)
22
+ raise ArgumentError.new("set_width() expects a positive, non-zero number") if val <= 0
23
+ @width = val
24
+ rebuild_vertices
25
+ end
26
+
27
+ def height=(val)
28
+ raise ArgumentError.new("set_height() expects a positive, non-zero number") if val <= 0
29
+ @height = val
30
+ rebuild_vertices
31
+ end
32
+
33
+ private
34
+
35
+ def rebuild_vertices
36
+ set_vertices_rect(@width, @height)
37
+ end
38
+
39
+ private :set_vertices
40
+ end
41
+ end
@@ -1,50 +1,50 @@
1
- require_relative 'rect.rb'
2
-
3
- module Gosling
4
- ##
5
- # This type of Actor accepts a Gosu::Image to be rendered in place of the standard flat-colored shape. It behaves very
6
- # much like a Rect, except its width and height are automatically set to the width and height of the image given to it
7
- # and cannot be modified otherwise. The image can be changed at runtime. Changing this actor's color or alpha
8
- # applies tinting and transparency to the image rendered.
9
- #
10
- class Sprite < Rect
11
- def initialize(window)
12
- super(window)
13
- @image = nil
14
- @color = Gosu::Color.rgba(255, 255, 255, 255)
15
- end
16
-
17
- ##
18
- # Returns the image currently assigned to this Sprite.
19
- #
20
- def get_image
21
- @image
22
- end
23
-
24
- ##
25
- # Assigns the image to this Sprite, setting its width and height to match the image's.
26
- #
27
- def set_image(image)
28
- raise ArgumentError.new("Expected Image, but received #{image.inspect}!") unless image.is_a?(Gosu::Image)
29
- @image = image
30
- self.width = @image.width
31
- self.height = @image.height
32
- end
33
-
34
- private
35
-
36
- def render(matrix)
37
- # TODO: write transformed vertices to a reserved list of vertices retained in memory each time
38
- global_vertices = @vertices.map { |v| Transformable.transform_point(matrix, v, Snow::Vec3.new) }
39
- @image.draw_as_quad(
40
- global_vertices[0][0].to_f, global_vertices[0][1].to_f, @color,
41
- global_vertices[1][0].to_f, global_vertices[1][1].to_f, @color,
42
- global_vertices[2][0].to_f, global_vertices[2][1].to_f, @color,
43
- global_vertices[3][0].to_f, global_vertices[3][1].to_f, @color,
44
- 0
45
- )
46
- end
47
-
48
- private :'width=', :'height='
49
- end
50
- end
1
+ require_relative 'rect.rb'
2
+
3
+ module Gosling
4
+ ##
5
+ # This type of Actor accepts a Gosu::Image to be rendered in place of the standard flat-colored shape. It behaves very
6
+ # much like a Rect, except its width and height are automatically set to the width and height of the image given to it
7
+ # and cannot be modified otherwise. The image can be changed at runtime. Changing this actor's color or alpha
8
+ # applies tinting and transparency to the image rendered.
9
+ #
10
+ class Sprite < Rect
11
+ def initialize(window)
12
+ super(window)
13
+ @image = nil
14
+ @color = Gosu::Color.rgba(255, 255, 255, 255)
15
+ end
16
+
17
+ ##
18
+ # Returns the image currently assigned to this Sprite.
19
+ #
20
+ def get_image
21
+ @image
22
+ end
23
+
24
+ ##
25
+ # Assigns the image to this Sprite, setting its width and height to match the image's.
26
+ #
27
+ def set_image(image)
28
+ raise ArgumentError.new("Expected Image, but received #{image.inspect}!") unless image.is_a?(Gosu::Image)
29
+ @image = image
30
+ self.width = @image.width
31
+ self.height = @image.height
32
+ end
33
+
34
+ private
35
+
36
+ def render(matrix)
37
+ # TODO: write transformed vertices to a reserved list of vertices retained in memory each time
38
+ global_vertices = @vertices.map { |v| Transformable.transform_point(matrix, v, Snow::Vec3.new) }
39
+ @image.draw_as_quad(
40
+ global_vertices[0][0].to_f, global_vertices[0][1].to_f, @color,
41
+ global_vertices[1][0].to_f, global_vertices[1][1].to_f, @color,
42
+ global_vertices[2][0].to_f, global_vertices[2][1].to_f, @color,
43
+ global_vertices[3][0].to_f, global_vertices[3][1].to_f, @color,
44
+ 0
45
+ )
46
+ end
47
+
48
+ private :'width=', :'height='
49
+ end
50
+ end
@@ -1,413 +1,413 @@
1
- require 'snow-math'
2
-
3
- require_relative 'patches.rb'
4
- require_relative 'utils.rb'
5
-
6
- module Gosling
7
- ##
8
- # A helper class for performing vector transforms in 2D space. Relies heavily on the Vec3 and Mat3 classes of the
9
- # SnowMath gem to remain performant.
10
- #
11
- module Transformable
12
- attr_reader :rotation
13
-
14
- ##
15
- # Initializes this Transformable to have no transformations (identity matrix).
16
- #
17
- def initialize
18
- @center = Snow::Vec3[0, 0, 1]
19
- @scale = Snow::Vec2[1, 1]
20
- @translation = Snow::Vec3[0, 0, 1]
21
- reset
22
- end
23
-
24
- ##
25
- # Resets center and translation to [0, 0], scale to [1, 1], and rotation to 0, restoring this transformation to the identity
26
- # matrix.
27
- #
28
- def reset
29
- self.center = 0, 0
30
- self.scale = 1, 1
31
- self.rotation = 0
32
- self.translation = 0, 0
33
- end
34
-
35
- ##
36
- # Returns a duplicate of the center Vec3 (@center is read-only).
37
- #
38
- def center
39
- @center.dup.freeze
40
- end
41
-
42
- ##
43
- # Returns the x component of the centerpoint of this Transformable. See Transformable#center.
44
- #
45
- def center_x
46
- @center[0]
47
- end
48
-
49
- ##
50
- # Returns the y component of the centerpoint of this Transformable. See Transformable#center.
51
- #
52
- def center_y
53
- @center[1]
54
- end
55
-
56
- ##
57
- # Returns a duplicate of the scale Vec2 (@scale is read-only).
58
- #
59
- def scale
60
- @scale.dup.freeze
61
- end
62
-
63
- ##
64
- # Returns the x component of the scaling of this Transformable. See Transformable#scale.
65
- #
66
- def scale_x
67
- @scale[0]
68
- end
69
-
70
- ##
71
- # Returns the y component of the scaling of this Transformable. See Transformable#scale.
72
- #
73
- def scale_y
74
- @scale[1]
75
- end
76
-
77
- ##
78
- # Returns a duplicate of the translation Vec3 (@translation is read-only).
79
- #
80
- def translation
81
- @translation.dup.freeze
82
- end
83
- alias :pos :translation
84
-
85
- ##
86
- # Returns this Transformable's x position in relative space. See Transformable#translation.
87
- #
88
- def x
89
- @translation[0]
90
- end
91
-
92
- ##
93
- # Returns this Transformable's y position in relative space. See Transformable#translation.
94
- #
95
- def y
96
- @translation[1]
97
- end
98
-
99
- ##
100
- # Sets this transform's centerpoint. All other transforms are performed relative to this central point.
101
- #
102
- # The default centerpoint is [0, 0], which is the same as no transform. For a square defined by the vertices
103
- # [[0, 0], [10, 0], [10, 10], [0, 10]], this would translate to that square's upper left corner. In this case, when scaled
104
- # larger or smaller, only the square's right and bottom edges would expand or contract, and when rotated it
105
- # would spin around its upper left corner. For most applications, this is probably not what we want.
106
- #
107
- # By setting the centerpoint to something other than the origin, we can change the scaling and rotation to
108
- # something that makes more sense. For the square defined above, we could set center to be the actual center of
109
- # the square: [5, 5]. By doing so, scaling the square would cause it to expand evenly on all sides, and rotating it
110
- # would cause it to spin about its center like a four-cornered wheel.
111
- #
112
- # You can set the centerpoint to be any point in local space, inside or even outside of the shape in question.
113
- #
114
- # If passed more than two numeric arguments, only the first two are used.
115
- #
116
- # Usage:
117
- # - transform.center = x, y
118
- # - transform.center = [x, y]
119
- # - transform.center = Snow::Vec2[x, y]
120
- # - transform.center = Snow::Vec3[x, y, z]
121
- # - transform.center = Snow::Vec4[x, y, z, c]
122
- # - transform.set_center { |c| c.add(-sprite.pos, c) }
123
- #
124
- def center=(args = nil)
125
- if block_given?
126
- yield(@center)
127
- else
128
- case args[0]
129
- when Array
130
- self.center = args[0][0], args[0][1]
131
- when Snow::Vec2, Snow::Vec3, Snow::Vec4
132
- @center[0] = args[0][0]
133
- @center[1] = args[0][1]
134
- when Numeric
135
- raise ArgumentError.new("Cannot set center from #{args.inspect}: numeric array requires at least two arguments!") unless args.length >= 2
136
- args.each { |arg| type_check(arg, Numeric) }
137
- @center[0] = args[0]
138
- @center[1] = args[1]
139
- else
140
- raise ArgumentError.new("Cannot set center from #{args.inspect}: bad type!")
141
- end
142
- end
143
- @center_is_dirty = @is_dirty = true
144
- end
145
- alias :set_center :center=
146
-
147
- ##
148
- # Sets the x component of the centerpoint of this Transformable. See Transformable#center.
149
- #
150
- def center_x=(val)
151
- type_check(val, Numeric)
152
- @center[0] = val
153
- @center_is_dirty = @is_dirty = true
154
- end
155
-
156
- ##
157
- # Sets the y component of the centerpoint of this Transformable. See Transformable#center.
158
- #
159
- def center_y=(val)
160
- type_check(val, Numeric)
161
- @center[1] = val
162
- @center_is_dirty = @is_dirty = true
163
- end
164
-
165
- ##
166
- # Sets this transform's x/y scaling. A scale value of [1, 1] results in no scaling. Larger values make a shape bigger,
167
- # while smaller values will make it smaller. Great for shrinking/growing animations, or to zoom the camera in/out.
168
- #
169
- # If passed more than two numeric arguments, only the first two are used.
170
- #
171
- # Usage:
172
- # - transform.scale = scalar
173
- # - transform.scale = x, y
174
- # - transform.scale = [x, y]
175
- # - transform.scale = Snow::Vec2[x, y]
176
- # - transform.scale = Snow::Vec3[x, y, z]
177
- # - transform.scale = Snow::Vec4[x, y, z, c]
178
- # - transform.set_scale { |s| s.multiply(0.5, s) }
179
- #
180
- def scale=(*args)
181
- if block_given?
182
- yield(@scale)
183
- else
184
- if args.length >= 2
185
- case args[0]
186
- when Numeric
187
- args.each { |arg| type_check(arg, Numeric) }
188
- @scale[0] = args[0]
189
- @scale[1] = args[1]
190
- else
191
- raise ArgumentError.new("Bad combination of arguments: #{args.inspect}! Please supply a Snow::Vec2, an Array of Numerics, or a single scalar.")
192
- end
193
- elsif args.length == 1
194
- case args[0]
195
- when Array
196
- self.set_scale(*(args[0]))
197
- when Snow::Vec2
198
- self.set_scale(args[0][0], args[0][1])
199
- when Numeric
200
- self.set_scale(args[0], args[0])
201
- else
202
- raise ArgumentError.new("Bad combination of arguments: #{args.inspect}! Please supply a Snow::Vec2, an Array of Numerics, or a single scalar.")
203
- end
204
- else
205
- raise ArgumentError.new("Bad combination of arguments: #{args.inspect}! Please supply a Snow::Vec2, an Array of Numerics, or a single scalar.")
206
- end
207
- end
208
- @scale_is_dirty = @is_dirty = true
209
- end
210
- alias :set_scale :scale=
211
-
212
- ##
213
- # Wrapper method. Sets the x component of the scaling of this Actor. See Transformable#scale.
214
- #
215
- def scale_x=(val)
216
- type_check(val, Numeric)
217
- @scale[0] = val
218
- @scale_is_dirty = @is_dirty = true
219
- end
220
-
221
- ##
222
- # Wrapper method. Sets the y component of the scaling of this Actor. See Transformable#scale.
223
- #
224
- def scale_y=(val)
225
- type_check(val, Numeric)
226
- @scale[1] = val
227
- @scale_is_dirty = @is_dirty = true
228
- end
229
-
230
- ##
231
- # Sets this transform's rotation in radians. A value of 0 results in no rotation. Great for spinning animations, or
232
- # rotating the player's camera view.
233
- #
234
- # Usage:
235
- # - transform.rotation = radians
236
- #
237
- def rotation=(radians)
238
- type_check(radians, Numeric)
239
- if radians.is_a?(Float)
240
- raise ArgumentError.new("Expected a finite number, but received #{radians.inspect}!") unless radians.finite?
241
- end
242
- @rotation = radians
243
- @rotate_is_dirty = @is_dirty = true
244
- end
245
- alias :set_rotation :rotation=
246
-
247
- ##
248
- # Sets this transform's x/y translation in radians. A value of [0, 0] results in no translation. Great for moving
249
- # actors across the screen or scrolling the camera.
250
- #
251
- # If passed more than two numeric arguments, only the first two are used.
252
- #
253
- # Optionally, this method can be passed a block and no arguments. A reference to this Transformable's translation
254
- # will be passed to the block as the first parameter, allowing direct manipulation using all of snow-math's Vec3
255
- # methods. This is particularly useful when optimizing methods that MUST be as fast as possible, such as animation
256
- # and game physics, since the result of your mathematics can be written directly to this Transformable's translation
257
- # without having to instantiate an interim Vector during every physics step.
258
- #
259
- # Usage:
260
- # - transform.translation = x, y
261
- # - transform.translation = [x, y]
262
- # - transform.translation = Snow::Vec2[x, y]
263
- # - transform.translation = Snow::Vec3[x, y, z]
264
- # - transform.translation = Snow::Vec4[x, y, z, c]
265
- # - transform.set_translation { |t| t.add(velocity * elapsed, t) }
266
- #
267
- def translation=(args = nil)
268
- if block_given?
269
- yield(@translation)
270
- else
271
- case args[0]
272
- when Array
273
- self.translation = args[0][0], args[0][1]
274
- when Snow::Vec2, Snow::Vec3, Snow::Vec4
275
- @translation[0] = args[0][0]
276
- @translation[1] = args[0][1]
277
- when Numeric
278
- raise ArgumentError.new("Cannot set translation from #{args.inspect}: numeric array requires at least two arguments!") unless args.length >= 2
279
- args.each { |arg| type_check(arg, Numeric) }
280
- @translation[0] = args[0]
281
- @translation[1] = args[1]
282
- else
283
- raise ArgumentError.new("Cannot set translation from #{args.inspect}: bad type!")
284
- end
285
- end
286
- @translate_is_dirty = @is_dirty = true
287
- end
288
- alias :set_translation :translation=
289
- alias :pos= :translation=
290
-
291
- ##
292
- # Sets this Transformable's x position in relative space. See Transformable#translation.
293
- #
294
- def x=(val)
295
- type_check(val, Numeric)
296
- @translation[0] = val
297
- @translate_is_dirty = @is_dirty = true
298
- end
299
-
300
- ##
301
- # Sets this Transformable's y position in relative space. See Transformable#translation.
302
- #
303
- def y=(val)
304
- type_check(val, Numeric)
305
- @translation[1] = val
306
- @translate_is_dirty = @is_dirty = true
307
- end
308
-
309
- ##
310
- # Returns a Snow::Mat3 which combines our current center, scale, rotation, and translation into a single transform
311
- # matrix. When a point in space is multiplied by this transform, the centering, scaling, rotation, and translation
312
- # will all be applied to that point.
313
- #
314
- # This Snow::Mat3 is cached and will only be recalculated as needed.
315
- #
316
- def to_matrix
317
- return @matrix unless @is_dirty || @matrix.nil?
318
-
319
- update_center_matrix
320
- update_scale_matrix
321
- update_rotate_matrix
322
- update_translate_matrix
323
-
324
- @matrix = Snow::Mat3.new unless @matrix
325
-
326
- @matrix.set(@center_mat)
327
- @matrix.multiply!(@scale_mat)
328
- @matrix.multiply!(@rotate_mat)
329
- @matrix.multiply!(@translate_mat)
330
-
331
- @is_dirty = false
332
- @matrix
333
- end
334
-
335
- ##
336
- # Transforms a Vec3 using the provided Mat3 transform and returns the result as a new Vec3. This is the
337
- # opposite of Transformable.untransform_point.
338
- #
339
- @@transformable_point
340
- def self.transform_point(mat, point, out = nil)
341
- @@transformable_point ||= Snow::Vec3.new
342
- @@transformable_point.set(point[0], point[1], 1)
343
-
344
- out ||= Snow::Vec3.new
345
- mat.multiply(@@transformable_point, out)
346
- out[2] = 0
347
- out
348
- end
349
-
350
- ##
351
- # Applies all of our transformations to the point, returning the resulting point as a new Vec3. This is the opposite
352
- # of Transformable#untransform_point.
353
- #
354
- def transform_point(point, out = nil)
355
- Transformable.transform_point(to_matrix, point, out)
356
- end
357
-
358
- ##
359
- # Transforms a Vec3 using the inverse of the provided Mat3 transform and returns the result as a new Vec3. This
360
- # is the opposite of Transformable.transform_point.
361
- #
362
- def self.untransform_point(mat, point, out = nil)
363
- inverse_mat = MatrixCache.instance.get
364
- raise "Unable to invert matrix: #{mat}!" unless mat.inverse(inverse_mat)
365
- transform_point(inverse_mat, point, out)
366
- ensure
367
- MatrixCache.instance.recycle(inverse_mat) if inverse_mat
368
- end
369
-
370
- ##
371
- # Applies the inverse of all of our transformations to the point, returning the resulting point as a new Vec3. This
372
- # is the opposite of Transformable#transform_point.
373
- #
374
- def untransform_point(point, out = nil)
375
- Transformable.untransform_point(to_matrix, point, out)
376
- end
377
-
378
- private
379
-
380
- def update_center_matrix
381
- return unless @center_is_dirty || @center_mat.nil?
382
- @center_mat ||= Snow::Mat3.new
383
- @center_mat[2] = -@center[0]
384
- @center_mat[5] = -@center[1]
385
- @center_is_dirty = false
386
- end
387
-
388
- def update_scale_matrix
389
- return unless @scale_is_dirty || @scale_mat.nil?
390
- @scale_mat ||= Snow::Mat3.new
391
- @scale_mat[0] = @scale[0]
392
- @scale_mat[4] = @scale[1]
393
- @scale_is_dirty = false
394
- end
395
-
396
- def update_rotate_matrix
397
- return unless @rotate_is_dirty || @rotate_mat.nil?
398
- @rotate_mat ||= Snow::Mat3.new
399
- @rotate_mat[4] = @rotate_mat[0] = Math.cos(@rotation)
400
- @rotate_mat[1] = Math.sin(@rotation)
401
- @rotate_mat[3] = -@rotate_mat[1]
402
- @rotate_is_dirty = false
403
- end
404
-
405
- def update_translate_matrix
406
- return unless @translate_is_dirty || @translate_mat.nil?
407
- @translate_mat ||= Snow::Mat3.new
408
- @translate_mat[2] = @translation[0]
409
- @translate_mat[5] = @translation[1]
410
- @translate_is_dirty = false
411
- end
412
- end
413
- end
1
+ require 'snow-math'
2
+
3
+ require_relative 'patches.rb'
4
+ require_relative 'utils.rb'
5
+
6
+ module Gosling
7
+ ##
8
+ # A helper class for performing vector transforms in 2D space. Relies heavily on the Vec3 and Mat3 classes of the
9
+ # SnowMath gem to remain performant.
10
+ #
11
+ module Transformable
12
+ attr_reader :rotation
13
+
14
+ ##
15
+ # Initializes this Transformable to have no transformations (identity matrix).
16
+ #
17
+ def initialize
18
+ @center = Snow::Vec3[0, 0, 1]
19
+ @scale = Snow::Vec2[1, 1]
20
+ @translation = Snow::Vec3[0, 0, 1]
21
+ reset
22
+ end
23
+
24
+ ##
25
+ # Resets center and translation to [0, 0], scale to [1, 1], and rotation to 0, restoring this transformation to the identity
26
+ # matrix.
27
+ #
28
+ def reset
29
+ self.center = 0, 0
30
+ self.scale = 1, 1
31
+ self.rotation = 0
32
+ self.translation = 0, 0
33
+ end
34
+
35
+ ##
36
+ # Returns a duplicate of the center Vec3 (@center is read-only).
37
+ #
38
+ def center
39
+ @center.dup.freeze
40
+ end
41
+
42
+ ##
43
+ # Returns the x component of the centerpoint of this Transformable. See Transformable#center.
44
+ #
45
+ def center_x
46
+ @center[0]
47
+ end
48
+
49
+ ##
50
+ # Returns the y component of the centerpoint of this Transformable. See Transformable#center.
51
+ #
52
+ def center_y
53
+ @center[1]
54
+ end
55
+
56
+ ##
57
+ # Returns a duplicate of the scale Vec2 (@scale is read-only).
58
+ #
59
+ def scale
60
+ @scale.dup.freeze
61
+ end
62
+
63
+ ##
64
+ # Returns the x component of the scaling of this Transformable. See Transformable#scale.
65
+ #
66
+ def scale_x
67
+ @scale[0]
68
+ end
69
+
70
+ ##
71
+ # Returns the y component of the scaling of this Transformable. See Transformable#scale.
72
+ #
73
+ def scale_y
74
+ @scale[1]
75
+ end
76
+
77
+ ##
78
+ # Returns a duplicate of the translation Vec3 (@translation is read-only).
79
+ #
80
+ def translation
81
+ @translation.dup.freeze
82
+ end
83
+ alias :pos :translation
84
+
85
+ ##
86
+ # Returns this Transformable's x position in relative space. See Transformable#translation.
87
+ #
88
+ def x
89
+ @translation[0]
90
+ end
91
+
92
+ ##
93
+ # Returns this Transformable's y position in relative space. See Transformable#translation.
94
+ #
95
+ def y
96
+ @translation[1]
97
+ end
98
+
99
+ ##
100
+ # Sets this transform's centerpoint. All other transforms are performed relative to this central point.
101
+ #
102
+ # The default centerpoint is [0, 0], which is the same as no transform. For a square defined by the vertices
103
+ # [[0, 0], [10, 0], [10, 10], [0, 10]], this would translate to that square's upper left corner. In this case, when scaled
104
+ # larger or smaller, only the square's right and bottom edges would expand or contract, and when rotated it
105
+ # would spin around its upper left corner. For most applications, this is probably not what we want.
106
+ #
107
+ # By setting the centerpoint to something other than the origin, we can change the scaling and rotation to
108
+ # something that makes more sense. For the square defined above, we could set center to be the actual center of
109
+ # the square: [5, 5]. By doing so, scaling the square would cause it to expand evenly on all sides, and rotating it
110
+ # would cause it to spin about its center like a four-cornered wheel.
111
+ #
112
+ # You can set the centerpoint to be any point in local space, inside or even outside of the shape in question.
113
+ #
114
+ # If passed more than two numeric arguments, only the first two are used.
115
+ #
116
+ # Usage:
117
+ # - transform.center = x, y
118
+ # - transform.center = [x, y]
119
+ # - transform.center = Snow::Vec2[x, y]
120
+ # - transform.center = Snow::Vec3[x, y, z]
121
+ # - transform.center = Snow::Vec4[x, y, z, c]
122
+ # - transform.set_center { |c| c.add(-sprite.pos, c) }
123
+ #
124
+ def center=(args = nil)
125
+ if block_given?
126
+ yield(@center)
127
+ else
128
+ case args[0]
129
+ when Array
130
+ self.center = args[0][0], args[0][1]
131
+ when Snow::Vec2, Snow::Vec3, Snow::Vec4
132
+ @center[0] = args[0][0]
133
+ @center[1] = args[0][1]
134
+ when Numeric
135
+ raise ArgumentError.new("Cannot set center from #{args.inspect}: numeric array requires at least two arguments!") unless args.length >= 2
136
+ args.each { |arg| type_check(arg, Numeric) }
137
+ @center[0] = args[0]
138
+ @center[1] = args[1]
139
+ else
140
+ raise ArgumentError.new("Cannot set center from #{args.inspect}: bad type!")
141
+ end
142
+ end
143
+ @center_is_dirty = @is_dirty = true
144
+ end
145
+ alias :set_center :center=
146
+
147
+ ##
148
+ # Sets the x component of the centerpoint of this Transformable. See Transformable#center.
149
+ #
150
+ def center_x=(val)
151
+ type_check(val, Numeric)
152
+ @center[0] = val
153
+ @center_is_dirty = @is_dirty = true
154
+ end
155
+
156
+ ##
157
+ # Sets the y component of the centerpoint of this Transformable. See Transformable#center.
158
+ #
159
+ def center_y=(val)
160
+ type_check(val, Numeric)
161
+ @center[1] = val
162
+ @center_is_dirty = @is_dirty = true
163
+ end
164
+
165
+ ##
166
+ # Sets this transform's x/y scaling. A scale value of [1, 1] results in no scaling. Larger values make a shape bigger,
167
+ # while smaller values will make it smaller. Great for shrinking/growing animations, or to zoom the camera in/out.
168
+ #
169
+ # If passed more than two numeric arguments, only the first two are used.
170
+ #
171
+ # Usage:
172
+ # - transform.scale = scalar
173
+ # - transform.scale = x, y
174
+ # - transform.scale = [x, y]
175
+ # - transform.scale = Snow::Vec2[x, y]
176
+ # - transform.scale = Snow::Vec3[x, y, z]
177
+ # - transform.scale = Snow::Vec4[x, y, z, c]
178
+ # - transform.set_scale { |s| s.multiply(0.5, s) }
179
+ #
180
+ def scale=(*args)
181
+ if block_given?
182
+ yield(@scale)
183
+ else
184
+ if args.length >= 2
185
+ case args[0]
186
+ when Numeric
187
+ args.each { |arg| type_check(arg, Numeric) }
188
+ @scale[0] = args[0]
189
+ @scale[1] = args[1]
190
+ else
191
+ raise ArgumentError.new("Bad combination of arguments: #{args.inspect}! Please supply a Snow::Vec2, an Array of Numerics, or a single scalar.")
192
+ end
193
+ elsif args.length == 1
194
+ case args[0]
195
+ when Array
196
+ self.set_scale(*(args[0]))
197
+ when Snow::Vec2
198
+ self.set_scale(args[0][0], args[0][1])
199
+ when Numeric
200
+ self.set_scale(args[0], args[0])
201
+ else
202
+ raise ArgumentError.new("Bad combination of arguments: #{args.inspect}! Please supply a Snow::Vec2, an Array of Numerics, or a single scalar.")
203
+ end
204
+ else
205
+ raise ArgumentError.new("Bad combination of arguments: #{args.inspect}! Please supply a Snow::Vec2, an Array of Numerics, or a single scalar.")
206
+ end
207
+ end
208
+ @scale_is_dirty = @is_dirty = true
209
+ end
210
+ alias :set_scale :scale=
211
+
212
+ ##
213
+ # Wrapper method. Sets the x component of the scaling of this Actor. See Transformable#scale.
214
+ #
215
+ def scale_x=(val)
216
+ type_check(val, Numeric)
217
+ @scale[0] = val
218
+ @scale_is_dirty = @is_dirty = true
219
+ end
220
+
221
+ ##
222
+ # Wrapper method. Sets the y component of the scaling of this Actor. See Transformable#scale.
223
+ #
224
+ def scale_y=(val)
225
+ type_check(val, Numeric)
226
+ @scale[1] = val
227
+ @scale_is_dirty = @is_dirty = true
228
+ end
229
+
230
+ ##
231
+ # Sets this transform's rotation in radians. A value of 0 results in no rotation. Great for spinning animations, or
232
+ # rotating the player's camera view.
233
+ #
234
+ # Usage:
235
+ # - transform.rotation = radians
236
+ #
237
+ def rotation=(radians)
238
+ type_check(radians, Numeric)
239
+ if radians.is_a?(Float)
240
+ raise ArgumentError.new("Expected a finite number, but received #{radians.inspect}!") unless radians.finite?
241
+ end
242
+ @rotation = radians
243
+ @rotate_is_dirty = @is_dirty = true
244
+ end
245
+ alias :set_rotation :rotation=
246
+
247
+ ##
248
+ # Sets this transform's x/y translation in radians. A value of [0, 0] results in no translation. Great for moving
249
+ # actors across the screen or scrolling the camera.
250
+ #
251
+ # If passed more than two numeric arguments, only the first two are used.
252
+ #
253
+ # Optionally, this method can be passed a block and no arguments. A reference to this Transformable's translation
254
+ # will be passed to the block as the first parameter, allowing direct manipulation using all of snow-math's Vec3
255
+ # methods. This is particularly useful when optimizing methods that MUST be as fast as possible, such as animation
256
+ # and game physics, since the result of your mathematics can be written directly to this Transformable's translation
257
+ # without having to instantiate an interim Vector during every physics step.
258
+ #
259
+ # Usage:
260
+ # - transform.translation = x, y
261
+ # - transform.translation = [x, y]
262
+ # - transform.translation = Snow::Vec2[x, y]
263
+ # - transform.translation = Snow::Vec3[x, y, z]
264
+ # - transform.translation = Snow::Vec4[x, y, z, c]
265
+ # - transform.set_translation { |t| t.add(velocity * elapsed, t) }
266
+ #
267
+ def translation=(args = nil)
268
+ if block_given?
269
+ yield(@translation)
270
+ else
271
+ case args[0]
272
+ when Array
273
+ self.translation = args[0][0], args[0][1]
274
+ when Snow::Vec2, Snow::Vec3, Snow::Vec4
275
+ @translation[0] = args[0][0]
276
+ @translation[1] = args[0][1]
277
+ when Numeric
278
+ raise ArgumentError.new("Cannot set translation from #{args.inspect}: numeric array requires at least two arguments!") unless args.length >= 2
279
+ args.each { |arg| type_check(arg, Numeric) }
280
+ @translation[0] = args[0]
281
+ @translation[1] = args[1]
282
+ else
283
+ raise ArgumentError.new("Cannot set translation from #{args.inspect}: bad type!")
284
+ end
285
+ end
286
+ @translate_is_dirty = @is_dirty = true
287
+ end
288
+ alias :set_translation :translation=
289
+ alias :pos= :translation=
290
+
291
+ ##
292
+ # Sets this Transformable's x position in relative space. See Transformable#translation.
293
+ #
294
+ def x=(val)
295
+ type_check(val, Numeric)
296
+ @translation[0] = val
297
+ @translate_is_dirty = @is_dirty = true
298
+ end
299
+
300
+ ##
301
+ # Sets this Transformable's y position in relative space. See Transformable#translation.
302
+ #
303
+ def y=(val)
304
+ type_check(val, Numeric)
305
+ @translation[1] = val
306
+ @translate_is_dirty = @is_dirty = true
307
+ end
308
+
309
+ ##
310
+ # Returns a Snow::Mat3 which combines our current center, scale, rotation, and translation into a single transform
311
+ # matrix. When a point in space is multiplied by this transform, the centering, scaling, rotation, and translation
312
+ # will all be applied to that point.
313
+ #
314
+ # This Snow::Mat3 is cached and will only be recalculated as needed.
315
+ #
316
+ def to_matrix
317
+ return @matrix unless @is_dirty || @matrix.nil?
318
+
319
+ update_center_matrix
320
+ update_scale_matrix
321
+ update_rotate_matrix
322
+ update_translate_matrix
323
+
324
+ @matrix = Snow::Mat3.new unless @matrix
325
+
326
+ @matrix.set(@center_mat)
327
+ @matrix.multiply!(@scale_mat)
328
+ @matrix.multiply!(@rotate_mat)
329
+ @matrix.multiply!(@translate_mat)
330
+
331
+ @is_dirty = false
332
+ @matrix
333
+ end
334
+
335
+ ##
336
+ # Transforms a Vec3 using the provided Mat3 transform and returns the result as a new Vec3. This is the
337
+ # opposite of Transformable.untransform_point.
338
+ #
339
+ @@transformable_point
340
+ def self.transform_point(mat, point, out = nil)
341
+ @@transformable_point ||= Snow::Vec3.new
342
+ @@transformable_point.set(point[0], point[1], 1)
343
+
344
+ out ||= Snow::Vec3.new
345
+ mat.multiply(@@transformable_point, out)
346
+ out[2] = 0
347
+ out
348
+ end
349
+
350
+ ##
351
+ # Applies all of our transformations to the point, returning the resulting point as a new Vec3. This is the opposite
352
+ # of Transformable#untransform_point.
353
+ #
354
+ def transform_point(point, out = nil)
355
+ Transformable.transform_point(to_matrix, point, out)
356
+ end
357
+
358
+ ##
359
+ # Transforms a Vec3 using the inverse of the provided Mat3 transform and returns the result as a new Vec3. This
360
+ # is the opposite of Transformable.transform_point.
361
+ #
362
+ def self.untransform_point(mat, point, out = nil)
363
+ inverse_mat = MatrixCache.instance.get
364
+ raise "Unable to invert matrix: #{mat}!" unless mat.inverse(inverse_mat)
365
+ transform_point(inverse_mat, point, out)
366
+ ensure
367
+ MatrixCache.instance.recycle(inverse_mat) if inverse_mat
368
+ end
369
+
370
+ ##
371
+ # Applies the inverse of all of our transformations to the point, returning the resulting point as a new Vec3. This
372
+ # is the opposite of Transformable#transform_point.
373
+ #
374
+ def untransform_point(point, out = nil)
375
+ Transformable.untransform_point(to_matrix, point, out)
376
+ end
377
+
378
+ private
379
+
380
+ def update_center_matrix
381
+ return unless @center_is_dirty || @center_mat.nil?
382
+ @center_mat ||= Snow::Mat3.new
383
+ @center_mat[2] = -@center[0]
384
+ @center_mat[5] = -@center[1]
385
+ @center_is_dirty = false
386
+ end
387
+
388
+ def update_scale_matrix
389
+ return unless @scale_is_dirty || @scale_mat.nil?
390
+ @scale_mat ||= Snow::Mat3.new
391
+ @scale_mat[0] = @scale[0]
392
+ @scale_mat[4] = @scale[1]
393
+ @scale_is_dirty = false
394
+ end
395
+
396
+ def update_rotate_matrix
397
+ return unless @rotate_is_dirty || @rotate_mat.nil?
398
+ @rotate_mat ||= Snow::Mat3.new
399
+ @rotate_mat[4] = @rotate_mat[0] = Math.cos(@rotation)
400
+ @rotate_mat[1] = Math.sin(@rotation)
401
+ @rotate_mat[3] = -@rotate_mat[1]
402
+ @rotate_is_dirty = false
403
+ end
404
+
405
+ def update_translate_matrix
406
+ return unless @translate_is_dirty || @translate_mat.nil?
407
+ @translate_mat ||= Snow::Mat3.new
408
+ @translate_mat[2] = @translation[0]
409
+ @translate_mat[5] = @translation[1]
410
+ @translate_is_dirty = false
411
+ end
412
+ end
413
+ end