percolator 0.0.1.prealpha
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 +15 -0
- data/README.md +74 -0
- data/lib/percolator.rb +61 -0
- data/lib/percolator/behaviors/behavior.rb +14 -0
- data/lib/percolator/behaviors/collision.rb +78 -0
- data/lib/percolator/behaviors/edge_bound.rb +29 -0
- data/lib/percolator/integrators/euler.rb +28 -0
- data/lib/percolator/particle.rb +64 -0
- data/lib/percolator/vector.rb +113 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MzJlYTY2MGQ5OWIwNzljYmIzZmNhYTBmZTdiZWQ2MDY4ZTdkN2NkNg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZDI1ODg2ODJiOTk0MjM0MmNjYTZlMTQzNjhlOGQ4NGFkZjhjODg0Nw==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NjRmMzFiOGRiODZmYmYyOTVlOGVkNzk1ZjBkNGM1NmViNjFlMmJiNDFkZDFm
|
10
|
+
YzVhY2I0ZmEzYTg2YTg3ODYyM2FjZTU3NzE5MDRmNTBjMjAzYTlmYzAzY2Vk
|
11
|
+
MWVlYzdjNzQ0NmNjNDVkYWJjZTE4YmJlNzZjNDQzMWU2ZDYzNzU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OTJiOTY0YTkzYTUzYWQzMjEyNThkOTFiNjQ2Zjc3YzRiOGNmMjJjMWY3MmQ5
|
14
|
+
NzU2MTk5ZDFlNjFmOTA4MjA0OGY4NThkNTEzZjA0YzcyNTRhMWJkMGE4MmM1
|
15
|
+
Y2FiNTgzNWU2MzBjNDgzNjFkNjNjOTAwYzA4MWIyMGUzNjk1MWM=
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Percolator
|
2
|
+
|
3
|
+
Simple particle-based physics engine
|
4
|
+
|
5
|
+
__Still in pre-alpha state, not production ready__
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
This example will build a 50 lightweight and 50 heavyweight colliding particles in a bounded box (see more examples in `spec/percolator_spec.rb`):
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
percolator = Percolator.new
|
13
|
+
collision = Percolator::Behaviors::Collision.new
|
14
|
+
edge_bound = Percolator::Behaviors::EdgeBound.new(
|
15
|
+
Percolator::Vector.new(0, 0), # min
|
16
|
+
Percolator::Vector.new(WIDTH, HEIGHT) # max
|
17
|
+
)
|
18
|
+
50.times do
|
19
|
+
Percolator::Particle.new(
|
20
|
+
radius: 7,
|
21
|
+
mass: 1.0,
|
22
|
+
pos: Percolator::Vector.new(rand(WIDTH), rand(HEIGHT)),
|
23
|
+
vel: Percolator::Vector.new(rand(SPEED_RANGE), rand(SPEED_RANGE))
|
24
|
+
).tap do |p|
|
25
|
+
percolator.add_particle(p)
|
26
|
+
collision.add_particle(p)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
50.times do
|
30
|
+
Percolator::Particle.new(
|
31
|
+
radius: 9,
|
32
|
+
mass: 2.0,
|
33
|
+
pos: Percolator::Vector.new(rand(WIDTH), rand(HEIGHT)),
|
34
|
+
vel: Percolator::Vector.new(rand(SPEED_RANGE), rand(SPEED_RANGE))
|
35
|
+
).tap do |p|
|
36
|
+
percolator.add_particle(p)
|
37
|
+
collision.add_particle(p)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
percolator.add_behavior(collision)
|
42
|
+
percolator.add_behavior(edge_bound)
|
43
|
+
```
|
44
|
+
|
45
|
+
To simulate and save the state of each step:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
frames = []
|
49
|
+
120.times do
|
50
|
+
frames.push(percolator.to_h);
|
51
|
+
percolator.step
|
52
|
+
end
|
53
|
+
json = JSON.generate(content)
|
54
|
+
```
|
55
|
+
|
56
|
+
## Testing
|
57
|
+
|
58
|
+
Run `bundle exec guard` to automatically run the tests. If you want to see the actual simulation, start a local server (by running something like `python -m SimpleHTTPServer 8000`) You can then open `http://localhost:8000/spec/preview.html` to see animated demos.
|
59
|
+
|
60
|
+
## Todo
|
61
|
+
|
62
|
+
- More behaviors
|
63
|
+
- Benchmarking
|
64
|
+
- Algorithms
|
65
|
+
- MRI vs Jruby vs Rubinius
|
66
|
+
- GC optimization
|
67
|
+
|
68
|
+
## Credits
|
69
|
+
|
70
|
+
- Heavily influenced by [Soulwire's Coffee-Physics](http://github.com/soulwire/Coffee-Physics)
|
71
|
+
|
72
|
+
## License
|
73
|
+
|
74
|
+
Percolator is released under the [MIT License](http://opensource.org/licenses/MIT).
|
data/lib/percolator.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Percolator
|
4
|
+
attr_reader :particles
|
5
|
+
|
6
|
+
def initialize(integrator = Integrators::Euler)
|
7
|
+
@integrator = integrator
|
8
|
+
@viscosity = 0.005
|
9
|
+
@drag = 1.0 - @viscosity
|
10
|
+
@precision = 4 # Iterations per step
|
11
|
+
@dt = 1.0 / @precision
|
12
|
+
|
13
|
+
@behaviors = [] # Global behaviors
|
14
|
+
@particles = []
|
15
|
+
@springs = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def step(steps = 1)
|
19
|
+
steps.times do
|
20
|
+
integrate
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_particle(p)
|
25
|
+
@particles.push(p)
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_behavior(behavior)
|
29
|
+
@behaviors.push(behavior)
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_h
|
33
|
+
{ particles: @particles.map(&:to_h) }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def integrate
|
39
|
+
@particles.each_with_index do |p, index|
|
40
|
+
@behaviors.each { |b| b.apply(p, @dt, index) }
|
41
|
+
p.update(@dt)
|
42
|
+
end
|
43
|
+
|
44
|
+
@integrator.integrate(@particles, @dt, @drag)
|
45
|
+
|
46
|
+
# @springs.each do |s|
|
47
|
+
# s.apply()
|
48
|
+
# end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# core
|
53
|
+
require 'percolator/vector'
|
54
|
+
require 'percolator/particle'
|
55
|
+
|
56
|
+
# behaviors
|
57
|
+
require 'percolator/behaviors/collision'
|
58
|
+
require 'percolator/behaviors/edge_bound'
|
59
|
+
|
60
|
+
# integrators
|
61
|
+
require 'percolator/integrators/euler'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class Percolator
|
2
|
+
class Behaviors
|
3
|
+
class Collision
|
4
|
+
def initialize(useMass = true)
|
5
|
+
@pool = []
|
6
|
+
# Delta between particle positions.
|
7
|
+
@delta = Vector.new
|
8
|
+
|
9
|
+
@temp_vel1 = Vector.new
|
10
|
+
@temp_vel2 = Vector.new
|
11
|
+
@rotated_vel1 = Vector.new
|
12
|
+
@rotated_vel2 = Vector.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_particle(p)
|
16
|
+
@pool.push(p)
|
17
|
+
end
|
18
|
+
|
19
|
+
def apply(p1, dt, index)
|
20
|
+
@pool[index...@pool.length].each do |p2|
|
21
|
+
unless p1 == p2
|
22
|
+
dist = p1.pos.dist(p2.pos)
|
23
|
+
radii = p1.radius + p2.radius
|
24
|
+
|
25
|
+
if dist <= radii
|
26
|
+
# Heavily lifted from http://processing.org/learning/topics/circlecollision.html
|
27
|
+
delta = p1.pos.dup.sub(p2.pos)
|
28
|
+
theta = Math.atan2(delta.y, delta.x)
|
29
|
+
sine = Math.sin(theta)
|
30
|
+
cosine = Math.cos(theta)
|
31
|
+
|
32
|
+
# rotate temporary velocities
|
33
|
+
@temp_vel1.set(
|
34
|
+
(cosine * p1.vel.x) + sine * p1.vel.y,
|
35
|
+
(cosine * p1.vel.y) - sine * p1.vel.x
|
36
|
+
)
|
37
|
+
@temp_vel2.set(
|
38
|
+
(cosine * p2.vel.x) + sine * p2.vel.y,
|
39
|
+
(cosine * p2.vel.y) - sine * p2.vel.x
|
40
|
+
)
|
41
|
+
|
42
|
+
# Now that velocities are rotated, you can use 1D
|
43
|
+
# conservation of momentum equations to calculate
|
44
|
+
# the final velocity along the x-axis.
|
45
|
+
|
46
|
+
@rotated_vel1.set(
|
47
|
+
((p1.mass - p2.mass) * @temp_vel1.x + 2 * p2.mass * @temp_vel2.x) / (p1.mass + p2.mass),
|
48
|
+
@temp_vel1.y
|
49
|
+
)
|
50
|
+
@rotated_vel2.set(
|
51
|
+
((p2.mass - p1.mass) * @temp_vel2.x + 2 * p1.mass * @temp_vel1.x) / (p1.mass + p2.mass),
|
52
|
+
@temp_vel2.y
|
53
|
+
)
|
54
|
+
|
55
|
+
# update velocities
|
56
|
+
p1.vel.set(
|
57
|
+
cosine * @rotated_vel1.x - sine * @rotated_vel1.y,
|
58
|
+
cosine * @rotated_vel1.y + sine * @rotated_vel1.x
|
59
|
+
)
|
60
|
+
p2.vel.set(
|
61
|
+
cosine * @rotated_vel2.x - sine * @rotated_vel2.y,
|
62
|
+
cosine * @rotated_vel2.y + sine * @rotated_vel2.x
|
63
|
+
)
|
64
|
+
|
65
|
+
# make sure they aren't overlapping anymore
|
66
|
+
overlap = radii - dist
|
67
|
+
mass_ratio = p1.mass / (p1.mass + p2.mass)
|
68
|
+
p1.pos.x += cosine * overlap * mass_ratio
|
69
|
+
p1.pos.y += sine * overlap * mass_ratio
|
70
|
+
p2.pos.x -= cosine * overlap * (1 - mass_ratio)
|
71
|
+
p2.pos.y -= sine * overlap * (1 - mass_ratio)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Percolator
|
2
|
+
class Behaviors
|
3
|
+
class EdgeBound
|
4
|
+
def initialize(min, max)
|
5
|
+
@min = min
|
6
|
+
@max = max
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply(p, dt, index)
|
10
|
+
if p.pos.x - p.radius < @min.x
|
11
|
+
p.pos.x = @min.x + p.radius
|
12
|
+
p.vel.x = p.vel.x.abs
|
13
|
+
elsif p.pos.x + p.radius > @max.x
|
14
|
+
p.pos.x = @max.x - p.radius
|
15
|
+
p.vel.x = -1 * p.vel.x.abs
|
16
|
+
end
|
17
|
+
|
18
|
+
if p.pos.y - p.radius < @min.y
|
19
|
+
p.pos.y = @min.y + p.radius
|
20
|
+
p.vel.y = p.vel.y.abs
|
21
|
+
elsif p.pos.y + p.radius > @max.y
|
22
|
+
p.pos.y = @max.y - p.radius
|
23
|
+
p.vel.y = -1 * p.vel.y.abs
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Percolator
|
2
|
+
module Integrators
|
3
|
+
class Euler
|
4
|
+
# x += (v * dt) + (a * 0.5 * dt * dt)
|
5
|
+
# v += a * dt
|
6
|
+
def self.integrate(particles, delta, drag)
|
7
|
+
delta_squared = delta * delta
|
8
|
+
momentum = Vector.new
|
9
|
+
acc = Vector.new
|
10
|
+
|
11
|
+
particles.reject { |p| p.fixed? }.each do |p|
|
12
|
+
p.acc.scale(p.mass_inv) # F = ma
|
13
|
+
momentum.become(p.vel)
|
14
|
+
acc.become(p.acc)
|
15
|
+
|
16
|
+
p.pos.
|
17
|
+
add(momentum.scale(delta)).
|
18
|
+
add(acc.scale(0.5 * delta_squared))
|
19
|
+
|
20
|
+
p.vel.add(p.acc.scale(delta))
|
21
|
+
p.vel.scale(drag) # Apply friction
|
22
|
+
|
23
|
+
p.acc.clear
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class Percolator
|
2
|
+
class Particle
|
3
|
+
# @GUID = 0
|
4
|
+
|
5
|
+
DEFAULTS = {
|
6
|
+
mass: 1.0,
|
7
|
+
radius: 1.0,
|
8
|
+
fixed: false,
|
9
|
+
}
|
10
|
+
|
11
|
+
attr_reader :pos, :vel, :acc, :mass_inv, :radius, :mass
|
12
|
+
|
13
|
+
def initialize(params)
|
14
|
+
# Set a unique id.
|
15
|
+
# @id = 'p' + Particle.GUID++
|
16
|
+
full_params = DEFAULTS.merge(params)
|
17
|
+
|
18
|
+
setMass full_params[:mass]
|
19
|
+
setRadius full_params[:radius]
|
20
|
+
@fixed = full_params[:fixed]
|
21
|
+
@pos = full_params[:pos] || Vector.new
|
22
|
+
@vel = full_params[:vel] || Vector.new
|
23
|
+
|
24
|
+
@behaviors = []
|
25
|
+
@acc = Vector.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def moveTo(pos)
|
29
|
+
@pos.become(pos)
|
30
|
+
end
|
31
|
+
|
32
|
+
def setMass(mass = 1.0)
|
33
|
+
@mass = mass
|
34
|
+
@mass_inv = 1.0 / @mass
|
35
|
+
end
|
36
|
+
|
37
|
+
def setRadius(radius = 1.0)
|
38
|
+
@radius = radius
|
39
|
+
@radiusSq = @radius * @radius
|
40
|
+
end
|
41
|
+
|
42
|
+
def fixed?
|
43
|
+
!!@fixed
|
44
|
+
end
|
45
|
+
|
46
|
+
# Applies all behaviors to derive new force
|
47
|
+
def update(dt)
|
48
|
+
unless @fixed
|
49
|
+
@behaviors.each do |behavior|
|
50
|
+
behavior.affect(self, dt, index)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_h
|
56
|
+
{
|
57
|
+
pos: @pos.to_h,
|
58
|
+
vel: @vel.to_h,
|
59
|
+
radius: @radius,
|
60
|
+
mass: @mass,
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
class Percolator
|
2
|
+
class Vector
|
3
|
+
attr_accessor :x, :y
|
4
|
+
|
5
|
+
def initialize(x = 0.0, y = 0.0)
|
6
|
+
@x = x
|
7
|
+
@y = y
|
8
|
+
end
|
9
|
+
|
10
|
+
# Sets the components of this vector
|
11
|
+
def set(x, y)
|
12
|
+
@x = x
|
13
|
+
@y = y
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add a vector to this one
|
18
|
+
def add(v)
|
19
|
+
@x += v.x
|
20
|
+
@y += v.y
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# Subtracts a vector from this one
|
25
|
+
def sub(v)
|
26
|
+
@x -= v.x
|
27
|
+
@y -= v.y
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
# Scales this vector by a value
|
32
|
+
def scale(f)
|
33
|
+
@x *= f
|
34
|
+
@y *= f
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# Normalises the vector, making it a unit vector (of length 1)
|
39
|
+
def norm
|
40
|
+
m = mag
|
41
|
+
@x /= m
|
42
|
+
@y /= m
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
# Limits the vector length to a given amount
|
47
|
+
def limit(l)
|
48
|
+
mSq = @x*@x + @y*@y
|
49
|
+
if mSq > l*l
|
50
|
+
m = Math.sqrt(mSq)
|
51
|
+
@x /= m
|
52
|
+
@y /= m
|
53
|
+
@x *= l
|
54
|
+
@y *= l
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# more memory efficient way of `v1 = v2.dup`
|
60
|
+
def become(v)
|
61
|
+
@x = v.x
|
62
|
+
@y = v.y
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
# Computes the dot product between vectors
|
67
|
+
def dot(v)
|
68
|
+
@x * v.x + @y * v.y
|
69
|
+
end
|
70
|
+
|
71
|
+
# Computes the cross product between vectors
|
72
|
+
def cross(v)
|
73
|
+
(@x * v.y) - (@y * v.x)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Computes the magnitude (length)
|
77
|
+
def mag
|
78
|
+
Math.sqrt(@x*@x + @y*@y)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Computes the squared magnitude (length)
|
82
|
+
def magSq
|
83
|
+
@x*@x + @y*@y
|
84
|
+
end
|
85
|
+
|
86
|
+
# Computes the distance to another vector
|
87
|
+
def dist(v)
|
88
|
+
dx = v.x - @x
|
89
|
+
dy = v.y - @y
|
90
|
+
Math.sqrt(dx*dx + dy*dy)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Computes the squared distance to another vector
|
94
|
+
def distSq(v)
|
95
|
+
dx = v.x - @x
|
96
|
+
dy = v.y - @y
|
97
|
+
dx*dx + dy*dy
|
98
|
+
end
|
99
|
+
|
100
|
+
# Resets the vector to zero
|
101
|
+
def clear
|
102
|
+
@x = 0.0
|
103
|
+
@y = 0.0
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_h
|
107
|
+
{
|
108
|
+
x: @x,
|
109
|
+
y: @y,
|
110
|
+
}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: percolator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.prealpha
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Gary
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-02-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: guard-rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rb-fsevent
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email: mrjamesgary@gmail.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- lib/percolator/behaviors/behavior.rb
|
62
|
+
- lib/percolator/behaviors/collision.rb
|
63
|
+
- lib/percolator/behaviors/edge_bound.rb
|
64
|
+
- lib/percolator/integrators/euler.rb
|
65
|
+
- lib/percolator/particle.rb
|
66
|
+
- lib/percolator/vector.rb
|
67
|
+
- lib/percolator.rb
|
68
|
+
- README.md
|
69
|
+
homepage: http://github.com/jamesgary/percolator
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
metadata: {}
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ! '>'
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 1.3.1
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 2.0.0
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: Simple particle-based physics engine
|
93
|
+
test_files: []
|