gosling 2.1.0 → 2.3.0
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.
- checksums.yaml +4 -4
- data/lib/gosling/actor.rb +31 -8
- data/lib/gosling/circle.rb +9 -14
- data/lib/gosling/collision.rb +334 -51
- data/lib/gosling/image_library.rb +1 -1
- data/lib/gosling/matrix_cache.rb +21 -0
- data/lib/gosling/object_cache.rb +48 -0
- data/lib/gosling/polygon.rb +43 -19
- data/lib/gosling/rect.rb +1 -7
- data/lib/gosling/sprite.rb +2 -1
- data/lib/gosling/transformable.rb +105 -75
- data/lib/gosling/vector_cache.rb +21 -0
- data/lib/gosling/version.rb +1 -1
- data/spec/collision_spec.rb +375 -77
- data/spec/matrix_cache_spec.rb +25 -0
- data/spec/object_cache_spec.rb +22 -0
- data/spec/polygon_spec.rb +27 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/transformable_spec.rb +122 -15
- data/spec/vector_cache_spec.rb +80 -0
- metadata +12 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f40ad4e1b06a4aeb3653172b748bb6b9cff0aaec
|
4
|
+
data.tar.gz: '0249b7dbe396d152de3791ad3e5548ffad9c8ece'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
143
|
-
|
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 =
|
227
|
-
|
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
|
#
|
data/lib/gosling/circle.rb
CHANGED
@@ -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
|
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
|
-
|
57
|
-
|
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
|
data/lib/gosling/collision.rb
CHANGED
@@ -33,9 +33,11 @@ module Gosling
|
|
33
33
|
|
34
34
|
return false if shapeA === shapeB
|
35
35
|
|
36
|
-
|
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
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
118
|
+
global_pos = nil
|
119
|
+
centers_axis = nil
|
120
|
+
global_vertices = nil
|
108
121
|
if shape.instance_of?(Circle)
|
109
|
-
|
110
|
-
|
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
|
-
|
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.
|
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.
|
127
|
-
|
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.
|
133
|
-
|
134
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
363
|
+
reset_separation_axes
|
364
|
+
global_vertices = nil
|
160
365
|
|
161
366
|
unless shapeA.instance_of?(Circle)
|
162
|
-
|
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
|
-
|
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
|
-
|
171
|
-
separation_axes.push(axis) if axis
|
389
|
+
get_circle_separation_axis(shapeA, shapeB)
|
172
390
|
end
|
173
391
|
|
174
|
-
|
175
|
-
|
392
|
+
nil
|
393
|
+
ensure
|
394
|
+
global_vertices.each { |v| VectorCache.instance.recycle(v) } if global_vertices
|
176
395
|
end
|
177
396
|
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
192
|
-
|
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
|
-
|
202
|
-
|
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]
|