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.
@@ -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=
@@ -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).
@@ -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,14 @@
1
+ #class Percolator
2
+ # class Behaviours
3
+ # class Behaviour
4
+ # # Each behaviour has a unique id
5
+ #
6
+ # def initialize
7
+ # @interval = 1
8
+ # end
9
+ #
10
+ # def apply(p, dt, index)
11
+ # ed
12
+ # end
13
+ # end
14
+ #end
@@ -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: []