ein 0.0.1
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.
- data/lib/ein/angle.rb +10 -0
- data/lib/ein/circle.rb +38 -0
- data/lib/ein/console.rb +48 -0
- data/lib/ein/plane.rb +43 -0
- data/lib/ein/pose.rb +29 -0
- data/lib/ein/position.rb +27 -0
- data/lib/ein/simulator.rb +53 -0
- data/lib/ein/vector.rb +30 -0
- data/lib/ein/version.rb +3 -0
- data/lib/ein/world.rb +75 -0
- data/lib/ein.rb +5 -0
- data/spec/lib/ein/circle_spec.rb +20 -0
- data/spec/lib/ein/console_spec.rb +27 -0
- data/spec/lib/ein/simulator_spec.rb +26 -0
- data/spec/spec_helper.rb +10 -0
- metadata +81 -0
data/lib/ein/angle.rb
ADDED
data/lib/ein/circle.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
###################
|
2
|
+
# Our obstacles are circles, but other shapes may come later
|
3
|
+
###################
|
4
|
+
module Ein
|
5
|
+
class Circle
|
6
|
+
|
7
|
+
attr_reader :position, :radius
|
8
|
+
|
9
|
+
def initialize(position, radius)
|
10
|
+
@position, @radius = position, radius
|
11
|
+
end
|
12
|
+
|
13
|
+
def pos
|
14
|
+
@position.round
|
15
|
+
end
|
16
|
+
|
17
|
+
def distance_to(object)
|
18
|
+
case object_physics(object)
|
19
|
+
when :circle
|
20
|
+
@position.distance_to(object.pos) - ( @radius + object.radius)
|
21
|
+
when :position
|
22
|
+
@position.distance_to(object) - ( @radius )
|
23
|
+
else
|
24
|
+
raise "can not calculate distance from Circle to #{object.class}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def object_physics(object)
|
30
|
+
if object.respond_to?(:physical_shape)
|
31
|
+
object.physical_shape
|
32
|
+
else
|
33
|
+
object.class.to_s.split('::').last.downcase.to_sym
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/ein/console.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
###############
|
2
|
+
# One of the possible formatters of our information.
|
3
|
+
# It outputs to the console. By default only info messages will be output
|
4
|
+
# create it with new(:debug) to make it much more verbose
|
5
|
+
###############
|
6
|
+
module Ein
|
7
|
+
class Console
|
8
|
+
|
9
|
+
def initialize(level=:info)
|
10
|
+
if available?(level)
|
11
|
+
@level = level
|
12
|
+
else
|
13
|
+
@level = :info
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def info(text)
|
18
|
+
puts(text) if should_print(:info)
|
19
|
+
end
|
20
|
+
|
21
|
+
def debug(text)
|
22
|
+
puts(text) if should_print(:debug)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def available_levels
|
27
|
+
[:debug, :info, :quiet]
|
28
|
+
end
|
29
|
+
|
30
|
+
def available?(level)
|
31
|
+
available_levels.include?(level)
|
32
|
+
end
|
33
|
+
|
34
|
+
def priority(level)
|
35
|
+
available_levels.index(level)
|
36
|
+
end
|
37
|
+
|
38
|
+
def should_print(level)
|
39
|
+
priority(level) >= priority(@level)
|
40
|
+
end
|
41
|
+
|
42
|
+
def puts(text)
|
43
|
+
return if defined?(Rails) && Rails.env == :test
|
44
|
+
Kernel.puts text
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
data/lib/ein/plane.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#######################################
|
2
|
+
# This is an infinite 2D plane with infinite mass and infinitely strong
|
3
|
+
# Objects and entities can collide with it.
|
4
|
+
# normal is the normal of the plane towards the origin
|
5
|
+
# distance_to_origin is the distance from the plane to the origin
|
6
|
+
#######################################
|
7
|
+
module Ein
|
8
|
+
class Plane
|
9
|
+
attr_reader :normal, :distance_to_origin
|
10
|
+
|
11
|
+
def initialize(normal, distance_to_origin)
|
12
|
+
@normal = normal
|
13
|
+
@distance_to_origin = distance_to_origin
|
14
|
+
end
|
15
|
+
|
16
|
+
def distance_to(object)
|
17
|
+
case object_physics(object)
|
18
|
+
when :circle
|
19
|
+
distance_to_point(object.pos) - object.radius
|
20
|
+
when :position
|
21
|
+
distance_to_point(object)
|
22
|
+
else
|
23
|
+
raise "can not calculate distance from Plane to #{object.class}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def object_physics(object)
|
30
|
+
if object.respond_to?(:physical_shape)
|
31
|
+
object.physical_shape
|
32
|
+
else
|
33
|
+
object.class.to_s.split('::').last.downcase.to_sym
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def distance_to_point(point)
|
38
|
+
projected_point = normal.project(point)
|
39
|
+
projected_point.x + projected_point.y + @distance_to_origin
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/lib/ein/pose.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
################################
|
2
|
+
# Pose of an object in a 2D environment
|
3
|
+
# Its position in 2D coordinates and its angle in degrees
|
4
|
+
# angle=0 => +y, angle=90 => +x, angle=180 => -y, angle=270 => -x
|
5
|
+
################################
|
6
|
+
module Ein
|
7
|
+
class Pose
|
8
|
+
|
9
|
+
attr_reader :position
|
10
|
+
|
11
|
+
def initialize(position, angle)
|
12
|
+
@position = position
|
13
|
+
@angle = angle
|
14
|
+
end
|
15
|
+
|
16
|
+
#Create a new pose, modifying previous with a relative position and angle
|
17
|
+
def advance(distance, angle)
|
18
|
+
new_angle = @angle + angle
|
19
|
+
new_y = @position.y + distance * Math.cos(Angle.degrees_to_radians(new_angle))
|
20
|
+
new_x = @position.x + distance * Math.sin(Angle.degrees_to_radians(new_angle))
|
21
|
+
Pose.new(Position.new(new_x, new_y), new_angle)
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"N: #@angle, #@position"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/ein/position.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
########################
|
2
|
+
# A 2D position
|
3
|
+
# Positions of objects and entities always represent
|
4
|
+
# the position if their centers
|
5
|
+
########################
|
6
|
+
module Ein
|
7
|
+
class Position
|
8
|
+
attr_accessor :x, :y
|
9
|
+
|
10
|
+
def initialize(x,y)
|
11
|
+
@x, @y = x,y
|
12
|
+
end
|
13
|
+
|
14
|
+
def distance_to(position)
|
15
|
+
Math.sqrt((@x - position.x)**2 + (@y - position.y)**2) # Pythagoras, miss you buddy. RIP
|
16
|
+
end
|
17
|
+
|
18
|
+
def round
|
19
|
+
Position.new(@x.round, @y.round)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"x: #@x, y: #@y"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'console'
|
2
|
+
require 'world'
|
3
|
+
|
4
|
+
##############################
|
5
|
+
#
|
6
|
+
#This class is the highest level entity, controls how
|
7
|
+
#the simulation behaves, contains the world and the entities
|
8
|
+
#
|
9
|
+
###############################
|
10
|
+
module Ein
|
11
|
+
class Simulator
|
12
|
+
attr_reader :world, :current_time, :formatter
|
13
|
+
|
14
|
+
def initialize(formatter = nil)
|
15
|
+
@current_time = Time.now
|
16
|
+
@formatter = formatter || Console.new
|
17
|
+
@world = World.new
|
18
|
+
@running = false
|
19
|
+
end
|
20
|
+
|
21
|
+
#TODO: should these methods add ! because they modify the simulation ?
|
22
|
+
def start
|
23
|
+
@running = true
|
24
|
+
Thread.abort_on_exception = true
|
25
|
+
Thread.new { run }
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def stop
|
30
|
+
@running = false
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def running?
|
35
|
+
@running
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
STEP = 0.01
|
41
|
+
|
42
|
+
def run
|
43
|
+
@formatter.info "Simulation started"
|
44
|
+
while (@running)
|
45
|
+
@current_time += STEP
|
46
|
+
@world.step(STEP)
|
47
|
+
sleep(STEP)
|
48
|
+
end
|
49
|
+
@formatter.info "Simulation terminated"
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
data/lib/ein/vector.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
################################
|
2
|
+
# A vector in 2D space
|
3
|
+
################################
|
4
|
+
module Ein
|
5
|
+
class Vector
|
6
|
+
attr_accessor :x, :y
|
7
|
+
|
8
|
+
def initialize(x,y)
|
9
|
+
@x, @y = x,y
|
10
|
+
end
|
11
|
+
|
12
|
+
def project(position)
|
13
|
+
Vector.new(@x * position.x, @y * position.y)
|
14
|
+
end
|
15
|
+
|
16
|
+
def length
|
17
|
+
Math.sqrt(@x**2 + @y**2)
|
18
|
+
end
|
19
|
+
|
20
|
+
def *(scalar)
|
21
|
+
@x += @x * scalar
|
22
|
+
@y += @y * scalar
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"x: #@x, y: #@y"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/ein/version.rb
ADDED
data/lib/ein/world.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
##############################
|
2
|
+
#
|
3
|
+
# This class represents the virtual world our simulation runs in
|
4
|
+
#
|
5
|
+
##############################
|
6
|
+
module Ein
|
7
|
+
class World
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@entities = []
|
11
|
+
read_world
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def render
|
16
|
+
world_ui = {}
|
17
|
+
world_ui['boundaries'] = @boundaries
|
18
|
+
world_ui['obstacles'] = @obstacles
|
19
|
+
@entities.each do |entity|
|
20
|
+
if entity.respond_to?(:render)
|
21
|
+
world_ui['entity'] = entity.render
|
22
|
+
end
|
23
|
+
end
|
24
|
+
world_ui.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def spawn(*entities)
|
28
|
+
entities.each do |entity|
|
29
|
+
@entities.push(entity)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def step (time_step)
|
34
|
+
@entities.each do |entity|
|
35
|
+
entity.step(time_step)
|
36
|
+
if collision_with?(entity)
|
37
|
+
entity.step_back
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def collision_with?(object)
|
44
|
+
|
45
|
+
@boundaries.each do |boundary|
|
46
|
+
return true if boundary.distance_to(object) <= 0
|
47
|
+
end
|
48
|
+
|
49
|
+
@obstacles.each do |obstacle|
|
50
|
+
return true if obstacle.distance_to(object) <= 0
|
51
|
+
end
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def read_world
|
58
|
+
#TODO: read from external .yml or something
|
59
|
+
#TODO: mass and shape for the obstacles
|
60
|
+
#boundaries from left boundary clockwise.
|
61
|
+
@boundaries = [
|
62
|
+
Plane.new(Vector.new(1,0), 1000),
|
63
|
+
Plane.new(Vector.new(0,-1), 800),
|
64
|
+
Plane.new(Vector.new(-1,0), 1000),
|
65
|
+
Plane.new(Vector.new(0,1), 800)
|
66
|
+
]
|
67
|
+
@obstacles = [
|
68
|
+
Circle.new(Position.new(0,500), 20),
|
69
|
+
Circle.new(Position.new(300,0), 20),
|
70
|
+
Circle.new(Position.new(-900,-700), 10)
|
71
|
+
]
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
data/lib/ein.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ein
|
4
|
+
describe Circle do
|
5
|
+
it "calculates the distance to other circle" do
|
6
|
+
circle1 = Circle.new(Position.new(0,0), 100)
|
7
|
+
circle2 = Circle.new(Position.new(300,0), 100)
|
8
|
+
expect(circle1.distance_to(circle2)).to eq(100)
|
9
|
+
|
10
|
+
circle2 = Circle.new(Position.new(200,0), 100)
|
11
|
+
expect(circle1.distance_to(circle2)).to eq(0)
|
12
|
+
|
13
|
+
circle2 = Circle.new(Position.new(100,0), 100)
|
14
|
+
expect(circle1.distance_to(circle2)).to eq(-100)
|
15
|
+
|
16
|
+
circle2 = Circle.new(Position.new(200,0),0)
|
17
|
+
expect(circle1.distance_to(circle2)).to eq(100)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ein
|
4
|
+
describe Console do
|
5
|
+
it "prints when the message priority is bigger than the default priority" do
|
6
|
+
console = Console.new(:debug)
|
7
|
+
Kernel.should_receive(:puts).with("trusmis")
|
8
|
+
console.info("trusmis")
|
9
|
+
end
|
10
|
+
it "prints the message when priority is equal to the default priority" do
|
11
|
+
console = Console.new(:debug)
|
12
|
+
Kernel.should_receive(:puts).with("trusmis")
|
13
|
+
console.debug("trusmis")
|
14
|
+
end
|
15
|
+
it "does not print when the priority is less than the default priority" do
|
16
|
+
console = Console.new(:info)
|
17
|
+
Kernel.should_not_receive(:puts).with("trusmis")
|
18
|
+
console.debug("trusmis")
|
19
|
+
end
|
20
|
+
it "prints nothing if we chose a quite level" do
|
21
|
+
console = Console.new(:quiet)
|
22
|
+
Kernel.should_not_receive(:puts).with("trusmis")
|
23
|
+
console.debug("trusmis")
|
24
|
+
console.info("trusmis")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Ein
|
5
|
+
describe Simulator do
|
6
|
+
|
7
|
+
context "new simulator with no params" do
|
8
|
+
subject {Simulator.new}
|
9
|
+
it { should_not be_running }
|
10
|
+
its(:world) {should be}
|
11
|
+
end
|
12
|
+
|
13
|
+
context "it can run simulations" do
|
14
|
+
subject(:simulator) {Simulator.new(Console.new(:quiet)) }
|
15
|
+
|
16
|
+
it "can be started and stopped" do
|
17
|
+
simulator.start
|
18
|
+
expect(simulator).to be_running
|
19
|
+
simulator.stop
|
20
|
+
expect(simulator).not_to be_running
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
3
|
+
config.run_all_when_everything_filtered = true
|
4
|
+
config.filter_run :focus
|
5
|
+
config.order = 'random'
|
6
|
+
end
|
7
|
+
$LOAD_PATH << File.expand_path( 'lib/ein/')
|
8
|
+
Dir['lib/ein/*.rb'].each do |file|
|
9
|
+
require File.basename(file, '.rb')
|
10
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ein
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jordi Polo Carres
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.13'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.13'
|
30
|
+
description: Very simple 2D physics simulator
|
31
|
+
email:
|
32
|
+
- mumismo@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- lib/ein/angle.rb
|
38
|
+
- lib/ein/circle.rb
|
39
|
+
- lib/ein/console.rb
|
40
|
+
- lib/ein/plane.rb
|
41
|
+
- lib/ein/pose.rb
|
42
|
+
- lib/ein/position.rb
|
43
|
+
- lib/ein/simulator.rb
|
44
|
+
- lib/ein/vector.rb
|
45
|
+
- lib/ein/version.rb
|
46
|
+
- lib/ein/world.rb
|
47
|
+
- lib/ein.rb
|
48
|
+
- spec/lib/ein/circle_spec.rb
|
49
|
+
- spec/lib/ein/console_spec.rb
|
50
|
+
- spec/lib/ein/simulator_spec.rb
|
51
|
+
- spec/spec_helper.rb
|
52
|
+
homepage: https://github.com/jordiPolo/ein
|
53
|
+
licenses: []
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.8.25
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Very simple 2D physics simulator
|
76
|
+
test_files:
|
77
|
+
- spec/lib/ein/circle_spec.rb
|
78
|
+
- spec/lib/ein/console_spec.rb
|
79
|
+
- spec/lib/ein/simulator_spec.rb
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
has_rdoc:
|