gosling 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6522b7771bdf2e2b85a0bf3cbf7f440497d21a91
4
- data.tar.gz: e0d6aa8c63c4361aeb7dffb1f25c6310bb050730
3
+ metadata.gz: f40ad4e1b06a4aeb3653172b748bb6b9cff0aaec
4
+ data.tar.gz: '0249b7dbe396d152de3791ad3e5548ffad9c8ece'
5
5
  SHA512:
6
- metadata.gz: a1646556b4e6765ceda7ede9e7c7447cd71eda9af4eb510bd7413e9ba8e9116c4c4177e9b8096c3f2fc81584c0bcb310ab6caf00691d951f1b549b5ffb98eeb8
7
- data.tar.gz: 719da8786b573ae344338586f2c53ebc0bc41987d392fc0383b218fc4408e94f5f411ffc3a506bf78a9958eae033ae34db14385db3c4702f380e6e312250d753
6
+ metadata.gz: 05a6efbacdee880176e86660408c0d005d0e32e86e1eca91a213e1552dfdbccfe40edf43d167669e0684690d2280299ce49448d2f3719b7604bb037c683e6a49
7
+ data.tar.gz: b5670a5dace288c80bb8ba339cbba83b171d1e0ec8d104b2ba327792e550dc4957783ec0ee710940bc45c115af4f40d958bf77563b7711b6a248fd31ed5a933b
data/lib/gosling/actor.rb CHANGED
@@ -139,12 +139,19 @@ module Gosling
139
139
  # be skipped and not drawn.
140
140
  #
141
141
  def draw(matrix = nil)
142
- matrix ||= Snow::Mat3.new
143
- transform = to_matrix * matrix
142
+ transform = MatrixCache.instance.get
143
+ if matrix
144
+ to_matrix.multiply(matrix, transform)
145
+ else
146
+ transform.set(to_matrix)
147
+ end
148
+
144
149
  render(transform) if @is_visible
145
150
  if @are_children_visible
146
151
  @children.each { |child| child.draw(transform) }
147
152
  end
153
+ ensure
154
+ MatrixCache.instance.recycle(transform)
148
155
  end
149
156
 
150
157
  ##
@@ -209,12 +216,11 @@ module Gosling
209
216
  # space of its root ancestor).
210
217
  #
211
218
  def get_global_transform(out = nil)
219
+ out ||= Snow::Mat3.new
212
220
  if parent
213
- out ||= Snow::Mat3.new
214
221
  to_matrix.multiply(parent.get_global_transform, out)
215
- out
216
222
  else
217
- to_matrix
223
+ out.set(to_matrix)
218
224
  end
219
225
  end
220
226
 
@@ -222,9 +228,13 @@ module Gosling
222
228
  # Returns the global x/y position of this actor (where it is relative to its root ancestor). This value is calculated
223
229
  # using the Actor's center (see Transformable#center).
224
230
  #
225
- def get_global_position
226
- tf = get_global_transform
227
- Transformable.transform_point(tf, center)
231
+ def get_global_position(out = nil)
232
+ tf = MatrixCache.instance.get
233
+ get_global_transform(tf)
234
+ out ||= Snow::Vec3.new
235
+ Transformable.transform_point(tf, center, out)
236
+ ensure
237
+ MatrixCache.instance.recycle(tf)
228
238
  end
229
239
 
230
240
  ##
@@ -288,6 +298,19 @@ module Gosling
288
298
  def render(matrix)
289
299
  end
290
300
 
301
+ def fill_polygon(vertices)
302
+ (2...vertices.length).each do |i|
303
+ v0 = vertices[0]
304
+ v1 = vertices[i - 1]
305
+ v2 = vertices[i]
306
+ @window.draw_triangle(
307
+ v0[0].to_f, v0[1].to_f, @color,
308
+ v1[0].to_f, v1[1].to_f, @color,
309
+ v2[0].to_f, v2[1].to_f, @color,
310
+ )
311
+ end
312
+ end
313
+
291
314
  ##
292
315
  # Internal use only. See #add_child and #remove_child.
293
316
  #
@@ -34,9 +34,10 @@ module Gosling
34
34
  ##
35
35
  # Returns the angle's corresponding unit vector times this circle's radius.
36
36
  #
37
- def get_point_at_angle(radians)
37
+ def get_point_at_angle(radians, out = nil)
38
38
  raise ArgumentError.new("Expected Numeric, but received #{radians.inspect}!") unless radians.is_a?(Numeric)
39
- Snow::Vec3[Math.cos(radians) * @radius, Math.sin(radians) * @radius, 0]
39
+ out ||= Snow::Vec3.new
40
+ out.set(Math.cos(radians) * @radius, Math.sin(radians) * @radius, 0)
40
41
  end
41
42
 
42
43
  ##
@@ -48,23 +49,17 @@ module Gosling
48
49
 
49
50
  private
50
51
 
52
+ # TODO: keep a cached, class-level list of local vertices that can be re-used during rendering
53
+
51
54
  def render(matrix)
55
+ # TODO: store these vertices in a cached, class-level array (see above)
52
56
  local_vertices = (0...RENDER_VERTEX_COUNT).map do |i|
53
57
  get_point_at_angle(Math::PI * 2 * i / RENDER_VERTEX_COUNT)
54
58
  end
59
+ # TODO: retain an array of vertices in memory; write transformed vertices to this array
55
60
  global_vertices = local_vertices.map { |v| Transformable.transform_point(matrix, v) }
56
- i = 2
57
- while i < global_vertices.length
58
- v0 = global_vertices[0]
59
- v1 = global_vertices[i-1]
60
- v2 = global_vertices[i]
61
- @window.draw_triangle(
62
- v0[0].to_f, v0[1].to_f, @color,
63
- v1[0].to_f, v1[1].to_f, @color,
64
- v2[0].to_f, v2[1].to_f, @color,
65
- )
66
- i += 1
67
- end
61
+
62
+ fill_polygon(global_vertices)
68
63
  end
69
64
  end
70
65
  end
@@ -33,9 +33,11 @@ module Gosling
33
33
 
34
34
  return false if shapeA === shapeB
35
35
 
36
- separation_axes = get_separation_axes(shapeA, shapeB)
36
+ get_separation_axes(shapeA, shapeB)
37
37
 
38
+ reset_projection_axis_tracking
38
39
  separation_axes.each do |axis|
40
+ next if axis_already_projected?(axis)
39
41
  projectionA = project_onto_axis(shapeA, axis)
40
42
  projectionB = project_onto_axis(shapeB, axis)
41
43
  return false unless projections_overlap?(projectionA, projectionB)
@@ -58,18 +60,26 @@ module Gosling
58
60
  # - penetration: if colliding, a vector representing how far shape B must move to be separated from (or merely
59
61
  # touching) shape A; nil otherwise
60
62
  #
61
- def self.get_collision_info(shapeA, shapeB)
62
- info = { colliding: false, overlap: nil, penetration: nil }
63
+ def self.get_collision_info(shapeA, shapeB, info = nil)
64
+ if info
65
+ info.clear
66
+ else
67
+ info = {}
68
+ end
69
+ info.merge!(actors: [shapeA, shapeB], colliding: false, overlap: nil, penetration: nil)
63
70
 
64
71
  return info if shapeA.instance_of?(Actor) || shapeB.instance_of?(Actor)
65
72
 
66
73
  return info if shapeA === shapeB
67
74
 
68
- separation_axes = get_separation_axes(shapeA, shapeB)
75
+ get_separation_axes(shapeA, shapeB)
76
+ return info if separation_axes.empty?
69
77
 
70
78
  smallest_overlap = nil
71
79
  smallest_axis = nil
80
+ reset_projection_axis_tracking
72
81
  separation_axes.each do |axis|
82
+ next if axis_already_projected?(axis)
73
83
  projectionA = project_onto_axis(shapeA, axis)
74
84
  projectionB = project_onto_axis(shapeB, axis)
75
85
  overlap = get_overlap(projectionA, projectionB)
@@ -77,7 +87,8 @@ module Gosling
77
87
  if smallest_overlap.nil? || smallest_overlap > overlap
78
88
  smallest_overlap = overlap
79
89
  flip = (projectionA[0] + projectionA[1]) * 0.5 > (projectionB[0] + projectionB[1]) * 0.5
80
- smallest_axis = flip ? -axis : axis
90
+ smallest_axis = axis
91
+ smallest_axis.negate! if flip
81
92
  end
82
93
  end
83
94
 
@@ -85,7 +96,7 @@ module Gosling
85
96
  info[:overlap] = smallest_overlap
86
97
  info[:penetration] = smallest_axis.normalize * smallest_overlap
87
98
 
88
- return info
99
+ info
89
100
  end
90
101
 
91
102
  ##
@@ -104,47 +115,240 @@ module Gosling
104
115
 
105
116
  return false if shape.instance_of?(Actor)
106
117
 
107
- separation_axes = []
118
+ global_pos = nil
119
+ centers_axis = nil
120
+ global_vertices = nil
108
121
  if shape.instance_of?(Circle)
109
- centers_axis = point - shape.get_global_position
110
- separation_axes.push(centers_axis) if centers_axis && centers_axis.magnitude > 0
122
+ unless @@global_position_cache.key?(shape)
123
+ global_pos = VectorCache.instance.get
124
+ shape.get_global_position(global_pos)
125
+ end
126
+ centers_axis = VectorCache.instance.get
127
+ point.subtract(@@global_position_cache.fetch(shape, global_pos), centers_axis)
128
+ next_separation_axis.set(centers_axis) if centers_axis && (centers_axis[0] != 0 || centers_axis[1] != 0)
111
129
  else
112
- separation_axes.concat(get_polygon_separation_axes(shape.get_global_vertices))
130
+ unless @@global_vertices_cache.key?(shape)
131
+ global_vertices = Array.new(shape.get_vertices.length) { VectorCache.instance.get }
132
+ shape.get_global_vertices(global_vertices)
133
+ end
134
+ get_polygon_separation_axes(@@global_vertices_cache.fetch(shape, global_vertices))
113
135
  end
114
136
 
137
+ reset_projection_axis_tracking
115
138
  separation_axes.each do |axis|
139
+ next if axis_already_projected?(axis)
116
140
  shape_projection = project_onto_axis(shape, axis)
117
141
  point_projection = point.dot_product(axis)
118
- return false unless shape_projection.min <= point_projection && point_projection <= shape_projection.max
142
+ return false unless shape_projection.first <= point_projection && point_projection <= shape_projection.last
119
143
  end
120
144
 
121
145
  return true
146
+ ensure
147
+ VectorCache.instance.recycle(global_pos) if global_pos
148
+ VectorCache.instance.recycle(centers_axis) if centers_axis
149
+ global_vertices.each { |v| VectorCache.instance.recycle(v) } if global_vertices
150
+ end
151
+
152
+ @@collision_buffer = []
153
+ @@global_position_cache = {}
154
+ @@global_vertices_cache = {}
155
+ @@global_transform_cache = {}
156
+ @@buffer_iterator_a = nil
157
+ @@buffer_iterator_b = nil
158
+
159
+ ##
160
+ # Adds one or more descendents of Actor to the collision testing buffer. The buffer's iterators will be reset to the
161
+ # first potential collision in the buffer.
162
+ #
163
+ # When added to the buffer, important and expensive global-space collision values for each Actor - transform,
164
+ # position, and any vertices - are calculated and cached for re-use. This ensures that expensive transform
165
+ # calculations are only performed once per actor during each collision resolution step.
166
+ #
167
+ # If you modify a buffered actor's transforms in any way, you will need to update its cached values by calling
168
+ # buffer_shapes again. Otherwise, it will continue to use stale and inaccurate transform information.
169
+ #
170
+ def self.buffer_shapes(actors)
171
+ type_check(actors, Array)
172
+ actors.each { |a| type_check(a, Actor) }
173
+
174
+ reset_buffer_iterators
175
+
176
+ shapes = actors.reject { |a| a.instance_of?(Actor) }
177
+
178
+ @@collision_buffer = @@collision_buffer | shapes
179
+ shapes.each do |shape|
180
+ unless @@global_transform_cache.key?(shape)
181
+ @@global_transform_cache[shape] = MatrixCache.instance.get
182
+ end
183
+ shape.get_global_transform(@@global_transform_cache[shape])
184
+
185
+ unless @@global_position_cache.key?(shape)
186
+ @@global_position_cache[shape] = VectorCache.instance.get
187
+ end
188
+ # TODO: can we calculate this position using the global transform we already have?
189
+ @@global_position_cache[shape].set(shape.get_global_position)
190
+
191
+ if shape.is_a?(Polygon)
192
+ unless @@global_vertices_cache.key?(shape)
193
+ @@global_vertices_cache[shape] = Array.new(shape.get_vertices.length) { VectorCache.instance.get }
194
+ end
195
+ # TODO: can we calculate these vertices using the global transform we already have?
196
+ shape.get_global_vertices(@@global_vertices_cache[shape])
197
+ end
198
+ end
199
+ end
200
+
201
+ ##
202
+ # Removes one or more descendents of Actor from the collision testing buffer. Any cached values for the actors
203
+ # are discarded. The buffer's iterators will be reset to the first potential collision in the buffer.
204
+ #
205
+ def self.unbuffer_shapes(actors)
206
+ type_check(actors, Array)
207
+ actors.each { |a| type_check(a, Actor) }
208
+
209
+ reset_buffer_iterators
210
+
211
+ @@collision_buffer = @@collision_buffer - actors
212
+ actors.each do |actor|
213
+ if @@global_transform_cache.key?(actor)
214
+ MatrixCache.instance.recycle(@@global_transform_cache[actor])
215
+ @@global_transform_cache.delete(actor)
216
+ end
217
+
218
+ if @@global_position_cache.key?(actor)
219
+ VectorCache.instance.recycle(@@global_position_cache[actor])
220
+ @@global_position_cache.delete(actor)
221
+ end
222
+
223
+ if @@global_vertices_cache.key?(actor)
224
+ @@global_vertices_cache[actor].each do |vertex|
225
+ VectorCache.instance.recycle(vertex)
226
+ end
227
+ @@global_vertices_cache.delete(actor)
228
+ end
229
+ end
230
+ end
231
+
232
+ ##
233
+ # Removes all actors from the collision testing buffer. See Collision.unbuffer_shapes.
234
+ #
235
+ def self.clear_buffer
236
+ unbuffer_shapes(@@collision_buffer)
237
+ end
238
+
239
+ ##
240
+ # Returns collision information for the next pair of actors in the collision buffer, or returns nil if all pairs in the
241
+ # buffer have been tested. Advances the buffer's iterators to the next pair. See Collision.get_collision_info.
242
+ #
243
+ def self.next_collision_info
244
+ reset_buffer_iterators if @@buffer_iterator_a.nil? || @@buffer_iterator_b.nil?
245
+ return if iteration_complete?
246
+
247
+ info = get_collision_info(@@collision_buffer[@@buffer_iterator_a], @@collision_buffer[@@buffer_iterator_b])
248
+ skip_next_collision
249
+ info
250
+ end
251
+
252
+ ##
253
+ # Returns the pair of actors in the collision buffer that would be tested during the next call to
254
+ # Collision.next_collision_info, or returns nil if all pairs in the buffer have been tested. Does not perform
255
+ # collision testing or advance the buffer's iterators.
256
+ #
257
+ # One use of this method is to look at the two actors about to be tested and, using some custom and likely more
258
+ # efficient logic, determine if it's worth bothering to collision test these actors at all. If not, the pair's collision test
259
+ # can be skipped by calling Collision.skip_next_collision.
260
+ #
261
+ def self.peek_at_next_collision
262
+ reset_buffer_iterators if @@buffer_iterator_a.nil? || @@buffer_iterator_b.nil?
263
+ return if iteration_complete?
264
+
265
+ [@@collision_buffer[@@buffer_iterator_a], @@collision_buffer[@@buffer_iterator_b]]
266
+ end
267
+
268
+ ##
269
+ # Advances the collision buffer's iterators to the next pair of actors in the buffer without performing any collision
270
+ # testing. By using this method in conjunction with Collision.peek_at_next_collision, it is possible to selectively
271
+ # skip collision testing for pairs of actors that meet certain criteria.
272
+ #
273
+ def self.skip_next_collision
274
+ reset_buffer_iterators if @@buffer_iterator_a.nil? || @@buffer_iterator_b.nil?
275
+ return if iteration_complete?
276
+
277
+ @@buffer_iterator_b += 1
278
+ if @@buffer_iterator_b >= @@buffer_iterator_a
279
+ @@buffer_iterator_b = 0
280
+ @@buffer_iterator_a += 1
281
+ end
122
282
  end
123
283
 
124
284
  private
125
285
 
126
- def self.get_normal(vector)
127
- type_check(vector, Snow::Vec3)
128
- raise ArgumentError.new("Cannot determine normal of zero-length vector") if vector.magnitude_squared == 0
129
- Snow::Vec3[-vector[1], vector[0], 0]
286
+ def self.iteration_complete?
287
+ @@buffer_iterator_a >= @@collision_buffer.length
130
288
  end
131
289
 
132
- def self.get_polygon_separation_axes(vertices)
133
- type_check(vertices, Array)
134
- vertices.each { |v| type_check(v, Snow::Vec3) }
290
+ def self.reset_buffer_iterators
291
+ @@buffer_iterator_a = 1
292
+ @@buffer_iterator_b = 0
293
+ end
294
+
295
+ def self.get_normal(vector, out = nil)
296
+ raise ArgumentError.new("Cannot determine normal of zero-length vector") if vector[0] == 0 && vector[1] == 0
297
+ out ||= Snow::Vec3.new
298
+ out.set(-vector[1], vector[0], 0)
299
+ end
300
+
301
+ @@separation_axes = []
302
+ @@separation_axis_count = 0
303
+
304
+ def self.reset_separation_axes
305
+ @@separation_axis_count = 0
306
+ end
307
+
308
+ def self.next_separation_axis
309
+ axis = @@separation_axes[@@separation_axis_count] ||= Snow::Vec3.new
310
+ @@separation_axis_count += 1
311
+ axis
312
+ end
313
+
314
+ def self.separation_axes
315
+ @@separation_axes[0...@@separation_axis_count]
316
+ end
135
317
 
136
- axes = (0...vertices.length).map do |i|
137
- axis = vertices[(i + 1) % vertices.length] - vertices[i]
138
- (axis.magnitude > 0) ? get_normal(axis).normalize : nil
318
+ @@gpsa_axis = Snow::Vec3.new
319
+ def self.get_polygon_separation_axes(vertices)
320
+ # TODO: special case for Rects - only return two axes to avoid duplicitous math
321
+ vertices.each_index do |i|
322
+ vertices[i].subtract(vertices[i - 1], @@gpsa_axis)
323
+ if @@gpsa_axis[0] != 0 || @@gpsa_axis[1] != 0
324
+ get_normal(@@gpsa_axis, @@gpsa_axis).normalize(next_separation_axis)
325
+ end
139
326
  end
140
- axes.compact
327
+ nil
141
328
  end
142
329
 
330
+ @@global_pos_a = nil
331
+ @@global_pos_b = nil
332
+ @@gcsa_axis = nil
143
333
  def self.get_circle_separation_axis(circleA, circleB)
144
- type_check(circleA, Actor)
145
- type_check(circleB, Actor)
146
- axis = circleB.get_global_position - circleA.get_global_position
147
- (axis.magnitude > 0) ? axis.normalize : nil
334
+ unless @@global_position_cache.key?(circleA)
335
+ @@global_pos_a ||= Snow::Vec3.new
336
+ circleA.get_global_position(@@global_pos_a)
337
+ end
338
+
339
+ unless @@global_position_cache.key?(circleB)
340
+ @@global_pos_b ||= Snow::Vec3.new
341
+ circleB.get_global_position(@@global_pos_b)
342
+ end
343
+
344
+ @@gcsa_axis ||= Snow::Vec3.new
345
+ @@global_pos_a = @@global_position_cache.fetch(circleA, @@global_pos_a)
346
+ @@global_pos_b = @@global_position_cache.fetch(circleB, @@global_pos_b)
347
+ @@global_pos_b.subtract(@@global_pos_a, @@gcsa_axis)
348
+ if @@gcsa_axis[0] != 0 || @@gcsa_axis[1] != 0
349
+ @@gcsa_axis.normalize(next_separation_axis)
350
+ end
351
+ nil
148
352
  end
149
353
 
150
354
  def self.get_separation_axes(shapeA, shapeB)
@@ -156,40 +360,123 @@ module Gosling
156
360
  raise ArgumentError.new("Expected a child of the Actor class, but received #{shapeB.inspect}!")
157
361
  end
158
362
 
159
- separation_axes = []
363
+ reset_separation_axes
364
+ global_vertices = nil
160
365
 
161
366
  unless shapeA.instance_of?(Circle)
162
- separation_axes.concat(get_polygon_separation_axes(shapeA.get_global_vertices))
367
+ unless @@global_vertices_cache.key?(shapeA)
368
+ global_vertices = Array.new(shapeA.get_vertices.length) { VectorCache.instance.get }
369
+ shapeA.get_global_vertices(global_vertices)
370
+ end
371
+ get_polygon_separation_axes(@@global_vertices_cache.fetch(shapeA, global_vertices))
163
372
  end
164
373
 
165
374
  unless shapeB.instance_of?(Circle)
166
- separation_axes.concat(get_polygon_separation_axes(shapeB.get_global_vertices))
375
+ unless @@global_vertices_cache.key?(shapeB)
376
+ global_vertices ||= []
377
+ (shapeB.get_vertices.length - global_vertices.length).times do
378
+ global_vertices.push(VectorCache.instance.get)
379
+ end
380
+ (global_vertices.length - shapeB.get_vertices.length).times do
381
+ VectorCache.instance.recycle(global_vertices.pop)
382
+ end
383
+ shapeB.get_global_vertices(global_vertices)
384
+ end
385
+ get_polygon_separation_axes(@@global_vertices_cache.fetch(shapeB, global_vertices))
167
386
  end
168
387
 
169
388
  if shapeA.instance_of?(Circle) || shapeB.instance_of?(Circle)
170
- axis = get_circle_separation_axis(shapeA, shapeB)
171
- separation_axes.push(axis) if axis
389
+ get_circle_separation_axis(shapeA, shapeB)
172
390
  end
173
391
 
174
- separation_axes.map! { |v| v[0] < 0 ? v * -1 : v }
175
- separation_axes.uniq
392
+ nil
393
+ ensure
394
+ global_vertices.each { |v| VectorCache.instance.recycle(v) } if global_vertices
176
395
  end
177
396
 
178
- def self.project_onto_axis(shape, axis)
179
- type_check(shape, Actor)
180
- type_check(axis, Snow::Vec3)
397
+ @@poa_zero_z_axis = nil
398
+ @@poa_local_axis = nil
399
+ @@poa_intersection = nil
400
+ @@poa_global_tf = nil
401
+ @@poa_global_tf_inverse = nil
402
+ def self.get_circle_vertices_by_axis(shape, axis)
403
+ unless @@global_transform_cache.key?(shape)
404
+ @@poa_global_tf ||= Snow::Mat3.new
405
+ shape.get_global_transform(@@poa_global_tf)
406
+ end
181
407
 
182
- global_vertices = if shape.instance_of?(Circle)
183
- global_tf = shape.get_global_transform
184
- local_axis = global_tf.inverse * Snow::Vec3[axis[0], axis[1], 0]
185
- v = shape.get_point_at_angle(Math.atan2(local_axis[1], local_axis[0]))
186
- [v, v * -1].map { |vertex| Transformable.transform_point(global_tf, vertex) }
187
- else
188
- shape.get_global_vertices
408
+ @@poa_zero_z_axis ||= Snow::Vec3.new
409
+ @@poa_zero_z_axis.set(axis[0], axis[1], 0)
410
+
411
+ @@poa_global_tf_inverse ||= Snow::Mat3.new
412
+ @@global_transform_cache.fetch(shape, @@poa_global_tf).inverse(@@poa_global_tf_inverse)
413
+
414
+ @@poa_local_axis ||= Snow::Vec3.new
415
+ @@poa_global_tf_inverse.multiply(@@poa_zero_z_axis, @@poa_local_axis)
416
+
417
+ @@poa_intersection ||= Snow::Vec3.new
418
+ shape.get_point_at_angle(Math.atan2(@@poa_local_axis[1], @@poa_local_axis[0]), @@poa_intersection)
419
+
420
+ Transformable.transform_point(@@global_transform_cache.fetch(shape, @@poa_global_tf), @@poa_intersection, next_global_vertex)
421
+
422
+ @@poa_intersection.negate!
423
+ Transformable.transform_point(@@global_transform_cache.fetch(shape, @@poa_global_tf), @@poa_intersection, next_global_vertex)
424
+ end
425
+
426
+ @@global_vertices = nil
427
+ @@global_vertices_count = 0
428
+
429
+ def self.reset_global_vertices
430
+ @@global_vertices ||= []
431
+ @@global_vertices_count = 0
432
+ end
433
+
434
+ def self.next_global_vertex
435
+ vertex = @@global_vertices[@@global_vertices_count] ||= Snow::Vec3.new
436
+ @@global_vertices_count += 1
437
+ vertex
438
+ end
439
+
440
+ @@projected_axes = nil
441
+
442
+ def self.reset_projection_axis_tracking
443
+ @@projected_axes ||= {}
444
+ @@projected_axes.clear
445
+ end
446
+
447
+ def self.axis_already_projected?(axis)
448
+ key = axis.to_s
449
+ return true if @@projected_axes.key?(key)
450
+ @@projected_axes[key] = nil
451
+ end
452
+
453
+ def self.project_onto_axis(shape, axis, out = nil)
454
+ unless @@global_vertices_cache.key?(shape)
455
+ reset_global_vertices
456
+ if shape.instance_of?(Circle)
457
+ get_circle_vertices_by_axis(shape, axis)
458
+ else
459
+ shape.get_global_vertices(@@global_vertices)
460
+ @@global_vertices_count = shape.get_vertices.length
461
+ end
189
462
  end
190
463
 
191
- projections = global_vertices.map { |vertex| vertex.dot_product(axis) }.sort
192
- [projections.first, projections.last]
464
+ min = nil
465
+ max = nil
466
+ @@global_vertices_cache.fetch(shape, @@global_vertices[0...@@global_vertices_count]).each do |vertex|
467
+ projection = vertex.dot_product(axis)
468
+ if min.nil?
469
+ min = projection
470
+ max = projection
471
+ else
472
+ min = projection if projection < min
473
+ max = projection if projection > max
474
+ end
475
+ end
476
+ out ||= []
477
+ out[1] = max
478
+ out[0] = min
479
+ out
193
480
  end
194
481
 
195
482
  def self.projections_overlap?(a, b)
@@ -198,12 +485,8 @@ module Gosling
198
485
  end
199
486
 
200
487
  def self.get_overlap(a, b)
201
- type_check(a, Array)
202
- type_check(b, Array)
203
- a.each { |x| type_check(x, Numeric) }
204
- b.each { |x| type_check(x, Numeric) }
205
- raise ArgumentError.new("Expected two arrays of length 2, but received #{a.inspect} and #{b.inspect}!") unless a.length == 2 && b.length == 2
206
-
488
+ raise ArgumentError.new("Projection array must be length 2, not #{a.inspect}!") unless a.length == 2
489
+ raise ArgumentError.new("Projection array must be length 2, not #{b.inspect}!") unless b.length == 2
207
490
  a.sort! if a[0] > a[1]
208
491
  b.sort! if b[0] > b[1]
209
492
  return b[1] - b[0] if a[0] <= b[0] && b[1] <= a[1]