def my_constructor(
      self
    , STARTING_MATRIX
    , STARTING_SCALE
    , INPUT_DEVICE_TYPE
    , INPUT_DEVICE_NAME
    , NO_TRACKING_MAT
    , GROUND_FOLLOWING_SETTINGS
    , INVERT
    , TRACE_VISIBILITY_LIST
    , DEVICE_TRACKING_NAME = None
    , IS_REQUESTABLE = False
    , REQUEST_BUTTON_NUM = None
    , REACTS_ON_PORTAL_TRANSIT = False
    ):

    self.list_constructor(TRACE_VISIBILITY_LIST)

    ## @var input_device_type
    # String indicating the type of input device to be created, e.g. "XBoxController" or "OldSpheron"
    self.input_device_type = INPUT_DEVICE_TYPE

    ## @var input_device_name
    # Name of the input device sensor as chosen in daemon.
    self.input_device_name = INPUT_DEVICE_NAME

    if self.input_device_name == None:
      self.input_device_name = "keyboard"

    ## @var start_matrix
    # Initial position matrix of the navigation.
    self.start_matrix = STARTING_MATRIX

    ## @var start_scale
    # Initial scaling factor of the navigation.
    self.start_scale = STARTING_SCALE
    
    # create device
    ## @var device
    # Device instance handling relative inputs of physical device.
    if self.input_device_type == "OldSpheron":
      self.device = OldSpheronDevice()
      self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME, NO_TRACKING_MAT)
    elif self.input_device_type == "NewSpheron":
      self.device = NewSpheronDevice()
      self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME, NO_TRACKING_MAT)
    elif self.input_device_type == "XBoxController":
      self.device = XBoxDevice()
      self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME, NO_TRACKING_MAT)
    elif self.input_device_type == "KeyboardMouse":
      self.device = KeyboardMouseDevice()
      self.device.my_constructor(NO_TRACKING_MAT)
    elif self.input_device_type == "Spacemouse":
      self.device = SpacemouseDevice()
      self.device.my_constructor(INPUT_DEVICE_NAME, NO_TRACKING_MAT)
    elif self.input_device_type == "Globefish":
      self.device = GlobefishDevice()
      self.device.my_constructor(INPUT_DEVICE_NAME, NO_TRACKING_MAT)

    
    # create ground following
    ## @var groundfollowing
    # GroundFollowing instance to correct the absolute matrices with respect to gravity.
    self.groundfollowing = GroundFollowing()
    self.groundfollowing.my_constructor(self.device.sf_station_mat, float(GROUND_FOLLOWING_SETTINGS[1]))

    # create input mapping
    ## @var inputmapping
    # InputMapping instance to process and map relative device inputs to an absolute matrix.
    self.inputmapping = InputMapping()
    self.inputmapping.my_constructor(self, self.device, self.groundfollowing, STARTING_MATRIX, INVERT)
    self.inputmapping.set_input_factors(self.device.translation_factor, self.device.rotation_factor)
    self.inputmapping.sf_scale.value = STARTING_SCALE

    # activate correct input mapping mode according to configuration file
    if GROUND_FOLLOWING_SETTINGS[0]:
      self.inputmapping.activate_realistic_mode()
    else:
      self.inputmapping.deactivate_realistic_mode()

    # init field connections
    self.sf_reset_trigger.connect_from(self.device.sf_reset_trigger)
    self.sf_coupling_trigger.connect_from(self.device.sf_coupling_trigger)
    self.sf_dof_trigger.connect_from(self.device.sf_dof_trigger)
    self.sf_abs_mat.connect_from(self.inputmapping.sf_abs_mat)
    self.sf_scale.connect_from(self.inputmapping.sf_scale)

    # attributes
    ## @var in_dofchange_animation
    # Boolean variable to indicate if a movement animation for a DOF change (realistic/unrealistic) is in progress.
    self.in_dofchange_animation = False

    ## @var timer
    # Instance of TimeSensor to handle the duration of animations.
    self.timer = avango.nodes.TimeSensor()

    # create trace and add 'Shadeless' to material string to have a nicer line apperance
    try:
      _device_pos = self.device.sf_station_mat.value.get_translate()
    except:
      _device_pos = avango.gua.Vec3(0.0, 0.0, 0.0)

    ## @var trace
    # Instance of Trace class to handle trace drawing of this navigation's movements.  
    self.trace = Trace(str(self), 100, 50.0, self.sf_abs_mat.value * avango.gua.make_trans_mat(_device_pos.x, 0, _device_pos.z), self.trace_material + 'Shadeless')

    ## @var is_requestable
    # Boolean saying if this Navigation is a requestable one. Requestable navigations
    # can be switched to using a special button on the device.
    self.is_requestable = IS_REQUESTABLE

    # connect request button
    if self.is_requestable:
      exec("self.sf_request_trigger.connect_from(self.device.device_sensor.Button" + str(REQUEST_BUTTON_NUM) + ")")

    ## @var reacts_on_portal_transit
    # Boolean saying if this navigation is allowed to be reset by portal transitions.
    self.reacts_on_portal_transit = REACTS_ON_PORTAL_TRANSIT

    # evaluate every frame
    self.always_evaluate(True)
Example #2
0
    def my_constructor(self,
                       STARTING_MATRIX,
                       STARTING_SCALE,
                       INPUT_DEVICE_TYPE,
                       INPUT_DEVICE_NAME,
                       NO_TRACKING_MAT,
                       GROUND_FOLLOWING_SETTINGS,
                       INVERT,
                       TRACE_VISIBILITY_LIST,
                       DEVICE_TRACKING_NAME=None,
                       IS_REQUESTABLE=False,
                       REQUEST_BUTTON_NUM=None,
                       REACTS_ON_PORTAL_TRANSIT=False):

        self.list_constructor(TRACE_VISIBILITY_LIST)

        ## @var input_device_type
        # String indicating the type of input device to be created, e.g. "XBoxController" or "OldSpheron"
        self.input_device_type = INPUT_DEVICE_TYPE

        ## @var input_device_name
        # Name of the input device sensor as chosen in daemon.
        self.input_device_name = INPUT_DEVICE_NAME

        if self.input_device_name == None:
            self.input_device_name = "keyboard"

        ## @var start_matrix
        # Initial position matrix of the navigation.
        self.start_matrix = STARTING_MATRIX

        ## @var start_scale
        # Initial scaling factor of the navigation.
        self.start_scale = STARTING_SCALE

        # create device
        ## @var device
        # Device instance handling relative inputs of physical device.
        if self.input_device_type == "OldSpheron":
            self.device = OldSpheronDevice()
            self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME,
                                       NO_TRACKING_MAT)
        elif self.input_device_type == "NewSpheron":
            self.device = NewSpheronDevice()
            self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME,
                                       NO_TRACKING_MAT)
        elif self.input_device_type == "XBoxController":
            self.device = XBoxDevice()
            self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME,
                                       NO_TRACKING_MAT)
        elif self.input_device_type == "KeyboardMouse":
            self.device = KeyboardMouseDevice()
            self.device.my_constructor(NO_TRACKING_MAT)
        elif self.input_device_type == "Spacemouse":
            self.device = SpacemouseDevice()
            self.device.my_constructor(INPUT_DEVICE_NAME, NO_TRACKING_MAT)
        elif self.input_device_type == "Globefish":
            self.device = GlobefishDevice()
            self.device.my_constructor(INPUT_DEVICE_NAME, NO_TRACKING_MAT)

        # create ground following
        ## @var groundfollowing
        # GroundFollowing instance to correct the absolute matrices with respect to gravity.
        self.groundfollowing = GroundFollowing()
        self.groundfollowing.my_constructor(
            self.device.sf_station_mat, float(GROUND_FOLLOWING_SETTINGS[1]))

        # create input mapping
        ## @var inputmapping
        # InputMapping instance to process and map relative device inputs to an absolute matrix.
        self.inputmapping = InputMapping()
        self.inputmapping.my_constructor(self, self.device,
                                         self.groundfollowing, STARTING_MATRIX,
                                         INVERT)
        self.inputmapping.set_input_factors(self.device.translation_factor,
                                            self.device.rotation_factor)
        self.inputmapping.sf_scale.value = STARTING_SCALE

        # activate correct input mapping mode according to configuration file
        if GROUND_FOLLOWING_SETTINGS[0]:
            self.inputmapping.activate_realistic_mode()
        else:
            self.inputmapping.deactivate_realistic_mode()

        # init field connections
        self.sf_reset_trigger.connect_from(self.device.sf_reset_trigger)
        self.sf_coupling_trigger.connect_from(self.device.sf_coupling_trigger)
        self.sf_dof_trigger.connect_from(self.device.sf_dof_trigger)
        self.sf_abs_mat.connect_from(self.inputmapping.sf_abs_mat)
        self.sf_scale.connect_from(self.inputmapping.sf_scale)

        # attributes
        ## @var in_dofchange_animation
        # Boolean variable to indicate if a movement animation for a DOF change (realistic/unrealistic) is in progress.
        self.in_dofchange_animation = False

        ## @var timer
        # Instance of TimeSensor to handle the duration of animations.
        self.timer = avango.nodes.TimeSensor()

        # create trace and add 'Shadeless' to material string to have a nicer line apperance
        try:
            _device_pos = self.device.sf_station_mat.value.get_translate()
        except:
            _device_pos = avango.gua.Vec3(0.0, 0.0, 0.0)

        ## @var trace
        # Instance of Trace class to handle trace drawing of this navigation's movements.
        self.trace = Trace(
            str(self), 100, 50.0,
            self.sf_abs_mat.value *
            avango.gua.make_trans_mat(_device_pos.x, 0, _device_pos.z),
            self.trace_material + 'Shadeless')

        ## @var is_requestable
        # Boolean saying if this Navigation is a requestable one. Requestable navigations
        # can be switched to using a special button on the device.
        self.is_requestable = IS_REQUESTABLE

        # connect request button
        if self.is_requestable:
            exec(
                "self.sf_request_trigger.connect_from(self.device.device_sensor.Button"
                + str(REQUEST_BUTTON_NUM) + ")")

        ## @var reacts_on_portal_transit
        # Boolean saying if this navigation is allowed to be reset by portal transitions.
        self.reacts_on_portal_transit = REACTS_ON_PORTAL_TRANSIT

        # evaluate every frame
        self.always_evaluate(True)
class SteeringNavigation(Navigation):

  ## Default constructor.
  def __init__(self):
    self.super(SteeringNavigation).__init__()

  ## Custom constructor.
  # @param STARTING_MATRIX Initial position matrix of the navigation to be created.
  # @param STARTING_SCALE Start scaling of the navigation.
  # @param INPUT_DEVICE_TYPE String indicating the type of input device to be created, e.g. "XBoxController" or "OldSpheron"
  # @param INPUT_DEVICE_NAME Name of the input device sensor as chosen in daemon.
  # @param NO_TRACKING_MAT Matrix which should be applied if no tracking is available.
  # @param GROUND_FOLLOWING_SETTINGS Setting list for the GroundFollowing instance: [activated, ray_start_height]
  # @param INVERT Boolean indicating if the input values should be inverted.
  # @param TRACE_VISIBILITY_LIST A list containing visibility rules according to the DisplayGroups' visibility tags. 
  # @param DEVICE_TRACKING_NAME Name of the device's tracking target name as chosen in daemon.
  # @param IS_REQUESTABLE Boolean saying if this Navigation is a requestable one. Requestable navigations can be switched to using a special button on the device.
  # @param REQUEST_BUTTON_NUM Button number of the device's sensor which should be used for the request mechanism.
  # @param REACTS_ON_PORTAL_TRANSIT Boolean saying if this navigation is allowed to be reset by portal transitions.
  def my_constructor(
      self
    , STARTING_MATRIX
    , STARTING_SCALE
    , INPUT_DEVICE_TYPE
    , INPUT_DEVICE_NAME
    , NO_TRACKING_MAT
    , GROUND_FOLLOWING_SETTINGS
    , INVERT
    , TRACE_VISIBILITY_LIST
    , DEVICE_TRACKING_NAME = None
    , IS_REQUESTABLE = False
    , REQUEST_BUTTON_NUM = None
    , REACTS_ON_PORTAL_TRANSIT = False
    ):

    self.list_constructor(TRACE_VISIBILITY_LIST)

    ## @var input_device_type
    # String indicating the type of input device to be created, e.g. "XBoxController" or "OldSpheron"
    self.input_device_type = INPUT_DEVICE_TYPE

    ## @var input_device_name
    # Name of the input device sensor as chosen in daemon.
    self.input_device_name = INPUT_DEVICE_NAME

    if self.input_device_name == None:
      self.input_device_name = "keyboard"

    ## @var start_matrix
    # Initial position matrix of the navigation.
    self.start_matrix = STARTING_MATRIX

    ## @var start_scale
    # Initial scaling factor of the navigation.
    self.start_scale = STARTING_SCALE
    
    # create device
    ## @var device
    # Device instance handling relative inputs of physical device.
    if self.input_device_type == "OldSpheron":
      self.device = OldSpheronDevice()
      self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME, NO_TRACKING_MAT)
    elif self.input_device_type == "NewSpheron":
      self.device = NewSpheronDevice()
      self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME, NO_TRACKING_MAT)
    elif self.input_device_type == "XBoxController":
      self.device = XBoxDevice()
      self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME, NO_TRACKING_MAT)
    elif self.input_device_type == "KeyboardMouse":
      self.device = KeyboardMouseDevice()
      self.device.my_constructor(NO_TRACKING_MAT)
    elif self.input_device_type == "Spacemouse":
      self.device = SpacemouseDevice()
      self.device.my_constructor(INPUT_DEVICE_NAME, NO_TRACKING_MAT)
    elif self.input_device_type == "Globefish":
      self.device = GlobefishDevice()
      self.device.my_constructor(INPUT_DEVICE_NAME, NO_TRACKING_MAT)

    
    # create ground following
    ## @var groundfollowing
    # GroundFollowing instance to correct the absolute matrices with respect to gravity.
    self.groundfollowing = GroundFollowing()
    self.groundfollowing.my_constructor(self.device.sf_station_mat, float(GROUND_FOLLOWING_SETTINGS[1]))

    # create input mapping
    ## @var inputmapping
    # InputMapping instance to process and map relative device inputs to an absolute matrix.
    self.inputmapping = InputMapping()
    self.inputmapping.my_constructor(self, self.device, self.groundfollowing, STARTING_MATRIX, INVERT)
    self.inputmapping.set_input_factors(self.device.translation_factor, self.device.rotation_factor)
    self.inputmapping.sf_scale.value = STARTING_SCALE

    # activate correct input mapping mode according to configuration file
    if GROUND_FOLLOWING_SETTINGS[0]:
      self.inputmapping.activate_realistic_mode()
    else:
      self.inputmapping.deactivate_realistic_mode()

    # init field connections
    self.sf_reset_trigger.connect_from(self.device.sf_reset_trigger)
    self.sf_coupling_trigger.connect_from(self.device.sf_coupling_trigger)
    self.sf_dof_trigger.connect_from(self.device.sf_dof_trigger)
    self.sf_abs_mat.connect_from(self.inputmapping.sf_abs_mat)
    self.sf_scale.connect_from(self.inputmapping.sf_scale)

    # attributes
    ## @var in_dofchange_animation
    # Boolean variable to indicate if a movement animation for a DOF change (realistic/unrealistic) is in progress.
    self.in_dofchange_animation = False

    ## @var timer
    # Instance of TimeSensor to handle the duration of animations.
    self.timer = avango.nodes.TimeSensor()

    # create trace and add 'Shadeless' to material string to have a nicer line apperance
    try:
      _device_pos = self.device.sf_station_mat.value.get_translate()
    except:
      _device_pos = avango.gua.Vec3(0.0, 0.0, 0.0)

    ## @var trace
    # Instance of Trace class to handle trace drawing of this navigation's movements.  
    self.trace = Trace(str(self), 100, 50.0, self.sf_abs_mat.value * avango.gua.make_trans_mat(_device_pos.x, 0, _device_pos.z), self.trace_material + 'Shadeless')

    ## @var is_requestable
    # Boolean saying if this Navigation is a requestable one. Requestable navigations
    # can be switched to using a special button on the device.
    self.is_requestable = IS_REQUESTABLE

    # connect request button
    if self.is_requestable:
      exec("self.sf_request_trigger.connect_from(self.device.device_sensor.Button" + str(REQUEST_BUTTON_NUM) + ")")

    ## @var reacts_on_portal_transit
    # Boolean saying if this navigation is allowed to be reset by portal transitions.
    self.reacts_on_portal_transit = REACTS_ON_PORTAL_TRANSIT

    # evaluate every frame
    self.always_evaluate(True)

  ## Resets the navigation's matrix to the initial value.
  def reset(self):
    self.inputmapping.set_abs_mat(self.start_matrix)
    self.inputmapping.set_scale(self.start_scale)

    self.trace.clear(self.start_matrix)

  ## Activates 3-DOF (realistic) navigation mode.
  def activate_realistic_mode(self):

    # remove pitch and roll from current orientation
    _current_mat = self.sf_abs_mat.value
    _current_trans = _current_mat.get_translate()
    _current_yaw = Utilities.get_yaw(_current_mat)

    ## @var start_rot
    # Quaternion representing the start rotation of the animation
    self.start_rot = self.sf_abs_mat.value.get_rotate()

    ## @var target_rot
    # Quaternion representing the target rotation of the animation
    self.target_rot = avango.gua.make_rot_mat(math.degrees(_current_yaw), 0, 1, 0).get_rotate()

    ## @var animation_time
    # Time of the rotation animation in relation to the rotation distance.
    self.animation_time = 2 * math.sqrt(math.pow(self.start_rot.x - self.target_rot.x, 2) \
      + math.pow(self.start_rot.y - self.target_rot.y, 2) \
      + math.pow(self.start_rot.z - self.target_rot.z, 2) \
      + math.pow(self.start_rot.w - self.target_rot.w, 2))
   
    # if no animation is needed, set animation time to a minimum value to avoid division by zero
    if self.animation_time == 0.0:
      self.animation_time = 0.01

    ## @var start_trans
    # Starting translation vector of the animation.
    self.start_trans = _current_trans

    ## @var animation_start_time
    # Point in time where the animation started.
    self.animation_start_time = self.timer.Time.value
 
    self.in_dofchange_animation = True                       
  
  ## Animates the removal of pitch and roll angles when switching from 6-DOF (unrealistic) to 3-DOF (realistic) navigation mode.
  def animate_dofchange(self):

    _current_time = self.timer.Time.value
    _slerp_ratio = (_current_time - self.animation_start_time) / self.animation_time

    # when end of animation is reached
    if _slerp_ratio > 1:
      _slerp_ratio = 1
      self.in_dofchange_animation = False
      self.inputmapping.activate_realistic_mode()

    # compute slerp position and set it on the player's inputmapping
    _transformed_quat = self.start_rot.slerp_to(self.target_rot, _slerp_ratio)

    _position_yaw_mat = avango.gua.make_trans_mat(self.start_trans.x, self.start_trans.y, self.start_trans.z) * \
                        avango.gua.make_rot_mat(_transformed_quat)

    self.inputmapping.set_abs_mat(_position_yaw_mat)

  ## Activates 6-DOF (unrealistic) navigation mode.
  def deactivate_realistic_mode(self):
    self.inputmapping.deactivate_realistic_mode()


  ## Switches from realistic to unrealistic or from unrealistic to realistic mode on this
  # and all other coupled instances.
  def trigger_dofchange(self):

    # if in realistic mode, switch to unrealistic mode
    if self.inputmapping.realistic == True:
      #print "GF off"
      self.deactivate_realistic_mode()
    
    # if in unrealistic mode, switch to realistic mode
    else:
      #print "GF on"
      self.activate_realistic_mode()
  
  ## Evaluated every frame.
  def evaluate(self):

    # handle dofchange animation
    if self.in_dofchange_animation:
      self.animate_dofchange()

    # draw the traces if enabled
    if len(self.active_user_representations) > 0:
      _device_pos = self.device.sf_station_mat.value.get_translate()
      self.trace.update(self.sf_abs_mat.value * avango.gua.make_trans_mat(_device_pos.x, 0, _device_pos.z))

    # update sf_nav_mat
    self.sf_nav_mat.value = self.sf_abs_mat.value * avango.gua.make_scale_mat(self.sf_scale.value)
Example #4
0
class SteeringNavigation(Navigation):

    ## Default constructor.
    def __init__(self):
        self.super(SteeringNavigation).__init__()

    ## Custom constructor.
    # @param STARTING_MATRIX Initial position matrix of the navigation to be created.
    # @param STARTING_SCALE Start scaling of the navigation.
    # @param INPUT_DEVICE_TYPE String indicating the type of input device to be created, e.g. "XBoxController" or "OldSpheron"
    # @param INPUT_DEVICE_NAME Name of the input device sensor as chosen in daemon.
    # @param NO_TRACKING_MAT Matrix which should be applied if no tracking is available.
    # @param GROUND_FOLLOWING_SETTINGS Setting list for the GroundFollowing instance: [activated, ray_start_height]
    # @param INVERT Boolean indicating if the input values should be inverted.
    # @param TRACE_VISIBILITY_LIST A list containing visibility rules according to the DisplayGroups' visibility tags.
    # @param DEVICE_TRACKING_NAME Name of the device's tracking target name as chosen in daemon.
    # @param IS_REQUESTABLE Boolean saying if this Navigation is a requestable one. Requestable navigations can be switched to using a special button on the device.
    # @param REQUEST_BUTTON_NUM Button number of the device's sensor which should be used for the request mechanism.
    # @param REACTS_ON_PORTAL_TRANSIT Boolean saying if this navigation is allowed to be reset by portal transitions.
    def my_constructor(self,
                       STARTING_MATRIX,
                       STARTING_SCALE,
                       INPUT_DEVICE_TYPE,
                       INPUT_DEVICE_NAME,
                       NO_TRACKING_MAT,
                       GROUND_FOLLOWING_SETTINGS,
                       INVERT,
                       TRACE_VISIBILITY_LIST,
                       DEVICE_TRACKING_NAME=None,
                       IS_REQUESTABLE=False,
                       REQUEST_BUTTON_NUM=None,
                       REACTS_ON_PORTAL_TRANSIT=False):

        self.list_constructor(TRACE_VISIBILITY_LIST)

        ## @var input_device_type
        # String indicating the type of input device to be created, e.g. "XBoxController" or "OldSpheron"
        self.input_device_type = INPUT_DEVICE_TYPE

        ## @var input_device_name
        # Name of the input device sensor as chosen in daemon.
        self.input_device_name = INPUT_DEVICE_NAME

        if self.input_device_name == None:
            self.input_device_name = "keyboard"

        ## @var start_matrix
        # Initial position matrix of the navigation.
        self.start_matrix = STARTING_MATRIX

        ## @var start_scale
        # Initial scaling factor of the navigation.
        self.start_scale = STARTING_SCALE

        # create device
        ## @var device
        # Device instance handling relative inputs of physical device.
        if self.input_device_type == "OldSpheron":
            self.device = OldSpheronDevice()
            self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME,
                                       NO_TRACKING_MAT)
        elif self.input_device_type == "NewSpheron":
            self.device = NewSpheronDevice()
            self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME,
                                       NO_TRACKING_MAT)
        elif self.input_device_type == "XBoxController":
            self.device = XBoxDevice()
            self.device.my_constructor(INPUT_DEVICE_NAME, DEVICE_TRACKING_NAME,
                                       NO_TRACKING_MAT)
        elif self.input_device_type == "KeyboardMouse":
            self.device = KeyboardMouseDevice()
            self.device.my_constructor(NO_TRACKING_MAT)
        elif self.input_device_type == "Spacemouse":
            self.device = SpacemouseDevice()
            self.device.my_constructor(INPUT_DEVICE_NAME, NO_TRACKING_MAT)
        elif self.input_device_type == "Globefish":
            self.device = GlobefishDevice()
            self.device.my_constructor(INPUT_DEVICE_NAME, NO_TRACKING_MAT)

        # create ground following
        ## @var groundfollowing
        # GroundFollowing instance to correct the absolute matrices with respect to gravity.
        self.groundfollowing = GroundFollowing()
        self.groundfollowing.my_constructor(
            self.device.sf_station_mat, float(GROUND_FOLLOWING_SETTINGS[1]))

        # create input mapping
        ## @var inputmapping
        # InputMapping instance to process and map relative device inputs to an absolute matrix.
        self.inputmapping = InputMapping()
        self.inputmapping.my_constructor(self, self.device,
                                         self.groundfollowing, STARTING_MATRIX,
                                         INVERT)
        self.inputmapping.set_input_factors(self.device.translation_factor,
                                            self.device.rotation_factor)
        self.inputmapping.sf_scale.value = STARTING_SCALE

        # activate correct input mapping mode according to configuration file
        if GROUND_FOLLOWING_SETTINGS[0]:
            self.inputmapping.activate_realistic_mode()
        else:
            self.inputmapping.deactivate_realistic_mode()

        # init field connections
        self.sf_reset_trigger.connect_from(self.device.sf_reset_trigger)
        self.sf_coupling_trigger.connect_from(self.device.sf_coupling_trigger)
        self.sf_dof_trigger.connect_from(self.device.sf_dof_trigger)
        self.sf_abs_mat.connect_from(self.inputmapping.sf_abs_mat)
        self.sf_scale.connect_from(self.inputmapping.sf_scale)

        # attributes
        ## @var in_dofchange_animation
        # Boolean variable to indicate if a movement animation for a DOF change (realistic/unrealistic) is in progress.
        self.in_dofchange_animation = False

        ## @var timer
        # Instance of TimeSensor to handle the duration of animations.
        self.timer = avango.nodes.TimeSensor()

        # create trace and add 'Shadeless' to material string to have a nicer line apperance
        try:
            _device_pos = self.device.sf_station_mat.value.get_translate()
        except:
            _device_pos = avango.gua.Vec3(0.0, 0.0, 0.0)

        ## @var trace
        # Instance of Trace class to handle trace drawing of this navigation's movements.
        self.trace = Trace(
            str(self), 100, 50.0,
            self.sf_abs_mat.value *
            avango.gua.make_trans_mat(_device_pos.x, 0, _device_pos.z),
            self.trace_material + 'Shadeless')

        ## @var is_requestable
        # Boolean saying if this Navigation is a requestable one. Requestable navigations
        # can be switched to using a special button on the device.
        self.is_requestable = IS_REQUESTABLE

        # connect request button
        if self.is_requestable:
            exec(
                "self.sf_request_trigger.connect_from(self.device.device_sensor.Button"
                + str(REQUEST_BUTTON_NUM) + ")")

        ## @var reacts_on_portal_transit
        # Boolean saying if this navigation is allowed to be reset by portal transitions.
        self.reacts_on_portal_transit = REACTS_ON_PORTAL_TRANSIT

        # evaluate every frame
        self.always_evaluate(True)

    ## Resets the navigation's matrix to the initial value.
    def reset(self):
        self.inputmapping.set_abs_mat(self.start_matrix)
        self.inputmapping.set_scale(self.start_scale)

        self.trace.clear(self.start_matrix)

    ## Activates 3-DOF (realistic) navigation mode.
    def activate_realistic_mode(self):

        # remove pitch and roll from current orientation
        _current_mat = self.sf_abs_mat.value
        _current_trans = _current_mat.get_translate()
        _current_yaw = Utilities.get_yaw(_current_mat)

        ## @var start_rot
        # Quaternion representing the start rotation of the animation
        self.start_rot = self.sf_abs_mat.value.get_rotate()

        ## @var target_rot
        # Quaternion representing the target rotation of the animation
        self.target_rot = avango.gua.make_rot_mat(math.degrees(_current_yaw),
                                                  0, 1, 0).get_rotate()

        ## @var animation_time
        # Time of the rotation animation in relation to the rotation distance.
        self.animation_time = 2 * math.sqrt(math.pow(self.start_rot.x - self.target_rot.x, 2) \
          + math.pow(self.start_rot.y - self.target_rot.y, 2) \
          + math.pow(self.start_rot.z - self.target_rot.z, 2) \
          + math.pow(self.start_rot.w - self.target_rot.w, 2))

        # if no animation is needed, set animation time to a minimum value to avoid division by zero
        if self.animation_time == 0.0:
            self.animation_time = 0.01

        ## @var start_trans
        # Starting translation vector of the animation.
        self.start_trans = _current_trans

        ## @var animation_start_time
        # Point in time where the animation started.
        self.animation_start_time = self.timer.Time.value

        self.in_dofchange_animation = True

    ## Animates the removal of pitch and roll angles when switching from 6-DOF (unrealistic) to 3-DOF (realistic) navigation mode.
    def animate_dofchange(self):

        _current_time = self.timer.Time.value
        _slerp_ratio = (_current_time -
                        self.animation_start_time) / self.animation_time

        # when end of animation is reached
        if _slerp_ratio > 1:
            _slerp_ratio = 1
            self.in_dofchange_animation = False
            self.inputmapping.activate_realistic_mode()

        # compute slerp position and set it on the player's inputmapping
        _transformed_quat = self.start_rot.slerp_to(self.target_rot,
                                                    _slerp_ratio)

        _position_yaw_mat = avango.gua.make_trans_mat(self.start_trans.x, self.start_trans.y, self.start_trans.z) * \
                            avango.gua.make_rot_mat(_transformed_quat)

        self.inputmapping.set_abs_mat(_position_yaw_mat)

    ## Activates 6-DOF (unrealistic) navigation mode.
    def deactivate_realistic_mode(self):
        self.inputmapping.deactivate_realistic_mode()

    ## Switches from realistic to unrealistic or from unrealistic to realistic mode on this
    # and all other coupled instances.
    def trigger_dofchange(self):

        # if in realistic mode, switch to unrealistic mode
        if self.inputmapping.realistic == True:
            #print "GF off"
            self.deactivate_realistic_mode()

        # if in unrealistic mode, switch to realistic mode
        else:
            #print "GF on"
            self.activate_realistic_mode()

    ## Evaluated every frame.
    def evaluate(self):

        # handle dofchange animation
        if self.in_dofchange_animation:
            self.animate_dofchange()

        # draw the traces if enabled
        if len(self.active_user_representations) > 0:
            _device_pos = self.device.sf_station_mat.value.get_translate()
            self.trace.update(
                self.sf_abs_mat.value *
                avango.gua.make_trans_mat(_device_pos.x, 0, _device_pos.z))

        # update sf_nav_mat
        self.sf_nav_mat.value = self.sf_abs_mat.value * avango.gua.make_scale_mat(
            self.sf_scale.value)