motel 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +8 -0
- data/LICENSE +661 -0
- data/README +0 -0
- data/conf/amqp.yml +13 -0
- data/conf/database.yml +41 -0
- data/db/migrate/001_create_locations_and_movement_strategies.rb +45 -0
- data/db/migrate/002_create_linear_movement_strategy.rb +23 -0
- data/db/migrate/003_create_elliptical_movement_strategy.rb +52 -0
- data/lib/motel/common.rb +54 -0
- data/lib/motel/loader.rb +47 -0
- data/lib/motel/models/elliptical.rb +259 -0
- data/lib/motel/models/linear.rb +76 -0
- data/lib/motel/models/location.rb +109 -0
- data/lib/motel/models/movement_strategy.rb +82 -0
- data/lib/motel/models/stopped.rb +16 -0
- data/lib/motel/runner.rb +135 -0
- data/lib/motel/semaphore.rb +58 -0
- data/lib/motel.rb +13 -0
- metadata +82 -0
data/conf/amqp.yml
ADDED
data/conf/database.yml
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# motel database config
|
2
|
+
#
|
3
|
+
# Fedora Install Instructions:
|
4
|
+
# sudo yum install ruby-postgres postgresql-server
|
5
|
+
# sudo /sbin/service postgresql initdb
|
6
|
+
# sudo /sbin/service postgresql start
|
7
|
+
# sudo su - postgres
|
8
|
+
# createdb motel
|
9
|
+
# $ psql motel
|
10
|
+
# $-# CREATE USER motel WITH PASSWORD 'motel';
|
11
|
+
# $-# GRANT ALL PRIVILEGES ON DATABASE motel to motel;
|
12
|
+
# $-# CREATE DATABASE motel_test;
|
13
|
+
# $-# GRANT ALL PRIVILEGES ON DATABASE motel_test to motel;
|
14
|
+
# $-# CREATE DATABASE motel_development;
|
15
|
+
# $-# GRANT ALL PRIVILEGES ON DATABASE motel_development to motel;
|
16
|
+
# $-# \q
|
17
|
+
# $ exit
|
18
|
+
|
19
|
+
production:
|
20
|
+
adapter: postgresql
|
21
|
+
database: motel
|
22
|
+
username: motel
|
23
|
+
password: motel
|
24
|
+
host: localhost
|
25
|
+
pool: 20
|
26
|
+
|
27
|
+
development:
|
28
|
+
adapter: postgresql
|
29
|
+
database: motel_development
|
30
|
+
username: motel
|
31
|
+
password: motel
|
32
|
+
host: localhost
|
33
|
+
pool: 20
|
34
|
+
|
35
|
+
test:
|
36
|
+
adapter: postgresql
|
37
|
+
database: motel_test
|
38
|
+
username: motel
|
39
|
+
password: motel
|
40
|
+
host: localhost
|
41
|
+
pool: 20
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# creates the locations and movement_strategies tables
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# See COPYING for the License of this software
|
5
|
+
|
6
|
+
require 'motel'
|
7
|
+
|
8
|
+
# ActiveRecord::Migration 001
|
9
|
+
class CreateLocationsAndMovementStrategies < ActiveRecord::Migration
|
10
|
+
def self.up
|
11
|
+
|
12
|
+
create_table :movement_strategies do |t|
|
13
|
+
t.string :type, :null => false, :size => 50
|
14
|
+
t.float :step_delay, :null => false
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :locations do |t|
|
18
|
+
t.float :x, :default => nil
|
19
|
+
t.float :y, :default => nil
|
20
|
+
t.float :z, :default => nil
|
21
|
+
t.integer :movement_strategy_id, :null => false
|
22
|
+
|
23
|
+
t.integer :parent_id, :default => nil
|
24
|
+
end
|
25
|
+
|
26
|
+
execute "alter table locations add constraint fk_location_parent
|
27
|
+
foreign key(parent_id) references locations(id)"
|
28
|
+
|
29
|
+
execute "alter table locations add constraint fk_location_movement_strategy
|
30
|
+
foreign key(movement_strategy_id) references movement_strategies(id)"
|
31
|
+
|
32
|
+
execute "alter table locations add constraint root_or_child
|
33
|
+
check (parent_id IS NULL AND x IS NULL AND y IS NULL AND z IS NULL OR
|
34
|
+
parent_id IS NOT NULL AND x IS NOT NULL AND y IS NOT NULL AND z IS NOT NULL)"
|
35
|
+
|
36
|
+
|
37
|
+
# create the first / default movement strategy 'stopped'
|
38
|
+
Motel::Models::Stopped.new(:step_delay => 5).save!
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.down
|
42
|
+
drop_table :locations
|
43
|
+
drop_table :movement_strategies
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# creates the neccessary fields for the Linear MovementStrategy
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# See COPYING for the License of this software
|
5
|
+
|
6
|
+
require 'motel'
|
7
|
+
|
8
|
+
# ActiveRecord::Migration 002
|
9
|
+
class CreateLinearMovementStrategy < ActiveRecord::Migration
|
10
|
+
def self.up
|
11
|
+
add_column :movement_strategies, :speed, :float
|
12
|
+
add_column :movement_strategies, :direction_vector_x, :float
|
13
|
+
add_column :movement_strategies, :direction_vector_y, :float
|
14
|
+
add_column :movement_strategies, :direction_vector_z, :float
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.down
|
18
|
+
remove_column :movement_strategies, :speed
|
19
|
+
remove_column :movement_strategies, :direction_vector_x
|
20
|
+
remove_column :movement_strategies, :direction_vector_y
|
21
|
+
remove_column :movement_strategies, :direction_vector_z
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# creates the neccessary fields for the Elliptical MovementStrategy
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# See COPYING for the License of this software
|
5
|
+
|
6
|
+
require 'motel'
|
7
|
+
|
8
|
+
# ActiveRecord::Migration 003
|
9
|
+
class CreateEllipticalMovementStrategy < ActiveRecord::Migration
|
10
|
+
def self.up
|
11
|
+
# we already have speed from linear
|
12
|
+
#add_column :movement_strategies, :speed, :float
|
13
|
+
|
14
|
+
# relative_to field indicates what type of parent
|
15
|
+
# this movement stategy is relative to, for Ellilptical
|
16
|
+
# strategies, this can be 'center' indicating the parent
|
17
|
+
# is the center of the ellipse or 'foci' indicating the
|
18
|
+
# parent is on of the ellipse foci's
|
19
|
+
add_column :movement_strategies, :relative_to, :string, :size => 50
|
20
|
+
|
21
|
+
# eccentricity of the ellipse
|
22
|
+
add_column :movement_strategies, :eccentricity, :float
|
23
|
+
|
24
|
+
# semi_latus_rectum of the ellipse
|
25
|
+
add_column :movement_strategies, :semi_latus_rectum, :float
|
26
|
+
|
27
|
+
# unit direction vector of the major axis
|
28
|
+
add_column :movement_strategies, :direction_major_x, :float
|
29
|
+
add_column :movement_strategies, :direction_major_y, :float
|
30
|
+
add_column :movement_strategies, :direction_major_z, :float
|
31
|
+
|
32
|
+
# unit direction vector of the minor axis
|
33
|
+
add_column :movement_strategies, :direction_minor_x, :float
|
34
|
+
add_column :movement_strategies, :direction_minor_y, :float
|
35
|
+
add_column :movement_strategies, :direction_minor_z, :float
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.down
|
39
|
+
# remove in linear strategy
|
40
|
+
#remove_column :movement_strategies, :speed
|
41
|
+
|
42
|
+
remove_column :movement_strategies, :relative_to
|
43
|
+
remove_column :movement_strategies, :eccentricity
|
44
|
+
remove_column :movement_strategies, :semi_latus_rectum
|
45
|
+
remove_column :movement_strategies, :direction_major_x
|
46
|
+
remove_column :movement_strategies, :direction_major_y
|
47
|
+
remove_column :movement_strategies, :direction_major_z
|
48
|
+
remove_column :movement_strategies, :direction_minor_x
|
49
|
+
remove_column :movement_strategies, :direction_minor_y
|
50
|
+
remove_column :movement_strategies, :direction_minor_z
|
51
|
+
end
|
52
|
+
end
|
data/lib/motel/common.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# common & useful methods and other.
|
2
|
+
#
|
3
|
+
# Things that don't fit elsewhere
|
4
|
+
#
|
5
|
+
# Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
|
6
|
+
# See COPYING for the License of this software
|
7
|
+
|
8
|
+
module Motel
|
9
|
+
|
10
|
+
# generate a random id
|
11
|
+
def gen_uuid
|
12
|
+
["%02x"*4, "%02x"*2, "%02x"*2, "%02x"*2, "%02x"*6].join("-") %
|
13
|
+
Array.new(16) {|x| rand(0xff) }
|
14
|
+
end
|
15
|
+
|
16
|
+
# normalize a vector if not normal already,
|
17
|
+
# eg divide each component x,y,z by the
|
18
|
+
# vector length and return them
|
19
|
+
def normalize(x,y,z)
|
20
|
+
return x,y,z if x.nil? || y.nil? || z.nil?
|
21
|
+
|
22
|
+
l = Math.sqrt(x**2 + y**2 + z**2)
|
23
|
+
if l != 1
|
24
|
+
x /= l
|
25
|
+
y /= l
|
26
|
+
z /= l
|
27
|
+
end
|
28
|
+
return x,y,z
|
29
|
+
end
|
30
|
+
|
31
|
+
# determine if two vectors are orthogonal
|
32
|
+
def orthogonal?(x1,y1,z1, x2,y2,z2)
|
33
|
+
return false if x1.nil? || y1.nil? || z1.nil? || x2.nil? || y2.nil? || z2.nil?
|
34
|
+
return (x1 * x2 + y1 * y2 + z1 * z2).abs == 0
|
35
|
+
end
|
36
|
+
|
37
|
+
end # module Motel
|
38
|
+
|
39
|
+
# provide floating point rounding mechanism
|
40
|
+
class Float
|
41
|
+
def round_to(precision)
|
42
|
+
return nil if precision <= 0
|
43
|
+
return (self * 10 ** precision).round.to_f / (10 ** precision)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# try to find key equal to method, returning value if found
|
48
|
+
#class Hash
|
49
|
+
# def method_missing(method, *params)
|
50
|
+
# method = method.to_sym
|
51
|
+
# return self[method] if self.keys.collect{ |k| k.to_sym }.include?(method)
|
52
|
+
# super
|
53
|
+
# end
|
54
|
+
#end
|
data/lib/motel/loader.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# The Loader class definition
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# See COPYING for the License of this software
|
5
|
+
|
6
|
+
require 'motel/runner'
|
7
|
+
|
8
|
+
module Motel
|
9
|
+
|
10
|
+
# A Loader loads instances of Location from the db
|
11
|
+
# according to any specified conditions, instantiates
|
12
|
+
# a new Runner for each of those locations and returns them
|
13
|
+
class Loader
|
14
|
+
|
15
|
+
public
|
16
|
+
|
17
|
+
# Default class constructor
|
18
|
+
def initialize
|
19
|
+
end
|
20
|
+
|
21
|
+
# Static member to load all locations that match a specified
|
22
|
+
# condition (ala activerecord) and add it to the singleton Runner
|
23
|
+
# instance using it to run the locations.
|
24
|
+
def self.Load(conditions = 'parent_id IS NULL')
|
25
|
+
locations = Location.find(:all, :conditions => conditions)
|
26
|
+
return nil if locations.size == 0
|
27
|
+
|
28
|
+
locations.each { |location|
|
29
|
+
run_location(location)
|
30
|
+
}
|
31
|
+
return locations.size
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Static internal helper method that adds a location and
|
37
|
+
# all its children to the Runner
|
38
|
+
def self.run_location(location)
|
39
|
+
Runner.get.run location
|
40
|
+
location.children.each { |child|
|
41
|
+
run_location child
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
# The Elliptcial MovementStrategy model definition
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# See COPYING for the License of this software
|
5
|
+
|
6
|
+
require 'motel/common'
|
7
|
+
require 'motel/models/movement_strategy'
|
8
|
+
|
9
|
+
module Motel
|
10
|
+
module Models
|
11
|
+
|
12
|
+
# The Elliptical MovementStrategy moves a location
|
13
|
+
# on an elliptical path described by major/minor
|
14
|
+
# axis direction vectors, and an eccentricity /
|
15
|
+
# semi_latus_rectum. The path equation will
|
16
|
+
# also depend on the value of the relative_to
|
17
|
+
# field indicating if the parent location
|
18
|
+
# is the center or a foci of the ellipse.
|
19
|
+
# Lastly a speed value is required indicating the
|
20
|
+
# angular velocity of the location.
|
21
|
+
class Elliptical < MovementStrategy
|
22
|
+
|
23
|
+
# Elliptical MovementStrategy must specify x,y,z components of
|
24
|
+
# the major and minor axis direction vectors
|
25
|
+
validates_presence_of [:direction_major_x,
|
26
|
+
:direction_major_y,
|
27
|
+
:direction_major_z,
|
28
|
+
:direction_minor_x,
|
29
|
+
:direction_minor_y,
|
30
|
+
:direction_minor_z]
|
31
|
+
|
32
|
+
# make sure the unit direction vectors are normal
|
33
|
+
before_validation :normalize_direction_vectors
|
34
|
+
def normalize_direction_vectors
|
35
|
+
dx, dy, dz =
|
36
|
+
normalize(direction_major_x, direction_major_y, direction_major_z)
|
37
|
+
self.direction_major_x, self.direction_major_y, self.direction_major_z = dx, dy, dz
|
38
|
+
|
39
|
+
dx, dy, dz =
|
40
|
+
normalize(direction_minor_x, direction_minor_y, direction_minor_z)
|
41
|
+
self.direction_minor_x, self.direction_minor_y, self.direction_minor_z = dx, dy, dz
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# Elliptical MovementStrategy must specify the angular velocity
|
46
|
+
# at which the location is moving
|
47
|
+
validates_presence_of :speed
|
48
|
+
validates_numericality_of :speed,
|
49
|
+
:greater_than_or_equal_to => 0,
|
50
|
+
:less_than_or_equal_to => 2 * Math::PI
|
51
|
+
|
52
|
+
# the eccentricity of the ellipse
|
53
|
+
validates_presence_of :eccentricity
|
54
|
+
validates_numericality_of :eccentricity,
|
55
|
+
:greater_than_or_equal_to => 0,
|
56
|
+
:less_than_or_equal_to => 1
|
57
|
+
|
58
|
+
def e
|
59
|
+
eccentricity
|
60
|
+
end
|
61
|
+
def e=(v)
|
62
|
+
eccentricity= v
|
63
|
+
end
|
64
|
+
|
65
|
+
# the semi_latus_rectum of the ellipse
|
66
|
+
validates_presence_of :semi_latus_rectum
|
67
|
+
validates_numericality_of :semi_latus_rectum,
|
68
|
+
:greater_than_or_equal_to => 0
|
69
|
+
|
70
|
+
def p
|
71
|
+
semi_latus_rectum
|
72
|
+
end
|
73
|
+
def p=(v)
|
74
|
+
semi_latus_rectum = v
|
75
|
+
end
|
76
|
+
|
77
|
+
# the possible relative_to values
|
78
|
+
RELATIVE_TO_CENTER = "center"
|
79
|
+
RELATIVE_TO_FOCI = "foci"
|
80
|
+
|
81
|
+
# must be relative to a parent center or foci
|
82
|
+
validates_presence_of :relative_to
|
83
|
+
validates_inclusion_of :relative_to,
|
84
|
+
:in => [ RELATIVE_TO_CENTER, RELATIVE_TO_FOCI ]
|
85
|
+
|
86
|
+
# ActiveRecord::Base::validate
|
87
|
+
def validate
|
88
|
+
errors.add("direction vectors must be orthogonal") unless orthogonal?(direction_major_x, direction_major_y, direction_major_z,
|
89
|
+
direction_minor_x, direction_minor_y, direction_minor_z)
|
90
|
+
end
|
91
|
+
|
92
|
+
# convert non-nil elliptical movement strategy attributes to a hash
|
93
|
+
def to_h
|
94
|
+
result = {}
|
95
|
+
result[:speed] = speed unless speed.nil?
|
96
|
+
result[:eccentricity] = eccentricity unless eccentricity.nil?
|
97
|
+
result[:semi_latus_rectum] = semi_latus_rectum unless semi_latus_rectum.nil?
|
98
|
+
result[:relative_to] = relative_to unless relative_to.nil?
|
99
|
+
result[:direction_major_x] = direction_major_x unless direction_major_x.nil?
|
100
|
+
result[:direction_major_y] = direction_major_y unless direction_major_y.nil?
|
101
|
+
result[:direction_major_z] = direction_major_z unless direction_major_z.nil?
|
102
|
+
result[:direction_minor_x] = direction_minor_x unless direction_minor_x.nil?
|
103
|
+
result[:direction_minor_y] = direction_minor_y unless direction_minor_y.nil?
|
104
|
+
result[:direction_minor_z] = direction_minor_z unless direction_minor_z.nil?
|
105
|
+
return result
|
106
|
+
end
|
107
|
+
|
108
|
+
# convert elliptical movement strategy to a string
|
109
|
+
def to_s
|
110
|
+
super + "; speed:#{speed}; eccentricity:#{eccentricity}; semi_latus_rectum:#{semi_latus_rectum}; relative_to:#{relative_to}; " +
|
111
|
+
"direction_major_x:#{direction_major_x}; direction_major_y:#{direction_major_y}; direction_major_z:#{direction_major_z}; " +
|
112
|
+
"direction_minor_x:#{direction_minor_x}; direction_minor_y:#{direction_minor_y}; direction_minor_z:#{direction_minor_z}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Motel::Models::MovementStrategy::move
|
116
|
+
def move(location, elapsed_seconds)
|
117
|
+
# make sure this movement strategy is valid
|
118
|
+
unless valid?
|
119
|
+
Logger.warn "elliptical movement strategy not valid, not proceeding with move"
|
120
|
+
return
|
121
|
+
end
|
122
|
+
|
123
|
+
# make sure location is on ellipse
|
124
|
+
unless location_valid? location
|
125
|
+
cx,cy,cz = closest_coordinates location
|
126
|
+
Logger.warn "location #{location} not on ellipse, the closest location is #{cl}, not proceeding with move"
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
Logger.debug "moving location #{location} via elliptical movement strategy"
|
131
|
+
|
132
|
+
# calculate distance moved and update x,y,z accordingly
|
133
|
+
distance = speed * elapsed_seconds
|
134
|
+
|
135
|
+
nX,nY,nZ = coordinates_from_theta(theta(location) + distance)
|
136
|
+
location.x = nX
|
137
|
+
location.y = nY
|
138
|
+
location.z = nZ
|
139
|
+
|
140
|
+
Logger.debug "moved location #{location} via elliptical movement strategy"
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
### internal helper movement methods
|
146
|
+
|
147
|
+
# return the a,b intercepts of the ellipse
|
148
|
+
# p = a(1 - e^2) = b^2 / a
|
149
|
+
# e = sqrt(1 - (b/a)^2)
|
150
|
+
def intercepts
|
151
|
+
a = p / (1 - e**2)
|
152
|
+
b = Math.sqrt(p * a)
|
153
|
+
return a,b
|
154
|
+
end
|
155
|
+
|
156
|
+
# return the linear eccentricity of the ellipse
|
157
|
+
# le = sqrt(a^2 - b^2)
|
158
|
+
def linear_eccentricity
|
159
|
+
a,b = intercepts
|
160
|
+
Math.sqrt(a**2 - b**2);
|
161
|
+
end
|
162
|
+
|
163
|
+
# return the coordinates of the center position
|
164
|
+
# C = (-direction_major) * le
|
165
|
+
def center
|
166
|
+
return 0,0,0 if relative_to == RELATIVE_TO_CENTER
|
167
|
+
|
168
|
+
a,b = intercepts
|
169
|
+
le = linear_eccentricity
|
170
|
+
|
171
|
+
centerX = -1 * direction_major_x * le;
|
172
|
+
centerY = -1 * direction_major_y * le;
|
173
|
+
centerZ = -1 * direction_major_z * le;
|
174
|
+
return centerX, centerY, centerZ
|
175
|
+
end
|
176
|
+
|
177
|
+
# return the coordinates of a focus position
|
178
|
+
# F = direction_major * le
|
179
|
+
def focus
|
180
|
+
return 0,0,0 if relative_to == RELATIVE_TO_FOCI
|
181
|
+
|
182
|
+
a,b = intercepts
|
183
|
+
le = linear_eccentricity
|
184
|
+
|
185
|
+
focusX = direction_major_x * le;
|
186
|
+
focusY = direction_major_y * le;
|
187
|
+
focusZ = direction_major_z * le;
|
188
|
+
return focusX, focusY, focusZ
|
189
|
+
end
|
190
|
+
|
191
|
+
# return the origin centered coordiates of a location
|
192
|
+
def origin_centered_coordinates(location)
|
193
|
+
cX,cY,cZ = center
|
194
|
+
return location.x - cX,
|
195
|
+
location.y - cY,
|
196
|
+
location.z - cZ
|
197
|
+
end
|
198
|
+
|
199
|
+
# return the theta corresponding to the position of a
|
200
|
+
# location on the elliptical path.
|
201
|
+
#
|
202
|
+
# derived formula for theta from x,y,z elliptical path equations (see below) and linear transformations:
|
203
|
+
# theta = acos((minY * (x-cX) - minX * (y-cY))/(a*(minY * majX - minX * majY)))
|
204
|
+
def theta(location)
|
205
|
+
a,b = intercepts
|
206
|
+
ocX,ocY,ocZ = origin_centered_coordinates location
|
207
|
+
|
208
|
+
t = (direction_minor_y * ocX - direction_minor_x * ocY) /
|
209
|
+
(a * (direction_minor_y * direction_major_x - direction_minor_x * direction_major_y))
|
210
|
+
t= 1.0 if(t>1.0)
|
211
|
+
theta = Math.acos(t)
|
212
|
+
|
213
|
+
# determine if current point is in negative quadrants of min axis coordinate system
|
214
|
+
below = ocY < ((direction_minor_x * ocX + direction_minor_z * ocZ) / (-direction_minor_y))
|
215
|
+
|
216
|
+
# adjust to compenate for acos loss if necessary
|
217
|
+
theta = (3 * Math::PI / 2) + (Math::PI / 2 - theta) if (below)
|
218
|
+
|
219
|
+
return theta;
|
220
|
+
end
|
221
|
+
|
222
|
+
# calculate the x,y,z coordinates of a location on the elliptical
|
223
|
+
# path given its theta
|
224
|
+
#
|
225
|
+
# Elliptical path equation:
|
226
|
+
# [x,y,z] = a * cos(theta) * maj + b * sin(theta) * min
|
227
|
+
# (if centered at origin)
|
228
|
+
def coordinates_from_theta(theta)
|
229
|
+
a,b = intercepts
|
230
|
+
cX,cY,cZ = center
|
231
|
+
|
232
|
+
x = cX + a * Math.cos(theta) * direction_major_x + b * Math.sin(theta) * direction_minor_x
|
233
|
+
y = cY + a * Math.cos(theta) * direction_major_y + b * Math.sin(theta) * direction_minor_y
|
234
|
+
z = cZ + a * Math.cos(theta) * direction_major_z + b * Math.sin(theta) * direction_minor_z
|
235
|
+
return x,y,z
|
236
|
+
end
|
237
|
+
|
238
|
+
# return x,y,z coordinates of the closest point on the ellipse to the given location
|
239
|
+
def closest_coordinates(location)
|
240
|
+
t = theta location
|
241
|
+
|
242
|
+
return nil if t.nan?
|
243
|
+
|
244
|
+
return coordinates_from_theta(t)
|
245
|
+
end
|
246
|
+
|
247
|
+
# return boolean indicating if the given location is on the ellipse or not
|
248
|
+
def location_valid?(location)
|
249
|
+
x,y,z = closest_coordinates(location)
|
250
|
+
|
251
|
+
return (x - location.x).round_to(4) == 0 &&
|
252
|
+
(y - location.y).round_to(4) == 0 &&
|
253
|
+
(z - location.z).round_to(4) == 0
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
end # module Models
|
259
|
+
end # module Motel
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# The Linear MovementStrategy model definition
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
|
4
|
+
# See COPYING for the License of this software
|
5
|
+
|
6
|
+
require 'motel/common'
|
7
|
+
require 'motel/models/movement_strategy'
|
8
|
+
|
9
|
+
module Motel
|
10
|
+
module Models
|
11
|
+
|
12
|
+
# The Linear MovementStrategy moves a location
|
13
|
+
# in a linear manner as defined by a
|
14
|
+
# unit direction vector and a floating point
|
15
|
+
# speed
|
16
|
+
class Linear < MovementStrategy
|
17
|
+
|
18
|
+
# Linear MovementStrategy must specify x,y,z components of
|
19
|
+
# a unit direction vector
|
20
|
+
validates_presence_of [:direction_vector_x,
|
21
|
+
:direction_vector_y,
|
22
|
+
:direction_vector_z]
|
23
|
+
|
24
|
+
# make sure the unit direction vector is normal
|
25
|
+
before_validation :normalize_direction_vector
|
26
|
+
def normalize_direction_vector
|
27
|
+
dx, dy, dz =
|
28
|
+
normalize(direction_vector_x, direction_vector_y, direction_vector_z)
|
29
|
+
self.direction_vector_x, self.direction_vector_y, self.direction_vector_z = dx, dy, dz
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Linear MovementStrategy must specify the speed
|
34
|
+
# at which the location is moving
|
35
|
+
validates_presence_of :speed
|
36
|
+
validates_numericality_of :speed,
|
37
|
+
:greater_than_or_equal_to => 0
|
38
|
+
|
39
|
+
# Motel::Models::MovementStrategy::move
|
40
|
+
def move(location, elapsed_seconds)
|
41
|
+
unless valid?
|
42
|
+
Logger.warn "linear movement strategy not valid, not proceeding with move"
|
43
|
+
return
|
44
|
+
end
|
45
|
+
|
46
|
+
Logger.debug "moving location #{location.to_s} via linear movement strategy"
|
47
|
+
|
48
|
+
# calculate distance and update x,y,z accordingly
|
49
|
+
distance = speed * elapsed_seconds
|
50
|
+
|
51
|
+
location.x += distance * direction_vector_x
|
52
|
+
location.y += distance * direction_vector_y
|
53
|
+
location.z += distance * direction_vector_z
|
54
|
+
|
55
|
+
Logger.debug "moved location #{location} via linear movement strategy"
|
56
|
+
end
|
57
|
+
|
58
|
+
# convert non-nil linear movement strategy attributes to a hash
|
59
|
+
def to_h
|
60
|
+
result = {}
|
61
|
+
result[:speed] = speed unless speed.nil?
|
62
|
+
result[:direction_vector_x] = direction_vector_x unless direction_vector_x.nil?
|
63
|
+
result[:direction_vector_y] = direction_vector_y unless direction_vector_y.nil?
|
64
|
+
result[:direction_vector_z] = direction_vector_z unless direction_vector_z.nil?
|
65
|
+
return result
|
66
|
+
end
|
67
|
+
|
68
|
+
# convert linear movement strategy to a string
|
69
|
+
def to_s
|
70
|
+
super + "; speed: #{speed}; direction_vector_x:#{direction_vector_x}; " +
|
71
|
+
"direction_vector_y:#{direction_vector_y}; direction_vector_z:#{direction_vector_z}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end # module Models
|
76
|
+
end # module Motel
|