technohippy-Pongo 0.1.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.
@@ -0,0 +1,36 @@
1
+ require 'pongo/abstract_particle'
2
+
3
+ module Pongo
4
+ # A circle shaped particle.
5
+ class CircleParticle < AbstractParticle
6
+ attr_accessor :radius
7
+
8
+ def initialize(x, y, radius, options={})
9
+ options = {:fixed => false, :mass => 1, :elasticity => 0.3, :friction => 0}.update(options)
10
+ super(x, y, options[:fixed], options[:mass], options[:elasticity], options[:friction])
11
+ @radius = radius
12
+ end
13
+
14
+ def projection(axis)
15
+ c = @samp.dot(axis)
16
+ @interval.min = c - @radius
17
+ @interval.max = c + @radius
18
+ @interval
19
+ end
20
+ alias get_projection projection
21
+
22
+ def interval_x
23
+ @interval.min = @samp.x - @radius
24
+ @interval.max = @samp.x + @radius
25
+ @interval
26
+ end
27
+ alias get_interval_x interval_x
28
+
29
+ def interval_y
30
+ @interval.min = @samp.y - @radius
31
+ @interval.max = @samp.y + @radius
32
+ @interval
33
+ end
34
+ alias get_interval_y interval_y
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ module Pongo
2
+ class Collision
3
+ attr_accessor :vn, :vt
4
+
5
+ def initialize(vn=Vector.new, vt=Vector.new)
6
+ @vn = vn
7
+ @vt = vt
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,222 @@
1
+ require 'pongo/util/numeric_ext'
2
+ module Pongo
3
+ class CollisionDetector
4
+ class <<self
5
+ attr_accessor :cpa, :cpb, :coll_normal, :coll_depth
6
+
7
+ # Tests the collision between two objects. This initial test determines
8
+ # the multisampling state of the two particles.
9
+ def test(obj_a, obj_b)
10
+ return if obj_a == obj_b # TODO: added by ando
11
+ return if obj_a.fixed? and obj_b.fixed?
12
+
13
+ if obj_a.multisample == 0 and obj_b.multisample == 0
14
+ norm_vs_norm(obj_a, obj_b)
15
+ elsif obj_a.multisample > 0 and obj_b.multisample == 0
16
+ samp_vs_norm(obj_a, obj_b)
17
+ elsif obj_b.multisample > 0 and obj_a.multisample == 0
18
+ samp_vs_norm(obj_b, obj_a)
19
+ elsif obj_a.multisample == obj_b.multisample
20
+ samp_vs_samp(obj_a, obj_b)
21
+ else
22
+ norm_vs_norm(obj_a, obj_b)
23
+ end
24
+ end
25
+
26
+ # default test for two non-multisampled particles
27
+ def norm_vs_norm(obj_a, obj_b)
28
+ obj_a.samp.copy(obj_a.curr)
29
+
30
+ obj_b.samp.copy(obj_b.curr)
31
+ if test_types(obj_a, obj_b)
32
+ CollisionResolver.resolve(@cpa, @cpb, @coll_normal, @coll_depth)
33
+ true
34
+ else
35
+ false
36
+ end
37
+ end
38
+
39
+ # Tests two particles where one is multisampled and the other is not. Let objectA
40
+ # be the multisampled particle.
41
+ def samp_vs_norm(obj_a, obj_b)
42
+ return if norm_vs_norm(obj_a, obj_b)
43
+
44
+ s = 1 / (obj_a.multisample + 1)
45
+ t = s
46
+ obj_a.multisample.times do
47
+ obj_a.samp.set_to(
48
+ obj_a.prev.x + t * (obj_a.curr.x - obj_a.prev.x),
49
+ obj_a.prev.y + t * (obj_a.curr.y - obj_a.prev.y)
50
+ )
51
+ if test_types(obj_a, obj_b)
52
+ CollisionResolver.resolve(@cpa, @cpb, @coll_normal, @coll_depth)
53
+ return
54
+ end
55
+ t += s
56
+ end
57
+ end
58
+
59
+ # Tests two particles where both are of equal multisample rate
60
+ def samp_vs_samp(obj_a, obj_b)
61
+ return if norm_vs_norm(obj_a, obj_b)
62
+
63
+ s = 1 / (obj_a.multisample + 1)
64
+ t = s
65
+
66
+ obj_a.multisample.times do
67
+ obj_a.samp.set_to(
68
+ obj_a.prev.x + t * (obj_a.curr.x - obj_a.prev.x),
69
+ obj_a.prev.y + t * (obj_a.curr.y - obj_a.prev.y)
70
+ )
71
+ obj_b.samp.set_to(
72
+ obj_b.prev.x + t * (obj_b.curr.x - obj_b.prev.x),
73
+ obj_b.prev.y + t * (obj_b.curr.y - obj_b.prev.y)
74
+ )
75
+ if test_types(obj_a, obj_b)
76
+ CollisionResolver.resolve(@cpa, @cpb, @coll_normal, @coll_depth)
77
+ return
78
+ end
79
+ t += s
80
+ end
81
+ end
82
+
83
+ # Tests collision based on primitive type.
84
+ def test_types(obj_a, obj_b)
85
+ if obj_a.is_a?(RectangleParticle) and obj_b.is_a?(RectangleParticle)
86
+ test_obb_vs_obb(obj_a, obj_b)
87
+ elsif obj_a.is_a?(CircleParticle) and obj_b.is_a?(CircleParticle)
88
+ test_circle_vs_circle(obj_a, obj_b)
89
+ elsif obj_a.is_a?(RectangleParticle) and obj_b.is_a?(CircleParticle)
90
+ test_obb_vs_circle(obj_a, obj_b)
91
+ elsif obj_a.is_a?(CircleParticle) and obj_b.is_a?(RectangleParticle)
92
+ test_obb_vs_circle(obj_b, obj_a)
93
+ else
94
+ false
95
+ end
96
+ end
97
+
98
+ # Tests the collision between two RectangleParticles (aka OBBs). If there is a
99
+ # collision it determines its axis and depth, and then passes it off to the
100
+ # CollisionResolver for handling.
101
+ def test_obb_vs_obb(rect_a, rect_b)
102
+ @coll_depth = Numeric::POSITIVE_INFINITY
103
+ 2.times do |i|
104
+ axis_a = rect_a.axes[i]
105
+ depth_a = test_intervals(rect_a.projection(axis_a), rect_b.projection(axis_a))
106
+ return false if depth_a == 0
107
+
108
+ axis_b = rect_b.axes[i]
109
+ depth_b = test_intervals(rect_a.projection(axis_b), rect_b.projection(axis_b))
110
+ return false if depth_b == 0
111
+
112
+ abs_a = depth_a.abs
113
+ abs_b = depth_b.abs
114
+
115
+ if abs_a < @coll_depth.abs or abs_b < @coll_depth.abs
116
+ altb = abs_a < abs_b
117
+ @coll_normal = altb ? axis_a : axis_b
118
+ @coll_depth = altb ? depth_a : depth_b
119
+ end
120
+ end
121
+
122
+ @cpa = rect_a
123
+ @cpb = rect_b
124
+ true
125
+ end
126
+
127
+ # Tests the collision between a RectangleParticle (aka an OBB) and a
128
+ # CircleParticle. If there is a collision it determines its axis and depth, and
129
+ # then passes it off to the CollisionResolver.
130
+ def test_obb_vs_circle(rect_a, cir_a)
131
+ @coll_depth = Numeric::POSITIVE_INFINITY
132
+ depths = []
133
+
134
+ # first go through the axes of the rectangle
135
+ 2.times do |i|
136
+ box_axis = rect_a.axes[i]
137
+ depth = test_intervals(rect_a.projection(box_axis), cir_a.projection(box_axis))
138
+ return false if depth == 0
139
+
140
+ if depth.abs < @coll_depth.abs
141
+ @coll_normal = box_axis
142
+ @coll_depth = depth
143
+ end
144
+ depths[i] = depth
145
+ end
146
+
147
+ # determine if the circle's center is in a vertex region
148
+ r = cir_a.radius
149
+ if depths[0].abs < r and depths[1].abs < r
150
+ vertex = closest_vertex_on_obb(cir_a.samp, rect_a)
151
+
152
+ # get the distance from the closest vertex on rect to circle center
153
+ @coll_normal = vertex - cir_a.samp
154
+ mag = @coll_normal.magnitude
155
+ @coll_depth = r - mag
156
+
157
+ if @coll_depth > 0
158
+ # there is a collision in one of the vertex regions
159
+ @coll_normal.div!(mag)
160
+ else
161
+ # rect_a is in vertex region, but is not colliding
162
+ return false
163
+ end
164
+ end
165
+ @cpa = rect_a
166
+ @cpb = cir_a
167
+ true
168
+ end
169
+
170
+ # Tests the collision between two CircleParticles. If there is a collision it
171
+ # determines its axis and depth, and then passes it off to the CollisionResolver
172
+ # for handling.
173
+ def test_circle_vs_circle(cir_a, cir_b)
174
+ depth_x = test_intervals(cir_a.interval_x, cir_b.interval_x)
175
+ return false if depth_x == 0
176
+
177
+ depth_y = test_intervals(cir_a.interval_y, cir_b.interval_y)
178
+ return false if depth_y == 0
179
+
180
+ @coll_normal = cir_a.samp - cir_b.samp
181
+ mag = @coll_normal.magnitude
182
+ @coll_depth = cir_a.radius + cir_b.radius - mag
183
+
184
+ if @coll_depth > 0
185
+ @coll_normal.div!(mag)
186
+ @cpa = cir_a
187
+ @cpb = cir_b
188
+ true
189
+ else
190
+ false
191
+ end
192
+ end
193
+
194
+ # Returns 0 if intervals do not overlap. Returns smallest depth if they do.
195
+ def test_intervals(interval_a, interval_b)
196
+ return 0 if interval_a.max < interval_b.min
197
+ return 0 if interval_b.max < interval_a.min
198
+
199
+ len_a = interval_b.max - interval_a.min
200
+ len_b = interval_b.min - interval_a.max
201
+
202
+ len_a.abs < len_b.abs ? len_a : len_b
203
+ end
204
+
205
+ # Returns the location of the closest vertex on rect to point
206
+ def closest_vertex_on_obb(point, rect)
207
+ d = point - rect.samp
208
+ q = rect.samp.dup
209
+
210
+ 2.times do |i|
211
+ dist = d.dot(rect.axes[i])
212
+ if dist >= 0; dist = rect.extents[i]
213
+ elsif dist < 0; dist = -rect.extents[i]
214
+ end
215
+
216
+ q.plus!(rect.axes[i] * dist)
217
+ end
218
+ q
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,23 @@
1
+ require 'pongo/spring_constraint_particle'
2
+
3
+ module Pongo
4
+ class CollisionEvent
5
+ COLLIDE = :collide
6
+ FIRST_COLLIDE = :first_collide
7
+
8
+ attr_accessor :type, :colliding_item
9
+
10
+ def initialize(type, colliding_item=nil)
11
+ @type = type
12
+ @colliding_item = colliding_item
13
+ end
14
+
15
+ def colliding_item
16
+ if @colliding_item.is_a?(SpringConstraintParticle)
17
+ @colliding_item.parent
18
+ else
19
+ @colliding_item
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ require 'pongo/util/math_util'
2
+
3
+ module Pongo
4
+ class CollisionResolver
5
+ def self.resolve(pa, pb, normal, depth)
6
+ mtd = normal * depth
7
+ te = pa.elasticity + pb.elasticity
8
+ sum_inv_mass = pa.inv_mass + pb.inv_mass
9
+
10
+ # the total friction in a collision is combined but clamped to [0,1]
11
+ tf = MathUtil.clamp(1 - (pa.friction + pb.friction), 0, 1)
12
+
13
+ # get the collision components, vn and vt
14
+ ca = pa.components(normal)
15
+ cb = pb.components(normal)
16
+
17
+ # calculate the coefficient of restitution as the normal component
18
+ vn_a = (cb.vn * ((te + 1) * pa.inv_mass) +
19
+ (ca.vn * (pb.inv_mass - te * pa.inv_mass))) / sum_inv_mass
20
+ vn_b = (ca.vn * ((te + 1) * pb.inv_mass) +
21
+ (cb.vn * (pa.inv_mass - te * pb.inv_mass))) / sum_inv_mass
22
+
23
+ # apply friction to the tangental component
24
+ ca.vt.mult!(tf)
25
+ cb.vt.mult!(tf)
26
+
27
+ # scale the mtd by the ratio of the masses. heavier particles move less
28
+ mtd_a = mtd * ( pa.inv_mass / sum_inv_mass)
29
+ mtd_b = mtd * (-pb.inv_mass / sum_inv_mass)
30
+
31
+ # add the tangental component to the normal component for the new velocity
32
+ vn_a.plus!(ca.vt)
33
+ vn_b.plus!(cb.vt)
34
+
35
+ pa.resolve_collision(mtd_a, vn_a, normal, depth, -1, pb)
36
+ pb.resolve_collision(mtd_b, vn_b, normal, depth, 1, pa)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,52 @@
1
+ require 'pongo/abstract_collection'
2
+ require 'pongo/util/numeric_ext'
3
+
4
+ module Pongo
5
+ # The Composite class can contain Particles, and Constraints. Composites can be added
6
+ # to a parent Group, along with Particles and Constraints. Members of a Composite
7
+ # are not checked for collision with one another, internally.
8
+ class Composite < AbstractCollection
9
+ attr_accessor :delta
10
+
11
+ def initialize
12
+ super
13
+ @delta = Vector.new
14
+ end
15
+
16
+ # Rotates the Composite to an angle specified in radians, around a given center
17
+ def rotate_by_radian(angle_radians, center)
18
+ particles.each do |p|
19
+ radius = p.center.distance(center)
20
+ angle = relative_angle(center, p.center) + angle_radians
21
+ p.px = (Math.cos(angle) * radius) + center.x
22
+ p.py = (Math.sin(angle) * radius) + center.y
23
+ end
24
+ end
25
+
26
+ # Rotates the Composite to an angle specified in degrees, around a given center
27
+ def rotate_by_angle(angle_degrees, center)
28
+ rotate_by_radian(angle_degrees * Numeric::PI_OVER_ONE_EIGHTY, center)
29
+ end
30
+
31
+ # The fixed state of the Composite. Setting this value to true or false will
32
+ # set all of this Composite's component particles to that value. Getting this
33
+ # value will return false if any of the component particles are not fixed.
34
+ def fixed?
35
+ particles.each do |p|
36
+ return false unless p.fixed?
37
+ end
38
+ true
39
+ end
40
+ alias fixed fixed?
41
+
42
+ def fixed=(b)
43
+ particles.each {|p| p.fixed = b}
44
+ end
45
+
46
+ def relative_angle(center, p)
47
+ @delta.set_to(p.x - center.x, p.y - center.y)
48
+ Math.atan2(@delta.y, @delta.x)
49
+ end
50
+ alias get_relative_angle relative_angle
51
+ end
52
+ end
@@ -0,0 +1,7 @@
1
+ module Pongo
2
+ module Container
3
+ class Container
4
+ attr_accessor :renderer, :logger
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ require 'pongo/container/container'
2
+ require 'pongo/renderer/shoes_renderer'
3
+ require 'pongo/logger/shoes_logger'
4
+
5
+ module Pongo
6
+ module Container
7
+ class ShoesContainer < Container
8
+ def initialize(shoes)
9
+ @renderer = Renderer::ShoesRenderer.new(shoes)
10
+ @logger = Logger::ShoesLogger.new(shoes)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,166 @@
1
+ module Pongo
2
+ # The Group class can contain Particles, Constraints, and Composites. Groups can be
3
+ # assigned to be checked for collision with other Groups or internally.
4
+ class Group < AbstractCollection
5
+ attr_accessor :composites, :collision_list, :collide_internal
6
+
7
+ def initialize(collide_internal=false)
8
+ super()
9
+ @composites = []
10
+ @collision_list = []
11
+ self.collide_internal = collide_internal
12
+ end
13
+
14
+ def init
15
+ super
16
+ @composites.each {|c| c.init}
17
+ end
18
+
19
+ def collide_internal!
20
+ @collide_internal = true
21
+ end
22
+
23
+ def <<(item)
24
+ case item
25
+ when Composite
26
+ add_composite(item)
27
+ when Group
28
+ add_collidable(item)
29
+ when Array
30
+ add_collidable_list(item)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ def add_composite(c)
37
+ @composites << c
38
+ c.is_parented = true
39
+ c.init if @is_parented
40
+ end
41
+
42
+ def remove_composite(c)
43
+ if @composites.delete(c)
44
+ c.is_parented = false
45
+ c.cleanup
46
+ end
47
+ end
48
+
49
+ def draw
50
+ super
51
+ @composites.each {|c| c.draw}
52
+ end
53
+
54
+ def add_collidable(g)
55
+ @collision_list << g
56
+ end
57
+
58
+ def remove_collidable(g)
59
+ @collision_list.delete(g)
60
+ end
61
+
62
+ def add_collidable_list(*list)
63
+ list = list.first if list.first.is_a?(Array)
64
+ list.each {|g| @collision_list << g}
65
+ end
66
+
67
+ def get_all
68
+ @particles + @constraints + @composites
69
+ end
70
+ alias all get_all
71
+
72
+ def clieanup
73
+ super
74
+ @composites.each {|c| c.cleanup}
75
+ end
76
+
77
+ def integrate(dt2)
78
+ super
79
+ @composites.each {|cmp| cmp.integrate(dt2)}
80
+ end
81
+
82
+ def satisfy_constraints
83
+ super
84
+ @composites.each {|cmp| cmp.satisfy_constraints}
85
+ end
86
+
87
+ def check_collisions
88
+ check_collision_group_internal if @collide_internal
89
+ @collision_list.each {|g| check_collision_vs_group(g) if g}
90
+ end
91
+
92
+ def check_collision_group_internal
93
+ # check collisions not in composites
94
+ check_internal_collisions
95
+
96
+ # for every composite in this Group..
97
+ valid_composites.each do |ca|
98
+ # .. vs non composite particles and constraints in this group
99
+ ca.check_collisions_vs_collection(self)
100
+
101
+ # ...vs every other composite in this Group
102
+ valid_composites.each do |cb|
103
+ ca.check_collisions_vs_collection(cb)
104
+ end
105
+ end
106
+ end
107
+
108
+ def check_collision_vs_group(g)
109
+ # check particles and constraints not in composites of either group
110
+ check_collisions_vs_collection(g)
111
+
112
+ # for every composite in this group..
113
+ valid_composites.each do |c|
114
+ # check vs the particles and constraints of g
115
+ c.check_collisions_vs_collection(g)
116
+
117
+ # check vs composites of g
118
+ g.valid_composites.each do |g|
119
+ c.check_collections_vs_collection(g)
120
+ end
121
+ end
122
+
123
+ # check particles and constraints of this group vs the composites of g
124
+ g.valid_composites.each do |gc|
125
+ check_collisions_vs_collection(gc)
126
+ end
127
+ end
128
+
129
+ def valid_composites
130
+ @composites.select{|c| not c.nil?}
131
+ end
132
+
133
+ def circle(x, y, radius, options={})
134
+ cir = CircleParticle.new(x, y, radius, options)
135
+ cir.always_redraw! if options[:always_redraw]
136
+ self << cir
137
+ cir
138
+ end
139
+
140
+ def rectangle(x, y, width, height, options={})
141
+ rect = RectangleParticle.new(x, y, width, height, options)
142
+ rect.always_redraw! if options[:always_redraw]
143
+ self << rect
144
+ rect
145
+ end
146
+
147
+ def wheel(x, y, radius, options={})
148
+ wh = WheelParticle.new(x, y, radius, options)
149
+ wh.always_redraw! if options[:always_redraw]
150
+ self << wh
151
+ wh
152
+ end
153
+
154
+ def connect(p1, p2, options={})
155
+ con = SpringConstraint.new(p1, p2, options)
156
+ self << con
157
+ con
158
+ end
159
+
160
+ def brace(p1, p2, p3, min_ang, max_ang, options={})
161
+ ang = AngularConstraint.new(p1, p2, p3, min_ang, max_ang, options)
162
+ self << ang
163
+ ang
164
+ end
165
+ end
166
+ end