percolator 0.0.1.prealpha

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