gosling 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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