gosling 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2e286ba691604561ac6676e60e49699cd3167587
4
+ data.tar.gz: 6e2083451057aa524bed3f13012134e829a55f80
5
+ SHA512:
6
+ metadata.gz: 6232f177ad08bc4869af947c3c6748c004a052d47ca92247a8d0c54910117a6c8984aa5c5d7d35edb109e31ca5281536fc52c5013928b43bdd958e5edb1c6c44
7
+ data.tar.gz: 35fe442a079c785663f73640b006171c57872fe0c189bf55c9aa1146fd88c336f4cdabf52db410f3a84361fa862081dff505391c563e9f1f80e16729f1514a5f
data/lib/gosling.rb ADDED
@@ -0,0 +1,4 @@
1
+ Dir.glob(File.join(File.dirname(__FILE__), 'gosling/*.rb')).each { |file| require_relative file }
2
+
3
+ module Gosling
4
+ end
@@ -0,0 +1,235 @@
1
+ require_relative 'transform.rb'
2
+
3
+ require 'gosu'
4
+
5
+ module Gosling
6
+ class Actor
7
+ attr_reader :transform, :parent, :children, :window
8
+ attr_accessor :is_visible, :is_tangible, :are_children_visible, :are_children_tangible, :is_mask, :color
9
+
10
+ def initialize(window)
11
+ @window = window
12
+ @transform = Transform.new
13
+ @parent = nil
14
+ @children = []
15
+ @is_visible = true
16
+ @is_tangible = true
17
+ @are_children_visible = true
18
+ @are_children_tangible = true
19
+ @is_mask = false
20
+ @color = Gosu::Color.from_hsv(rand(360), rand(), rand())
21
+ end
22
+
23
+ def inspect
24
+ "#<#{self.class}:#{self.object_id}>"
25
+ end
26
+
27
+ def parent=(parent)
28
+ return if parent == @parent
29
+ unless parent
30
+ raise Gosling::InheritanceError.new("You should use Actor.remove_child() instead of setting the parent directly.") if @parent.has_child?(self)
31
+ end
32
+ @parent = parent
33
+ if @parent
34
+ raise Gosling::InheritanceError.new("You should use Actor.add_child() instead of setting the parent directly.") unless @parent.has_child?(self)
35
+ end
36
+ end
37
+
38
+ def add_child(child)
39
+ return if @children.include?(child)
40
+ raise Gosling::InheritanceError.new("An Actor cannot be made a child of itself.") if child == self
41
+ ancestor = parent
42
+ until ancestor.nil?
43
+ raise Gosling::InheritanceError.new("Adding a child's ancestor as a child would create a circular reference.") if child == ancestor
44
+ ancestor = ancestor.parent
45
+ end
46
+
47
+ child.parent.remove_child(child) if child.parent
48
+ @children.push(child)
49
+ child.parent = self
50
+ end
51
+
52
+ def remove_child(child)
53
+ return unless @children.include?(child)
54
+
55
+ @children.delete(child)
56
+ child.parent = nil
57
+ end
58
+
59
+ def has_child?(child)
60
+ @children.include?(child)
61
+ end
62
+
63
+ def x
64
+ @transform.translation[0]
65
+ end
66
+
67
+ def x=(val)
68
+ array = @transform.translation.to_a
69
+ array[0] = val
70
+ @transform.set_translation(Vector.elements(array))
71
+ end
72
+
73
+ def y
74
+ @transform.translation[1]
75
+ end
76
+
77
+ def y=(val)
78
+ array = @transform.translation.to_a
79
+ array[1] = val
80
+ @transform.set_translation(Vector.elements(array))
81
+ end
82
+
83
+ def pos
84
+ Vector[x, y, 0]
85
+ end
86
+
87
+ def pos=(val)
88
+ array = @transform.translation.to_a
89
+ array[0] = val[0]
90
+ array[1] = val[1]
91
+ @transform.set_translation(Vector.elements(array))
92
+ end
93
+
94
+ def center_x
95
+ @transform.center[0]
96
+ end
97
+
98
+ def center_x=(val)
99
+ array = @transform.center.to_a
100
+ array[0] = val
101
+ @transform.set_center(Vector.elements(array))
102
+ end
103
+
104
+ def center_y
105
+ @transform.center[1]
106
+ end
107
+
108
+ def center_y=(val)
109
+ array = @transform.center.to_a
110
+ array[1] = val
111
+ @transform.set_center(Vector.elements(array))
112
+ end
113
+
114
+ def scale_x
115
+ @transform.scale[0]
116
+ end
117
+
118
+ def scale_x=(val)
119
+ array = @transform.scale.to_a
120
+ array[0] = val
121
+ @transform.set_scale(Vector.elements(array))
122
+ end
123
+
124
+ def scale_y
125
+ @transform.scale[1]
126
+ end
127
+
128
+ def scale_y=(val)
129
+ array = @transform.scale.to_a
130
+ array[1] = val
131
+ @transform.set_scale(Vector.elements(array))
132
+ end
133
+
134
+ def rotation
135
+ @transform.rotation
136
+ end
137
+
138
+ def rotation=(val)
139
+ @transform.set_rotation(val)
140
+ end
141
+
142
+ def render(matrix)
143
+ end
144
+
145
+ def draw(matrix = nil)
146
+ matrix ||= Matrix.identity(3)
147
+ transform = matrix * @transform.to_matrix
148
+ render(transform) if @is_visible
149
+ if @are_children_visible
150
+ @children.each { |child| child.draw(transform) }
151
+ end
152
+ end
153
+
154
+ def is_point_in_bounds(point)
155
+ false
156
+ end
157
+
158
+ def get_actor_at(point)
159
+ hit = nil
160
+ if @are_children_tangible
161
+ @children.reverse_each do |child|
162
+ hit = child.get_actor_at(point)
163
+ if hit
164
+ break if @is_mask
165
+ return hit
166
+ end
167
+ end
168
+ end
169
+ hit = self if hit == nil && @is_tangible && is_point_in_bounds(point)
170
+ hit = @parent if @is_mask && hit == self
171
+ hit
172
+ end
173
+
174
+ def get_actors_at(point)
175
+ actors = []
176
+ if @are_children_tangible
177
+ @children.reverse_each do |child|
178
+ actors |= child.get_actors_at(point)
179
+ end
180
+ end
181
+ actors.push(self) if @is_tangible && is_point_in_bounds(point)
182
+ actors.uniq!
183
+ if @is_mask
184
+ actors.map! { |actor| (actor == self) ? @parent : actor }
185
+ end
186
+ actors
187
+ end
188
+
189
+ def get_global_transform
190
+ tf = if parent
191
+ parent.get_global_transform * @transform.to_matrix
192
+ else
193
+ @transform.to_matrix
194
+ end
195
+ return tf
196
+ end
197
+
198
+ def get_global_position
199
+ tf = get_global_transform
200
+ Transform.transform_point(tf, Vector[@transform.center[0], @transform.center[1], 0])
201
+ end
202
+
203
+ def alpha
204
+ @color.alpha
205
+ end
206
+
207
+ def alpha=(val)
208
+ @color.alpha = val
209
+ end
210
+
211
+ def red
212
+ @color.red
213
+ end
214
+
215
+ def red=(val)
216
+ @color.red = val
217
+ end
218
+
219
+ def green
220
+ @color.green
221
+ end
222
+
223
+ def green=(val)
224
+ @color.green = val
225
+ end
226
+
227
+ def blue
228
+ @color.blue
229
+ end
230
+
231
+ def blue=(val)
232
+ @color.blue = val
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,48 @@
1
+ require_relative 'actor.rb'
2
+ require_relative 'collision.rb'
3
+
4
+ module Gosling
5
+ class Circle < Actor
6
+ RENDER_VERTEX_COUNT = 16
7
+
8
+ attr_reader :radius
9
+
10
+ def initialize(window)
11
+ super(window)
12
+ @radius = 0
13
+ end
14
+
15
+ def radius=(val)
16
+ raise ArgumentError.new("Circle.radius cannot be negative") if val < 0
17
+ @radius = val
18
+ end
19
+
20
+ def get_point_at_angle(radians)
21
+ raise ArgumentError.new("Expected Numeric, but received #{radians.inspect}!") unless radians.is_a?(Numeric)
22
+ Vector[Math.cos(radians) * @radius, Math.sin(radians) * @radius, 0]
23
+ end
24
+
25
+ def render(matrix)
26
+ local_vertices = (0...RENDER_VERTEX_COUNT).map do |i|
27
+ get_point_at_angle(Math::PI * 2 * i / RENDER_VERTEX_COUNT)
28
+ end
29
+ global_vertices = local_vertices.map { |v| Transform.transform_point(matrix, v) }
30
+ i = 2
31
+ while i < global_vertices.length
32
+ v0 = global_vertices[0]
33
+ v1 = global_vertices[i-1]
34
+ v2 = global_vertices[i]
35
+ @window.draw_triangle(
36
+ v0[0].to_f, v0[1].to_f, @color,
37
+ v1[0].to_f, v1[1].to_f, @color,
38
+ v2[0].to_f, v2[1].to_f, @color,
39
+ )
40
+ i += 1
41
+ end
42
+ end
43
+
44
+ def is_point_in_bounds(point)
45
+ Collision.is_point_in_shape?(point, self)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,126 @@
1
+ require 'singleton'
2
+
3
+ module Gosling
4
+ class Collision
5
+ # 11.4 - 6.7 (get_separation_axes) - 4.7 (project_onto_axis)
6
+ def self.test(shapeA, shapeB)
7
+ return false if shapeA.instance_of?(Actor) || shapeB.instance_of?(Actor)
8
+
9
+ return false if shapeA === shapeB
10
+
11
+ separation_axes = get_separation_axes(shapeA, shapeB)
12
+
13
+ separation_axes.each do |axis|
14
+ projectionA = project_onto_axis(shapeA, axis)
15
+ projectionB = project_onto_axis(shapeB, axis)
16
+ return false unless projections_overlap?(projectionA, projectionB)
17
+ end
18
+
19
+ return true
20
+ end
21
+
22
+ def self.is_point_in_shape?(point, shape)
23
+ raise ArgumentError.new("Collision.get_normal() requires a point and an actor") unless point.is_a?(Vector) && point.size == 3 && shape.is_a?(Actor)
24
+
25
+ return false if shape.instance_of?(Actor)
26
+
27
+ separation_axes = []
28
+ if shape.instance_of?(Circle)
29
+ centers_axis = point - shape.get_global_position
30
+ separation_axes.push(centers_axis) if centers_axis && centers_axis.magnitude > 0
31
+ else
32
+ separation_axes.concat(get_polygon_separation_axes(shape.get_global_vertices))
33
+ end
34
+
35
+ separation_axes.each do |axis|
36
+ shape_projection = project_onto_axis(shape, axis)
37
+ point_projection = point.inner_product(axis)
38
+ return false unless shape_projection.min <= point_projection && point_projection <= shape_projection.max
39
+ end
40
+
41
+ return true
42
+ end
43
+
44
+ def self.get_normal(vector)
45
+ raise ArgumentError.new("Collision.get_normal() requires a length 3 vector") unless vector.is_a?(Vector) && vector.size == 3
46
+ raise ArgumentError.new("Cannot determine normal of zero-length vector") if vector.magnitude == 0
47
+
48
+ Vector[-vector[1], vector[0], 0]
49
+ end
50
+
51
+ def self.get_polygon_separation_axes(vertices)
52
+ unless vertices.is_a?(Array) && vertices.reject { |v| v.is_a?(Vector) && v.size == 3 }.empty?
53
+ raise ArgumentError.new("Collission.get_polygon_separation_axes() expects an array of vectors similar to that produced by Polygon.get_vertices")
54
+ end
55
+
56
+ axes = (0...vertices.length).map do |i|
57
+ axis = vertices[(i + 1) % vertices.length] - vertices[i]
58
+ (axis.magnitude > 0) ? get_normal(axis).normalize : nil
59
+ end
60
+ axes.compact
61
+ end
62
+
63
+ def self.get_circle_separation_axis(circleA, circleB)
64
+ unless circleA.is_a?(Actor) && circleB.is_a?(Actor)
65
+ raise ArgumentError.new("Collision.get_circle_separation_axis() expects two circles")
66
+ end
67
+ axis = circleB.get_global_position - circleA.get_global_position
68
+ (axis.magnitude > 0) ? axis.normalize : nil
69
+ end
70
+
71
+ # 4.8 - 2.6 - 1.6 - .4
72
+ def self.get_separation_axes(shapeA, shapeB)
73
+ unless shapeA.is_a?(Actor) && !shapeA.instance_of?(Actor)
74
+ raise ArgumentError.new("Expected a child of the Actor class, but received #{shapeA.inspect}!")
75
+ end
76
+
77
+ unless shapeB.is_a?(Actor) && !shapeB.instance_of?(Actor)
78
+ raise ArgumentError.new("Expected a child of the Actor class, but received #{shapeB.inspect}!")
79
+ end
80
+
81
+ separation_axes = []
82
+
83
+ unless shapeA.instance_of?(Circle)
84
+ separation_axes.concat(get_polygon_separation_axes(shapeA.get_global_vertices))
85
+ end
86
+
87
+ unless shapeB.instance_of?(Circle)
88
+ separation_axes.concat(get_polygon_separation_axes(shapeB.get_global_vertices))
89
+ end
90
+
91
+ if shapeA.instance_of?(Circle) || shapeB.instance_of?(Circle)
92
+ axis = get_circle_separation_axis(shapeA, shapeB)
93
+ separation_axes.push(axis) if axis
94
+ end
95
+
96
+ separation_axes.map! { |v| v[0] < 0 ? v * -1 : v }
97
+ separation_axes.uniq
98
+ end
99
+
100
+ def self.project_onto_axis(shape, axis)
101
+ raise ArgumentError.new("Expected Actor, but received #{shape.inspect}!") unless shape.is_a?(Actor)
102
+ raise ArgumentError.new("Expected Vector, but received #{shape.inspect}!") unless axis.is_a?(Vector)
103
+
104
+ global_vertices = if shape.instance_of?(Circle)
105
+ global_tf = shape.get_global_transform
106
+ local_axis = global_tf.inverse * Vector[axis[0], axis[1], 0]
107
+ v = shape.get_point_at_angle(Math.atan2(local_axis[1], local_axis[0]))
108
+ [v, v * -1].map { |vertex| Transform.transform_point(global_tf, vertex) }
109
+ else
110
+ shape.get_global_vertices
111
+ end
112
+
113
+ projections = global_vertices.map { |vertex| vertex.inner_product(axis) }.sort
114
+ [projections.first, projections.last]
115
+ end
116
+
117
+ def self.projections_overlap?(a, b)
118
+ raise ArgumentError.new("Collision.projections_overlap?() expects arrays") unless a.is_a?(Array) && b.is_a?(Array)
119
+ raise ArgumentError.new("Collision.projections_overlap?() projection arrays must be length 2") unless a.length == 2 && b.length == 2
120
+
121
+ a.sort! if a[0] > a[1]
122
+ b.sort! if b[0] > b[1]
123
+ (a[0] <= b[1] && (b[1] <= a[1] || b[0] <= a[0])) || (b[0] <= a[1] && (a[0] <= b[0] || a[1] <= b[1]))
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,17 @@
1
+ require 'singleton'
2
+
3
+ module Gosling
4
+ class ImageLibrary
5
+ @@cache = {}
6
+
7
+ include Singleton
8
+
9
+ def self.get(filename)
10
+ raise ArgumentError.new("File not found: '#{filename}' in '#{Dir.pwd}'") unless File.exists?(filename)
11
+ unless @@cache.has_key?(filename)
12
+ @@cache[filename] = Gosu::Image.new(filename, tileable: true)
13
+ end
14
+ @@cache[filename]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ module Gosling
2
+ class InheritanceError < StandardError
3
+ end
4
+ end