Class: Motel::Location

Inherits:
Object show all
Defined in:
lib/motel/location.rb

Overview

Locations are the entity at the center of the Motel subsystem and describe a set of x,y,z coordinates in cartesian space.

The location may be related to a parent through its parent_id and parent properties, in which case the x,y,z coordinates reference the position in and/or relative to its parent.

If no parent_id / parent is specified, the location is often assumed to be the 'root' location of its local system. Ultimately though the sematics of the location heirarchy is left up to the client.

A location is associated with a instance of a MovementStrategy subclass, by default MovementStrategies::Stopped. The movement_strategy#move method is invoked by the Runner with the location instance on every run cycle.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ Location

Location initializer

Options Hash (args):

  • :id,'id' (Integer)

    id to assign to the location

  • :parent_id,'parent_id' (Integer)

    parent_id to assign to the location

  • :parent,'parent' (Motel::Location)

    parent location to assign to the location

  • :children,'children' (Array<Motel::Location>)

    child locations to assign to the location

  • :x,'x' (Integer, Float)

    x coodinate of location

  • :y,'y' (Integer, Float)

    y coodinate of location

  • :z,'z' (Integer, Float)

    z coodinate of location

  • :orientation_x,'orientation_x' (Integer, Float)

    orientation_x coodinate of location

  • :orientation_y,'orientation_y' (Integer, Float)

    orientation_y coodinate of location

  • :orientation_z,'orientation_z' (Integer, Float)

    orientation_z coodinate of location

  • :movement_strategy,'movement_strategy' (Motel::MovementStrategy)

    movement strategy to assign to location

  • Hash (Hash<String,Motel::Callbacks> :callbacks, 'callbacks' hash of events/callbacks to assign to location)

    :callbacks,'callbacks' hash of events/callbacks to assign to location

  • :restrict_view,'restrict_view' (true, false)

    whether or not access to this location is restricted

  • :restrict_modify,'restrict_modify' (true, false)

    whether or not modifications to this location is restricted


131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/motel/location.rb', line 131

def initialize(args = {})
   reset_tracked_attributes

   @x, @y, @z = *(args[:coordinates] || args['coordinates'] || [])

   @orientation_x, @orientation_y, @orientation_z =
     *(args[:orientation] || args['orientation'] || [])

   # default to the stopped movement strategy
   attr_from_args args,
     :movement_strategy => MovementStrategies::Stopped.instance,
     :next_movement_strategy => nil,
     :callbacks         => Hash.new { |h,k| h[k] = [] },
     :children          => [],
     :parent            => nil,
     :parent_id         => nil,
     :id                => nil,
     :x                 => @x,
     :y                 => @y,
     :z                 => @z,
     :orientation_x     => @orientation_x,
     :orientation_y     => @orientation_y,
     :orientation_z     => @orientation_z,
     :orx               => @orientation_x,
     :ory               => @orientation_y,
     :orz               => @orientation_z,
     :distance_moved    => @distance_moved,
     :angle_rotated     => @angle_rotated,
     :restrict_view     => true,
     :restrict_modify   => true,
     :last_moved_at     => nil

   self.last_moved_at = Time.parse(self.last_moved_at) if self.last_moved_at.is_a?(String)

   # convert string callback keys into symbols
   callbacks.keys.each { |k|
     # ensure string correspond's to
     # valid callback type before interning
     if k.is_a?(String)
       if LOCATION_EVENTS.collect { |e| e.to_s }.include?(k)
         callbacks[k.intern] = callbacks[k]
         callbacks.delete(k)
       else
         raise ArgumentError, "invalid callback specified"
       end
     end
   }

   # no parsing errors will be raised (invalid conversions will be set to 0),
   # TODO use alternate conversions / raise error ?
   [:@x, :@y, :@z,
    :@orientation_x, :@orientation_y, :@orientation_z].each { |p|
     v = self.instance_variable_get(p)
     self.instance_variable_set(p, v.to_f) unless v.nil?
   }
end

Instance Attribute Details

#angle_rotatedObject

Angle rotated since the last reset


107
108
109
# File 'lib/motel/location.rb', line 107

def angle_rotated
  @angle_rotated
end

#callbacksObject

Hash<String, Motel::Callback>

Callbacks to be invoked on various events


89
90
91
# File 'lib/motel/location.rb', line 89

def callbacks
  @callbacks
end

#childrenObject

Array<Motel::Location>

child locations


53
54
55
# File 'lib/motel/location.rb', line 53

def children
  @children
end

#distance_movedObject

Distance moved since the last reset


104
105
106
# File 'lib/motel/location.rb', line 104

def distance_moved
  @distance_moved
end

#idObject

ID of location


32
33
34
# File 'lib/motel/location.rb', line 32

def id
  @id
end

#last_moved_atObject

Time the location was last moved. Used internally in the motel subsystem


101
102
103
# File 'lib/motel/location.rb', line 101

def last_moved_at
  @last_moved_at
end

#movement_strategyObject Also known as: ms

Motel::MovementStrategy

Movement strategy through which to move location


74
75
76
# File 'lib/motel/location.rb', line 74

def movement_strategy
  @movement_strategy
end

#next_movement_strategyObject

Next movement strategy, optionally used to register a movement strategy which to set next


85
86
87
# File 'lib/motel/location.rb', line 85

def next_movement_strategy
  @next_movement_strategy
end

#orientation_xObject Also known as: orx

Unit vector corresponding to Orientation of the location


65
66
67
# File 'lib/motel/location.rb', line 65

def orientation_x
  @orientation_x
end

#orientation_yObject Also known as: ory

Unit vector corresponding to Orientation of the location


65
66
67
# File 'lib/motel/location.rb', line 65

def orientation_y
  @orientation_y
end

#orientation_zObject Also known as: orz

Unit vector corresponding to Orientation of the location


65
66
67
# File 'lib/motel/location.rb', line 65

def orientation_z
  @orientation_z
end

#parentObject

Motel::Location

parent location


50
51
52
# File 'lib/motel/location.rb', line 50

def parent
  @parent
end

#parent_idObject

ID of location's parent


35
36
37
# File 'lib/motel/location.rb', line 35

def parent_id
  @parent_id
end

#restrict_modifyObject

Boolean flag indicating if permission checks should restrict modification of this location


97
98
99
# File 'lib/motel/location.rb', line 97

def restrict_modify
  @restrict_modify
end

#restrict_viewObject

Boolean flag indicating if permission checks should restrict access to this location


93
94
95
# File 'lib/motel/location.rb', line 93

def restrict_view
  @restrict_view
end

#xObject

Coordinates relative to location's parent


47
48
49
# File 'lib/motel/location.rb', line 47

def x
  @x
end

#yObject

Coordinates relative to location's parent


47
48
49
# File 'lib/motel/location.rb', line 47

def y
  @y
end

#zObject

Coordinates relative to location's parent


47
48
49
# File 'lib/motel/location.rb', line 47

def z
  @z
end

Class Method Details

.basic(id) ⇒ Object

Create a minimal valid location with id


413
414
415
# File 'lib/motel/location.rb', line 413

def self.basic(id)
  Location.new :coordinates => [0,0,0], :orientation => [0,0,1], :id => id
end

.json_create(o) ⇒ Object

Create new location from json representation


402
403
404
405
# File 'lib/motel/location.rb', line 402

def self.json_create(o)
  loc = new(o['data'])
  return loc
end

.random(args = {}) ⇒ Object

Create a random location and return it.

Options Hash (args):

  • :max,'max' (Float)

    max value of the x,y,z coordinates

  • :min,'min' (Float)

    min value of the x,y,z coordinates

  • :max_x,'max_x' (Float)

    max value of the x coordinate

  • :max_y,'max_y' (Float)

    max value of the y coordinate

  • :max_z,'max_z' (Float)

    max value of the z coordinate

  • :min_x,'min_x' (Float)

    max value of the x coordinate

  • :min_y,'min_y' (Float)

    max value of the y coordinate

  • :min_z,'min_z' (Float)

    max value of the z coordinate


427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# File 'lib/motel/location.rb', line 427

def self.random(args = {})
  max_x = max_y = max_z = nil
  max_x = max_y = max_z = args[:max] if args.has_key?(:max)
  max_x = args[:max_x] if args.has_key?(:max_x)
  max_y = args[:max_y] if args.has_key?(:max_y)
  max_z = args[:max_z] if args.has_key?(:max_z)

  min_x = min_y = min_z = 0
  min_x = min_y = min_z = args[:min] if args.has_key?(:min)
  min_x = args[:min_x] if args.has_key?(:min_x)
  min_y = args[:min_y] if args.has_key?(:min_y)
  min_z = args[:min_z] if args.has_key?(:min_z)

  # TODO this is a little weird w/ the semantics of the 'min'
  # arguments, at some point look into changing this
  nx = rand(2) == 0 ? -1 : 1
  ny = rand(2) == 0 ? -1 : 1
  nz = rand(2) == 0 ? -1 : 1

  new_x = ((min_x.nil? ? 0 : min_x) + (max_x.nil? ? rand : rand(max_x - min_x))) * nx
  new_y = ((min_y.nil? ? 0 : min_y) + (max_y.nil? ? rand : rand(max_y - min_y))) * ny
  new_z = ((min_z.nil? ? 0 : min_z) + (max_z.nil? ? rand : rand(max_z - min_z))) * nz
  new_args = {:coordinates => [new_x,new_y,new_z], :orientation => [0,0,1]}.merge args

  loc = Location.new new_args
  return loc
end

Instance Method Details

#+(values) ⇒ Motel::Location

Add specified quantities to each coordinate component and return new location

Examples:

loc = Motel::Location.new(:id => 42, :x => 100, :y => -100, :z => -200)
loc2 = loc + [100, 100, 100]
loc2   # => loc-(200, 0, -100)
loc    # => loc-42(100, -100, -200)

354
355
356
357
358
359
360
361
# File 'lib/motel/location.rb', line 354

def +(values)
  loc = Location.new
  loc.update(self)
  loc.x += values[0]
  loc.y += values[1]
  loc.z += values[2]
  loc
end

#-(location) ⇒ Float

Return the distance between this location and specified other

Examples:

loc1 = Motel::Location.new :x => 100
loc2 = Motel::Location.new :x => 200
loc1 - loc2    # => 100
loc2 - loc1    # => 100

337
338
339
340
341
342
# File 'lib/motel/location.rb', line 337

def -(location)
  dx = x - location.x
  dy = y - location.y
  dz = z - location.z
  Math.sqrt(dx ** 2 + dy ** 2 + dz ** 2)
end

#add_child(child) ⇒ Object

Add new child to location


295
296
297
# File 'lib/motel/location.rb', line 295

def add_child(child)
  @children << child unless @children.include?(child)
end

#cloneObject

Return clone of location


408
409
410
# File 'lib/motel/location.rb', line 408

def clone
  RJR::JSONParser.parse self.to_json
end

#coordinatesArray<Float,Float,Float> Also known as: coords

Return this location's coordinates in an array

location's x,y,z coordinates


228
229
230
# File 'lib/motel/location.rb', line 228

def coordinates
  [@x, @y, @z]
end

#coordinates=(*c) ⇒ Object Also known as: coords=

Set this location's coordiatnes


234
235
236
237
# File 'lib/motel/location.rb', line 234

def coordinates=(*c)
  c.flatten! if c.first.is_a?(Array)
  @x, @y, @z = *c
end

#each_child(&bl) ⇒ Object

Traverse all chilren recursively, calling specified block for each


281
282
283
284
285
286
287
288
289
290
# File 'lib/motel/location.rb', line 281

def each_child(&bl)
   children.each { |child|
      if bl.arity == 1
        bl.call child
      elsif bl.arity == 2
        bl.call self, child
      end
      child.each_child &bl
   }
end

#orientationObject

Return this location's orientation in an array


247
248
249
# File 'lib/motel/location.rb', line 247

def orientation
  [@orientation_x, @orientation_y, @orientation_z]
end

#orientation=(*o) ⇒ Object

Set this location's orientation


252
253
254
255
# File 'lib/motel/location.rb', line 252

def orientation=(*o)
  o.flatten! if o.first.is_a?(Array)
  @orientation_x, @orientation_y, @orientation_z = *o
end

#orientation_difference(x, y, z) ⇒ Object

Return axis angle between location's orientation and the specified coordinate.

Raises:

  • (ArgumentError)

263
264
265
266
267
268
# File 'lib/motel/location.rb', line 263

def orientation_difference(x, y, z)
  dx = x - @x ; dy = y - @y ; dz = z - @z
  raise ArgumentError if dx == 0 && dy == 0 && dz == 0
  #return [0, 0, 0, 1] if dx == 0 && dy == 0 && dz == 0
  Motel.axis_angle(orx, ory, orz, dx, dy, dz)
end

#oriented_towards?(x, y, z) ⇒ Boolean

Return boolean indicating if location is oriented towards the specified coordinate


258
259
260
# File 'lib/motel/location.rb', line 258

def oriented_towards?(x, y, z)
  orientation_difference(x, y, z).first == 0
end

#raise_event(evnt, *args) ⇒ Object

Invoke callbacks for the specified event


218
219
220
221
222
# File 'lib/motel/location.rb', line 218

def raise_event(evnt, *args)
  @callbacks[evnt].each { |cb|
    cb.invoke self, *args if cb.should_invoke? self, *args
  } if @callbacks.has_key?(evnt)
end

#remove_child(child) ⇒ Object

Remove child from location


302
303
304
# File 'lib/motel/location.rb', line 302

def remove_child(child)
  @children.reject!{ |ch| ch == child || ch.id == child }
end

#reset_tracked_attributesObject

Resets attributes used to internally track location


110
111
112
113
# File 'lib/motel/location.rb', line 110

def reset_tracked_attributes
  @distance_moved = 0
  @angle_rotated  = 0
end

#rootMotel::Location

Return the root location on this location's heirarchy tree


273
274
275
276
# File 'lib/motel/location.rb', line 273

def root
  return self if parent.nil?
  return parent.root
end

#scalarObject Also known as: abs

Return abs scalar value of location's coordinates


241
242
243
# File 'lib/motel/location.rb', line 241

def scalar
  Math.sqrt(@x**2 + @y**2 + @z**2)
end

#stopped?Boolean

true/false indicating if movement strategy is stopped


79
80
81
# File 'lib/motel/location.rb', line 79

def stopped?
  self.movement_strategy == Motel::MovementStrategies::Stopped.instance
end

#to_json(*a) ⇒ Object

Convert location to json representation and return it


364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/motel/location.rb', line 364

def to_json(*a)
  {
    'json_class' => self.class.name,
    'data'       =>
      {:id => id,
       :distance_moved => distance_moved,
       :angle_rotated  => angle_rotated,
       :x => x, :y => y, :z => z,
       :orientation_x => orientation_x,
       :orientation_y => orientation_y,
       :orientation_z => orientation_z,
       :restrict_view => restrict_view, :restrict_modify => restrict_modify,
       :parent_id => parent_id,
       :children  => children,
       :movement_strategy => movement_strategy,
       :next_movement_strategy => next_movement_strategy,
       :callbacks => callbacks,
       :last_moved_at =>
         last_moved_at.nil? ? nil : last_moved_at.strftime("%d %b %Y %H:%M:%S.%5N")}
  }.to_json(*a)
end

#to_sObject

Convert location to human readable string and return it


387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/motel/location.rb', line 387

def to_s
  s = "loc##{id}" +
      "(@#{parent_id.nil? ? nil : parent_id[0...8]}"
  if coordinates.size == 3 && coordinates.all?{ |c| c.numeric? }
    s += ":#{x.round_to(2)},#{y.round_to(2)},#{z.round_to(2)}"
  end
  if orientation.size == 3 && orientation.all? { |o| o.numeric? }
    s += ">#{orx.round_to(2)},#{ory.round_to(2)},#{orz.round_to(2)}"
  end
  s += " via #{movement_strategy}"
  s += ")"
  s
end

#total_xObject

Return the absolute 'x' value of this location, eg the sum of the x value of this location and that of all its parents


308
309
310
311
# File 'lib/motel/location.rb', line 308

def total_x
  return 0 if parent.nil?
  return parent.total_x + x
end

#total_yObject

Return the absolute 'y' value of this location, eg the sum of the y value of this location and that of all its parents


315
316
317
318
# File 'lib/motel/location.rb', line 315

def total_y
  return 0 if parent.nil?
  return parent.total_y + y
end

#total_zObject

Return the absolute 'z' value of this location, eg the sum of the z value of this location and that of all its parents


322
323
324
325
# File 'lib/motel/location.rb', line 322

def total_z
  return 0 if parent.nil?
  return parent.total_z + z
end

#update(location) ⇒ Object

Update this location's attributes from other location


191
192
193
194
195
196
# File 'lib/motel/location.rb', line 191

def update(location)
   update_from(location, :x, :y, :z, :parent, :parent_id,
                         :orientation_x, :orientation_y, :orientation_z,
                         :movement_strategy, :next_movement_strategy,
                         :restrict_view, :restrict_modify, :last_moved_at)
end

#valid?Boolean

Validate the location's properties

Currently tests

  • id is set

  • x, y, z are numeric

  • orientation is numeric

  • movement strategy is valid


207
208
209
210
211
212
213
214
215
# File 'lib/motel/location.rb', line 207

def valid?
  !@id.nil? &&

  [@x, @y, @z, @orientation_x,@orientation_y, @orientation_z].
    all? { |i| i.numeric? } &&

  @movement_strategy.kind_of?(MovementStrategy) &&
  @movement_strategy.valid?
end