//<copyright>
// 
// Copyright (c) 1993,94,95,96
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// 
//</copyright>

//<file>
//
// Name:        hgptrhand.C
//
// Purpose:     implementation of pointer handler
//
// Created:     17 Dec 92   Michael Pichler
//
// Changed:     16 Jan 96   Michael Pichler
//
// $Id: hgptrhand.C,v 1.8 1996/01/30 12:29:42 mpichler Exp $
//
// Description
// -----------
//
// Implementation of class HG3dInputHandler.
// Responsible for handling pointer (mouse) events.
// 
//
//</file>


/*
 * Spaceball support contributed by Dave Gosselin <drg@spacetec.com>
 */


#include "hgptrhand.h"

#include "scenewin.h"

#include "gecontext.h"
#include "geomobj.h"
#include "srcanch.h"
#include "camera.h"
#include "vecutil.h"
#include "stranslate.h"
#include "strarray.h"
#include "themenus.h"

#include <vrml/QvWWWAnchor.h>

#include <ge3d/ge3d.h>

#include <IV-look/kit.h>
#include <InterViews/event.h>
#include <InterViews/style.h>

#include <IV-X11/xdisplay.h>
#include <IV-X11/xevent.h>

#ifdef SPACEBALL
# include <IV-X11/Xdefs.h>
# include <SPW_Input.h>
# include <IV-X11/Xundefs.h>
#endif

#include <hyperg/widgets/cursors.h>
#include <hyperg/hyperg/verbose.h>

#include <iostream.h>
#include <math.h>



/* mouse dragging scaling factors ("all the magic constants") */   // [default]

// mouse movements in fractions of window width resp. height
// scene movements in fractions of scene "size"
// angle differences in radians


/* in all modes */

// overall control:
// speed factor that multiplies to all navigation constants; affects all
// settings (internal ones and those explicitly set by X-attribute)       [1.0]

float HG3dInputHandler::SpeedFactor_ = 1.0;


// camera panning/object translation with focal len of WalkFocal_ * size  [2.0]
// (objects at that distance exactly follow the mouse cursor,             [0.5]
//  objects nearer/farther move faster/slower due to perspective view)
// (FlipFocal_ for flip object, WalkFocal_ for all other modes)
// the *higher* the value the *faster* the panning

float HG3dInputHandler::FlipFocal_ = 2.0;
float HG3dInputHandler::WalkFocal_ = 0.5;


/* mode "flip object" (translate, rotate, zoom object) */

// move by window height = scene zooming by FlipZoom_ * size              [1.0]
// move by window width = horicontal scene rotation by FlipTurnHor_   [180 deg]
// move by window height = vertical scene rotation by FlipTurnVert_    [90 deg]

float HG3dInputHandler::FlipZoom_ = 1.0;
float HG3dInputHandler::FlipTurnHor_ = M_PI;
float HG3dInputHandler::FlipTurnVert_ = M_PI / 2;


/* modes "walk around"/"heads up" */

/* a) mouse dragging controls movement */

// move by window height = zooming (camera movement) by WalkZoom_ * size [0.75]
// move by window width = camera turning left-right by WalkTurnHor_    [90 deg]
// move by window height = camera turning up-down by WalkTurnVert_     [45 deg]
// over-linear motion part for zooming and rotation (f*x + f3*x^3)
// fast zoom: WalkZoom3_                                                  [2.0]
// fast horicontal turn: WalkTurnHor3_                                [720 deg]
// fast vertical turn: WalkTurnVert3_                                 [360 deg]

float HG3dInputHandler::WalkZoom_ = 0.75;
float HG3dInputHandler::WalkTurnHor_ = M_PI / 2;
float HG3dInputHandler::WalkTurnVert_ = M_PI / 4;
float HG3dInputHandler::WalkZoom3_ = 2.0;
float HG3dInputHandler::WalkTurnHor3_ = 4 * M_PI;
float HG3dInputHandler::WalkTurnVert3_ = 2 * M_PI;

/* b) mouse dragging controls velocity */

// drag by window height = zooming by VelocityZoom_
// drag by window width = turn left-right by VelocityTurnHor_
// drag by window height = turn up-down by VelocityTurnVert_
// panning (horicontal/vertical translation) by VelocityPan_
// fast movements (3rd power): VelocityZoom3_,
// fast horicontal turn: VelocityTurnHor3_,
// fast vertical turn: VelocityTurnVert3_
// fast panning: VelocityPan3_

float HG3dInputHandler::VelocityZoom_ = 0.01;
float HG3dInputHandler::VelocityPan_ = 0.1;
float HG3dInputHandler::VelocityTurnHor_ = M_PI / 16.0;
float HG3dInputHandler::VelocityTurnVert_ = M_PI / 32.0;
float HG3dInputHandler::VelocityZoom3_ = 2.0;
float HG3dInputHandler::VelocityPan3_ = 1.0;
float HG3dInputHandler::VelocityTurnHor3_ = 4 * M_PI;
float HG3dInputHandler::VelocityTurnVert3_ = 2 * M_PI;


/* mode "fly" (mouse position controls orientation, buttons speed) */

// dead zone: no movem. up to FlyDeadX_/FlyDeadY_ pixels away from middle  [20]
// FlySpeedInc_ 'speed' increment per mouse click                         [0.1]
// 'speed' is powered by 3, 1.0 = scene size
// mouse at left/right border = horicontal camera turn by FlyTurnHor_  [45 deg]
// mouse at top/bottom = vertical camera turn by FlyTurnVert_        [22.5 deg]

float HG3dInputHandler::FlyDeadX_ = 20;
float HG3dInputHandler::FlyDeadY_ = 20;
float HG3dInputHandler::FlySpeedInc_ = 0.1;
float HG3dInputHandler::FlyTurnHor_ = M_PI / 4.0;
float HG3dInputHandler::FlyTurnVert_ = M_PI / 8.0;


/* mode "fly to" (targetted movement to point of interest) */

// movement per frame FlyToTran_ (fraction of current distance)          [0.15]
// rotation per frame FlyToRot_ (fraction of current angle)              [0.25]
// FlyToRot_ should be somewhat higher than FlyToTran_

float HG3dInputHandler::FlyToTran_ = 0.15;
float HG3dInputHandler::FlyToRot_ = 0.25;


/* old mode "fly around" (mouse dragging controls movement; tran, rot, zoom) */
//#define FA_ZOOM  1.0
//#define FA_TURNHOR  (M_PI / 2.0)
//#define FA_TURNVERT  (M_PI / 4.0)
//#define FA_PANHOR 0.5
//#define FA_PANVERT 0.5


/* mode "heads up" (clickable icons for several movements) */

// inherits movement constants from "walk":
// WalkZoom_;
// WalkTurnHor_;
// WalkTurnVert_;
// WalkZoom3_;
// WalkTurnHor3_;
// WalkTurnVert3_;


HG3dInputHandler::HG3dInputHandler (Glyph* body, Style* style, SceneWindow* scene)
: InputHandler (body, style)
{
  scene_ = scene;
  camera_ = 0;
  click_ = 0;  // press without drag
  ignoredrag_ = 0;
  gecontext_ = 0;
  flyspeed_ = 0;
  flying_ = 0;
  poiset_ = 0;
  hu_icon_ = walkicon_ = _icon_none;
  hu_iconpos_ [_icon_eyes] = 72;  /* [_icon_body] + space + size */
  hu_iconpos_ [_icon_body] = 24;  /* half space + half size */
  hu_iconpos_ [_icon_lift] = -24;
  hu_iconpos_ [_icon_fly2] = -72;
}



// reset
// reset motion: turn off POI flying, stop flying_
// returns non zero if gecontext must be redrawn
// called on each change of navigation mode

int HG3dInputHandler::reset ()
{
  int redraw = 0;

  if (poiset_)
  { poiset_ = 0;
    redraw = 1;
  }
  if (flying_)
  {
    flying_ = 0;
    flyspeed_ = 0;
    scene_->interact (0);
    redraw = 1;
    gecontext_->resetCursor ();
  }
  if (hu_icon_ == _icon_fly2)
  { hu_icon_ = _icon_none;
    redraw = 1;
    gecontext_->resetCursor ();
  }

  walkicon_ = _icon_none;

  move_mode_ = scene_->navigationMode ();

  // set proper cursor
  if (move_mode_ == NavMode::fly_2)
    gecontext_->setCursor (HgCursors::instance ()->crosshair ());
  else
    gecontext_->resetCursor ();

  // giveNavigationHint now only on changing navigation modes

  if (move_mode_ == NavMode::fly_2)
    redraw |= scene_->selectObj (0);  // no anchor selection possible in this mode

  return redraw;
} // reset



void HG3dInputHandler::allocate (Canvas* c, const Allocation& a, Extension& e)
{
  InputHandler::allocate (c, a, e);

  //cerr << "stored window: " << win_ << endl;
  //cerr << "window reported by allocate: "<< c->window () << endl;

  float winwidth  = a.right () - a.left (),
        winheight = a.top () - a.bottom ();

  //cerr << "width : " << winwidth << ", height: " << winheight << endl;

  scene_->setWinAspect (winwidth/winheight);
}


void HG3dInputHandler::press (const Event& e)
{
  Window* win = e.rep ()->window_;

  float fx = tofract_x (win, e.pointer_x ()),
        fy = tofract_y (win, e.pointer_y ());

  dragx_ = downx_ = e.pointer_x () - win->width () / 2;
  dragy_ = downy_ = e.pointer_y () - win->height () / 2;

  lastfx_ = fx;
  lastfy_ = fy;

//cerr << "press (" << fx << ", " << fy << ")  ";

  // e. g. shift + click for picking at any time
  // be sure that no dragging is done in that case
  // this approach will be turned around: "real" clicks (without) dragging
  // will be handled on release of mouse button!!!

  button_ = e.pointer_button ();  // pressed button (for dragging)

  // shift and control will soon become anachronisms:
  // click selects anchors, double click activates them
  // picking: control + left mouse button (shift for object selection) or
  // simple left click when anchors highlighted and option anchormotion turned off

  click_ = 1;  // press without drag
  ignoredrag_ = 0;  // do not ignore further dragging

  int picking = (button_ == Event::left) && (e.shift_is_down () || e.control_is_down ())
             || scene_->linksActive () && !scene_->anchorMotion ();  // "pure pick mode"

  if (picking)
  {
    if (e.shift_is_down ())  // select an object
      selectObject (fx, fy);
    else  // pick an anchor
    {
      selectAnchor (fx, fy);
      scene_->activateAnchor ();
      // when anchors are selected with double click, anchor is selected in release
    }
    ignoredrag_ = 1;  // do not further handle this click
    return;
  }

  if (button_ == Event::left && e.meta_is_down ())  // meta+click: hit test (object number)
  {
    GeometricObject* hitobj = 0;
    QvNode* node = 0;
    QvWWWAnchor* anchor = 0;
    if (scene_->pickObject (fx, fy, &hitobj, &node, &anchor))
    {
      if (hitobj)
      { DEBUGNL ("Position=Object " << hitobj->getobj_num ());
      }
      else if (node)
      { DEBUGNL ("hit VRML node.");
        if (anchor)
        { DEBUGNL ("hit VRML anchor.");
        }
      }
      else
      { DEBUGNL ("I'm confused. I hit something, but what?");
      }
    }
    else
    { DEBUGNL ("nothing hit.");
    }

    ignoredrag_ = 1;  // do not further handle this click
    return;
  } // hit test


  // dragging depends on scene_->navigationMode () ... the kind of motion

  scene_->interact (1);  // now interactive (not set when ignoredrag)
  move_mode_ = scene_->navigationMode ();

  camera_ = scene_->getCamera ();
  size_ = scene_->size ();

  if (!camera_ || !gecontext_)
    return;

  int i;

  switch (move_mode_)  // press
  {
    case NavMode::walk_around:
      if (scene_->velocityControl ())
      {
        if (button_ == Event::left)
          walkicon_ = _icon_body;
        else if (button_ == Event::middle)
          walkicon_ = _icon_lift;
        else if (button_ == Event::right)
          walkicon_ = _icon_eyes;
        else
          walkicon_ = _icon_none;
      }
    break;

/*
    case NavMode::fly_around:
      do  // move while button is held down
      { move_fly_around (fx*2.0-1, fy*2.0-1);  // +-1 at window border
        scene_->redraw ();
      } while (!e.pending ());
    break;  // fly around
*/
    case NavMode::fly_1:
      if (button_ == Event::left)  // do/stop drawings
      {
        if (flying_)  // click anywhere to turn it off
        {
          flying_ = 0;
          gecontext_->resetCursor ();
          scene_->interact (0);
          click_ = 0;  // do not handle this click any more
          if (flyspeed_ || scene_->interactRelevant ())  // reset speed when stop to fly
          { flyspeed_ = 0;
            scene_->redraw ();
          }
        }
        else  // click inside cross to turn on flying
        {
          float x0 = win->width () / 2,
                y0 = win->height () / 2;  // window center
          float x = e.pointer_x () - x0,
                y = e.pointer_y () - y0;  // coordinates shifted to center

          if (x >= -FlyDeadX_ && x <= FlyDeadX_ && y >= -FlyDeadY_ && y <= FlyDeadY_)
	  {
	    flying_ = 1;
	    gecontext_->setCursor (HgCursors::instance ()->aeroplane ());
	    click_ = 0;  // do not handle this click any more
            if (scene_->selectObj (0))  // no selection during flight
              scene_->redraw ();
	  }
        }
      }  // left mouse button
      else
      { click_ = 0;
        fly1_hold_button (e);  // speed up/down
      }
    break;  // fly1

    case NavMode::flip_obj:
      // currently only interested in draggings
      // (no change of mouse cursor etc.)
    break;

    case NavMode::fly_2:
      click_ = 0;  // all clicks reserved for navigation
      fly2_hold_button (e, fx, fy);  // set POI and move towards/against it
    break;  // fly2

    case NavMode::heads_up:
      if (hu_icon_ == _icon_fly2)
      {
        if (e.meta_is_down ())  // stop POI picking (also with double click)
        { // inactive since meta-click is used for object info (more or less debugging feature)
          hu_icon_ = _icon_none;
          poiset_ = 0;
          scene_->statusMessage (STranslate::str (STranslate::NavHintHEADSUP));
          gecontext_->resetCursor ();
          scene_->redraw ();  // show icons
        }
        else
        { click_ = 0;  // do not select anchor with this click
          fly2_hold_button (e, fx, fy);  // set new POI or fly
        }
      } // submode fly to
      else
      { hu_icon_ = _icon_none;

        if (fabs (downx_) <= halficonsize_)  // find icon hit
        { for (i = 0;  i < _icon_num;  i++)
            if (fabs (downy_ - hu_iconpos_ [i]) <= halficonsize_)
              hu_icon_ = i;
        }

        if (hu_icon_ == _icon_fly2)
        { poiset_ = 0;
          click_ = 0;  // do not select anchor with this click
          scene_->selectObj (0);  // remove any selection
          scene_->statusMessage (STranslate::str (STranslate::NavHintFLYTOINHU));
          gecontext_->setCursor (HgCursors::instance ()->crosshair ());
          scene_->redraw ();  // hide icons
        }
      } // heads up
    break;  // heads up

  } // switch move mode

} // press



// double click only possible after press Event -
// all variables set in press valid

void HG3dInputHandler::double_click (const Event&)
{
  if (ignoredrag_ || !camera_ || !gecontext_)  // click already handled
    return;
  ignoredrag_ = 1;

  if (move_mode_ == NavMode::heads_up)
  {
    if (button_ == Event::left && hu_icon_ == _icon_fly2)  // turn off fly to
    {
      hu_icon_ = _icon_none;
      poiset_ = 0;
      scene_->statusMessage (STranslate::str (STranslate::NavHintHEADSUP));
      gecontext_->resetCursor ();
      scene_->redraw ();  // show icons
      return;  // do not activate anchor
    }
  }

  if (click_ && button_ == Event::left)
    scene_->activateAnchor ();

} // double_click



void HG3dInputHandler::drag (const Event& e)
{
  click_ = 0;  // no click in release after dragging

  if (ignoredrag_ || !camera_ || !gecontext_)  // no scene read
    return;

  Window* win = gecontext_->window ();
  if (!win)
    return;

  float win_width = win->width (),
        win_height = win->height ();

  float fx = tofract_x (win, e.pointer_x ()),
        fy = tofract_y (win, e.pointer_y ());

  float delta_x = fx - lastfx_,         // horicontal and
        delta_y = fy - lastfy_;         // vertical mouse movement (fract. coord.)

  float drx = e.pointer_x () - win_width / 2,
        dry = e.pointer_y () - win_height / 2;

  float temp1, temp2;
  temp1 = (drx - downx_) / win_width;
  temp2 = (dragx_ - downx_) / win_width;
  // cerr << "x distance changed from " << temp2 << " to " << temp1 << endl;
  float d3_x = temp1*temp1*temp1 - temp2*temp2*temp2;  // (fx - fxdown)^3 - (lastfx - fxdown)^3

  temp1 = (dry - downy_) / win_height;
  temp2 = (dragy_ - downy_) / win_height;
  // cerr << "y distance changed from " << temp2 << " to " << temp1 << endl;
  float d3_y = temp1*temp1*temp1 - temp2*temp2*temp2;  // (fy - fydown)^3 - (lastfy - fydown)^3


  switch (move_mode_)  // drag
  {
    case NavMode::flip_obj:
      drag_flipobj (delta_x, delta_y);
      scene_->redraw ();
    break;

    case NavMode::walk_around:
      if (scene_->velocityControl ())
      {
        hu_hold_button (e, walkicon_, (drx - downx_) / win_width, (dry - downy_) / win_height);
        break;
      }
      // else
      // rotations around camera position
      if (button_ == Event::left)  // zooming and horicontal rotation
      { camera_->rotate_camera_right (delta_x * SpeedFactor_*WalkTurnHor_ + d3_x * SpeedFactor_*WalkTurnHor3_);
        // left to right (around position)
        camera_->zoom_in ((delta_y * SpeedFactor_*WalkZoom_ + d3_y * SpeedFactor_*WalkZoom3_) * size_, scene_);
      }
      else if (button_ == Event::middle)  // 'panning' (+ delta_[xy] translates camera, - the scene)
      { camera_->translate (delta_x, delta_y, scene_->getWinAspect (), SpeedFactor_*WalkFocal_ * size_, scene_);
      }
      else if (button_ == Event::right)  // rotation ('eyes')
      { camera_->rotate_camera_right (delta_x * SpeedFactor_*WalkTurnHor_ + d3_x * SpeedFactor_*WalkTurnHor3_);
        // left to right (around position)
        camera_->rotate_camera_up (delta_y * SpeedFactor_*WalkTurnVert_ + d3_y * SpeedFactor_*WalkTurnVert3_);
        // up/down (around position)
      }
      scene_->redraw ();
    break; // walk_around

    case NavMode::fly_1:
      fly1_hold_button (e);
    break;

    case NavMode::fly_2:
      fly2_hold_button (e, fx, fy);  // change POI or fly towards/away
    break;

    case NavMode::heads_up:
      if (scene_->velocityControl () && hu_icon_ != _icon_fly2)
      {
        hu_hold_button (e, hu_icon_, (drx - downx_) / win_width, (dry - downy_) / win_height);
        break;
      }
      // else
      switch (hu_icon_)
      {
        case _icon_eyes:  // 'eyes': turn left-right, bottom-top
          camera_->rotate_camera_right (delta_x * SpeedFactor_*WalkTurnHor_ + d3_x * SpeedFactor_*WalkTurnHor3_);
          camera_->rotate_camera_up (delta_y * SpeedFactor_*WalkTurnVert_ + d3_y * SpeedFactor_*WalkTurnVert3_);
          scene_->redraw ();
        break;
        case _icon_body:  // 'walk':  move forward-back, turn left-right
          // camera_->translate (delta_x, 0, scene_->getwinaspect (), SpeedFactor_*WalkFocal_ * size_);
          camera_->rotate_camera_right (delta_x * SpeedFactor_*WalkTurnHor_ + d3_x * SpeedFactor_*WalkTurnHor3_);
          camera_->zoom_in ((delta_y * SpeedFactor_*WalkZoom_ + d3_y * SpeedFactor_*WalkZoom3_) * size_, scene_);
          scene_->redraw ();
        break;
        case _icon_lift: // 'pan' (earlier lift)
          camera_->translate (delta_x, delta_y, scene_->getWinAspect (), SpeedFactor_*WalkFocal_ * size_, scene_);
          scene_->redraw ();
        break;
        case _icon_fly2:
//          if (poiset_)
          fly2_hold_button (e, fx, fy);
        break;
      }
    break;
  }

  lastfx_ = fx;
  lastfy_ = fy;

  dragx_ = drx;
  dragy_ = dry;
} // drag



void HG3dInputHandler::release (const Event& e)
{
//cerr << "release (" << tofract_x (e.pointer_x ()) << ", " << tofract_y (e.pointer_y ()) << ")" << endl;

  if (ignoredrag_)  // this click was already handled
    return;

  if (!flying_)
    scene_->interact (0);  // finish interaction

  if (!click_ && scene_->interactRelevant ())  // when mouse was dragged and drawing mode changed
    scene_->redraw ();                         // redraw the scene

  switch (move_mode_)  // release
  {
    case NavMode::walk_around:
      if (walkicon_ != _icon_none)
      { walkicon_ = _icon_none;
        scene_->redraw ();
      }
    break;

    case NavMode::fly_1:
      move (e);  // do same as on mouse movement
    break;

    case NavMode::heads_up:
      if (hu_icon_ != _icon_none && hu_icon_ != _icon_fly2)  // stop movement
      { hu_icon_ = _icon_none;
        scene_->redraw ();
      }
    break;
  }

  if (click_ && button_ == Event::left)
    selectAnchor (lastfx_, lastfy_);

//cerr << (click_ ? "plain click" : "finished dragging") << endl;

} // release



void HG3dInputHandler::move (const Event& e)
{
  move_mode_ = scene_->navigationMode ();

  // currently only fly_1 is interested in mouse movements (without dragging)
  if (move_mode_ != NavMode::fly_1 || !flying_)
    return;

  camera_ = scene_->getCamera ();
  size_ = scene_->size ();

  if (!camera_ || !gecontext_)  // no scene read
    return;

  do
  { if (fly1_next_frame (e))
      scene_->redraw ();
  } while (!e.pending ());

} // move



void HG3dInputHandler::keystroke (const Event& e)
{
  scene_->handleKeystroke (e);
} // keystroke


#ifdef SPACEBALL
static void sballbeep (XEvent& xe)
{
  // may provide mechanism to turn beep off
  SPW_InputBeep (xe.xany.display, "cC");
}


void HG3dInputHandler::spaceball (const Event& e)
{
// cerr << "got spaceball event" << endl;
// SPW_InputEvent* = e.spaceball_data();

  SPW_InputEvent sballData;
  XEvent& xe = e.rep ()->xevent_;

  if (!SPW_InputIsSpaceballEvent (xe.xany.display, &xe, &sballData))
    return;

  if (!scene_ || !gecontext_)  // no scene
    return;

  SceneMenus* menus = scene_->menus ();

  if (!camera_)
  {
    camera_ = scene_->getCamera ();
    if (!camera_)
      return;
  }

  move_mode_ = scene_->navigationMode ();

  float* sbdata = sballData.fData;
  int i;
  float f;

  switch (sballData.type)
  {
    case SPW_InputMotionEvent:
      // cerr << "translation: " << sbdata[0] << ", " << sbdata[1] << ", " << sbdata[2] << endl;
      // cerr << "rotation: " << sbdata[3] << ", " << sbdata[4] << ", " << sbdata[5] << endl;
      // cerr << "??: " << sbdata[6] << endl;

      if (!sbdata[0] && !sbdata[1] && !sbdata[2] && !sbdata[3] && !sbdata[4] && !sbdata[5])
      { // released spaceball
        scene_->interact (0);  // reset drawing mode
        if (scene_->interactRelevant ())
          scene_->redraw ();
        return;
      }

      scene_->interact (1);  // during motion
      size_ = scene_->size ();

      f = - menus->sballSensitivity () * 0.0005;  // scale factor

      if (menus->getSballFlag (SceneMenus::sb_saf))  // filter out largest absolute value
      {
        int maxindex = 0;
        float maxval = fabs (*sbdata);
        for (i = 1;  i < 6;  i++)
          if (fabs (sbdata [i]) > maxval)
          { maxval = fabs (sbdata [i]);
            maxindex = i;
          }

        for (i = 0;  i < 6;  i++)
          if (i == maxindex)
            sbdata [i] *= f;
          else
            sbdata [i] = 0.0;
      }
      else
      {
        for (i = 0;  i < 6;  i++)
          sbdata [i] *= f;
      }

      switch (move_mode_)  // flip or walk
      {
        case NavMode::flip_obj:
// TODO: change coordinate system of rotations (screen space not object space)
          if (menus->getSballFlag (SceneMenus::sb_translate))
          {
            camera_->translate (sbdata [0], sbdata [1],
              scene_->getWinAspect (), SpeedFactor_ * FlipFocal_ * size_, scene_);
            camera_->zoom_in (sbdata [2] * SpeedFactor_* FlipZoom_ * size_, scene_);
          }
          if (menus->getSballFlag (SceneMenus::sb_rotate))
          {
            point3D center;
            scene_->getCenter (center);  // center of rotations is center of the scene
            point3D axis;
            init3D (axis, sbdata [3], sbdata [4], sbdata [5]);
            camera_->rotate_axis (center, axis, norm3D (axis));
          }
          scene_->redraw ();
        break;

        default:
          if (menus->getSballFlag (SceneMenus::sb_translate))
          {
            camera_->translate (-sbdata[0], -sbdata[1],
              scene_->getWinAspect (), SpeedFactor_ * FlipFocal_ * size_, scene_);
            camera_->zoom_in (-sbdata[2] * SpeedFactor_ * FlipZoom_ * size_, scene_);
          }
          if (menus->getSballFlag (SceneMenus::sb_rotate))
          {
            point3D position;
            camera_->getposition (position);  // center of rotations is center of the scene
            point3D axis;
            init3D (axis, -sbdata[3], -sbdata[4], -sbdata[5]);
            camera_->rotate_axis (position, axis, norm3D (axis));
          }
          scene_->redraw ();
        break;
      }
    break;  // spaceball motion

    case SPW_InputButtonPressEvent:
      // cerr << "button number: " << sballData.buttonNumber << endl;
      switch (sballData.buttonNumber)
      {
        case 1:  // toggle translation
          if (menus->toggleSballFlag (SceneMenus::sb_translate))
            sballbeep (xe);
        break;
        case 2:  // toggle rotation
          if (menus->toggleSballFlag (SceneMenus::sb_rotate))
            sballbeep (xe);
        break;
        case 3:  // toggle single axis filter
          if (menus->toggleSballFlag (SceneMenus::sb_saf))
            sballbeep (xe);
        break;
        case 4:  // toggle navigation (flip/walk)
          menus->toggleSballMode ();
        break;
        case 5:
          menus->decreaseSballSensitivity ();
        break;
        case 6:
          menus->increaseSballSensitivity ();
        break;
        case 7:
          menus->resetSballSensitivity ();
        break;
        case 8:
          sballRezero ();
        break;
      }
    break;  // spaceball buttonpress

    case SPW_InputButtonReleaseEvent:
      if (sballData.buttonNumber == 9)  // pushbutton on ball
      {
        scene_->interact (0);
        menus->resetView ();
      }
    break;  // spaceball buttonrelease
  }
} // spaceball



void HG3dInputHandler::sballRezero ()
{
  Window* w = scene_->appwin ();
  if (w)
    SPW_InputRezero (w->rep ()->display_->rep ()->display_);
}

#else

/* without Spaceball support: none of these routines will be called */

void HG3dInputHandler::spaceball (const Event&)
{
}
void HG3dInputHandler::sballRezero ()
{
}

#endif



// drawui
// draw ui components ("head mounted display" etc. atop the ge3d window)

void HG3dInputHandler::drawui (const Allocation& a)
{
  if (scene_->linksActive () && !scene_->anchorMotion ())  // pure pick mode
    return;

  move_mode_ = scene_->navigationMode ();

  // be sure that SceneWindow::modeDrawsUI returns true
  // for movemodes that do drawings here

  if (!SceneWindow::modeDrawsUI (move_mode_))
    return;

  ge3d_setmode (ge3d_wireframe);  // also turns off z-buffer

  // set up a simple orthogonal camera for 2d drawings:
  // screen coordinate units, origin at window center
  // TODO: get pixel values instead of coords for correct lines on anti aliasing
  float vpwidth = a.right () - a.left (),
        vpheight = a.top () - a.bottom ();
  float halfw = vpwidth / 2.0,
        halfh = vpheight / 2.0;

  if (move_mode_ != NavMode::walk_around)
  {
    ge3dLineColor (&scene_->col_hudisplay);
    ge3d_setlinestyle (0x7777);  // ----
  }

  if (poiset_)  // && dot3D (poinormal_, poinormal_) > 0.0
  { // still in 3D coordinate system
    const vector3D& n = poinormal_;  // ass.: |poinormal_| = 1

    ge3d_push_matrix ();
    ge3dTranslate (&poitarget_);
    if (fabs (n.z) < 0.99999)
    { // double phi = atan2 (n.y, n.x);  // "x to y"
      ge3d_rotate_axis ('z', atan2 (n.y, n.x) * 180.0/M_PI);
    } // otherwise normal vector is parallel to z-axis
    // double theta = acos (n.z);  // "z to x-y"
    ge3d_rotate_axis ('y', acos (n.z) * 180.0/M_PI);
    ge3d_circle (0, 0, size_ * 0.05);
    ge3d_circle (0, 0, size_ * 0.01);
    float len = size_ * 0.03;
    ge3d_moveto (-len, 0, 0);  ge3d_lineto (len, 0, 0);
    ge3d_moveto (0, -len, 0);  ge3d_lineto (0, len, 0);
    ge3d_pop_matrix ();
  }

  point3D eyepos, lookat;
  init3D (eyepos, 0, 0, 1);
  init3D (lookat, 0, 0, 0);
  ge3d_ortho_cam (&eyepos, &lookat, NULL, vpwidth, vpheight, 0, 2);

  const float  // constants for fly_1
    cll = 50,  // cross line length
    sbw2 = 5,  // half speed bar width
    sbh2 = 100; // half speed bar height
  float  // speed bar origin
    sbx = halfw - 20,
    sby = -halfh + sbh2 + 20,
    temp;

  int i;
  static char speedlabel[] = "speed";
  char chs [2];  // string of 1 char
  chs [1] = '\0';


  switch (move_mode_)  // drawui
  {
    case NavMode::walk_around:
      if (scene_->velocityControl () && walkicon_ != _icon_none)
      {
        ge3d_setlinecolor (1.0, 0, 0);  // red cross
        ge3d_line (downx_ - 15, downy_, 0, downx_ + 15, downy_, 0);
        ge3d_line (downx_, downy_ - 15, 0, downx_, downy_ + 15, 0);
        // ge3d_line (downx_, downy_, 0, dragx_, dragy_, 0);  // beam
      }
    break;

    case NavMode::fly_1:
      ge3d_setlinewidth (2);
      // cross indicating "dead zone"
      // use fast procedure for polylines of ge3d !!!@@@
      ge3d_moveto (FlyDeadX_+cll, FlyDeadY_, 0);  // upper right
      ge3d_lineto (FlyDeadX_, FlyDeadY_, 0);
      ge3d_lineto (FlyDeadX_, FlyDeadY_+cll, 0);
      ge3d_moveto (-FlyDeadX_-cll, FlyDeadY_, 0);  // upper left
      ge3d_lineto (-FlyDeadX_, FlyDeadY_, 0);
      ge3d_lineto (-FlyDeadX_, FlyDeadY_+cll, 0);
      ge3d_moveto (-FlyDeadX_-cll, -FlyDeadY_, 0);  // lower left
      ge3d_lineto (-FlyDeadX_, -FlyDeadY_, 0);
      ge3d_lineto (-FlyDeadX_, -FlyDeadY_-cll, 0);
      ge3d_moveto (FlyDeadX_+cll, -FlyDeadY_, 0);  // lower right
      ge3d_lineto (FlyDeadX_, -FlyDeadY_, 0);
      ge3d_lineto (FlyDeadX_, -FlyDeadY_-cll, 0);
      // circle and cross at center (decoration)
      ge3d_setlinewidth (1);
      ge3d_circle (0, 0, FlyDeadX_*0.5);
      ge3d_moveto (-FlyDeadX_, 0, 0);
      ge3d_lineto (FlyDeadX_, 0, 0);
      ge3d_moveto (0, -FlyDeadY_, 0);
      ge3d_lineto (0, FlyDeadY_, 0);
      // speed bar (in lower right window corner)
      ge3d_moveto (sbx, sby-sbh2, 0);
      ge3d_lineto (sbx, sby+sbh2, 0);
      ge3d_moveto (sbx-sbw2, sby+sbh2, 0);
      ge3d_lineto (sbx+sbw2, sby+sbh2, 0);
      ge3d_moveto (sbx-sbw2, sby-sbh2, 0);
      ge3d_lineto (sbx+sbw2, sby-sbh2, 0);
      // speed bar label (vertical)
      for (i = 0;  (*chs = speedlabel [i]) != '\0';  i++)
        ge3d_text (sbx-sbw2-5, sby+sbh2-(i+1)*15, 0, chs);
      // speed bar thumb
      ge3d_setlinestyle (-1);  // solid
      ge3d_line (sbx-sbw2, sby, 0, sbx+sbw2, sby, 0);
      ge3d_setlinecolor (1, 0, 0);  // red
      ge3d_setlinewidth (3);
      temp = sby + flyspeed_ * sbh2;
      ge3d_line (sbx-sbw2, temp, 0, sbx+sbw2, temp, 0);
      ge3d_setlinewidth (1);
    break;

    case NavMode::heads_up:
      if (hu_icon_ == _icon_fly2)  // hide icons during fly2
        break;

      // dashed, grey line
      ge3d_setlinestyle (-1);  // solid
      ge3d_setlinecolor (0.75, 0.75, 0.75);

      // icon frames
      for (i = 0;  i < _icon_num;  i++)
        ge3d_rect (-halficonsize_, hu_iconpos_ [i] - halficonsize_,
                    halficonsize_, hu_iconpos_ [i] + halficonsize_);

      // ge3d_setlinestyle (-1);  // solid

      ge3dLineColor (&scene_->col_hudisplay);

      float y0;
      // icon eyes
      y0 = hu_iconpos_ [_icon_eyes];
      ge3d_setlinewidth (1);
      ge3d_arc (-8, y0, 6, 19.47, 160.53);
      ge3d_arc (-8, y0+4, 6, 199.47, 340.53);
      ge3d_arc (8, y0, 6, 19.47, 160.53);
      ge3d_arc (8, y0+4, 6, 199.47, 340.53);
      ge3d_setlinewidth (3);
      ge3d_circle (-8, y0+2, 1);
      ge3d_circle (8, y0+2, 1);

      // icon body
      y0 = hu_iconpos_ [_icon_body];
      // ge3d_setlinewidth (3);
/*    // right to left
      ge3d_moveto (-8, y0+3, 0);  ge3d_lineto (-4, y0+3, 0);  ge3d_lineto (1, y0+7, 0);
        ge3d_lineto (1, y0-1, 0);  ge3d_lineto (-3, y0-6, 0);  ge3d_lineto (-3, y0-13, 0);
      ge3d_moveto (8, y0-1, 0);  ge3d_lineto (8, y0+3, 0);  ge3d_lineto (3, y0+7, 0);
        ge3d_lineto (3, y0-8, 0);  ge3d_lineto (7, y0-13, 0);
      ge3d_circle (2, y0+12, 1);
*/
      // left to right
      ge3d_moveto (8, y0+3, 0);  ge3d_lineto (4, y0+3, 0);  ge3d_lineto (-1, y0+7, 0);
        ge3d_lineto (-1, y0-1, 0);  ge3d_lineto (3, y0-6, 0);  ge3d_lineto (3, y0-13, 0);
      ge3d_moveto (-8, y0-1, 0);  ge3d_lineto (-8, y0+3, 0);  ge3d_lineto (-3, y0+7, 0);
        ge3d_lineto (-3, y0-8, 0);  ge3d_lineto (-7, y0-13, 0);
      ge3d_circle (-2, y0+12, 1);

      y0 = hu_iconpos_ [_icon_lift];
      // icon 'pan' (earlier icon lift)
      ge3d_setlinewidth (3);
      ge3d_moveto (-12, y0, 0);  ge3d_lineto (12, y0, 0);  // hor
      ge3d_moveto (0, y0-12, 0);  ge3d_lineto (0, y0+12, 0);  // vert
      ge3d_moveto (-8, y0+4, 0);  ge3d_lineto (-12, y0, 0);  ge3d_lineto (-8, y0-4, 0);  // l
      ge3d_moveto (8, y0+4, 0);  ge3d_lineto (12, y0, 0);  ge3d_lineto (8, y0-4, 0);  // r
      ge3d_moveto (-4, y0-8, 0);  ge3d_lineto (0, y0-12, 0);  ge3d_lineto (4, y0-8, 0);  // b
      ge3d_moveto (-4, y0+8, 0);  ge3d_lineto (0, y0+12, 0);  ge3d_lineto (4, y0+8, 0);  // t

//    ge3d should be extended by a routine for drawing 2D polylines in integer coordinates!

/*
      // icon lift (arrows in box)
      ge3d_setlinewidth (1);
      ge3d_rect (-9, y0-10, 8, y0+10);
      ge3d_setlinewidth (2);
      ge3d_moveto (-12, y0-12, 0);  ge3d_lineto (-12, y0+12, 0);
      ge3d_moveto (13, y0-12, 0);  ge3d_lineto (13, y0+12, 0);
      ge3d_moveto (-3, y0-8, 0);  ge3d_lineto (-3, y0+8, 0);
      ge3d_moveto (3, y0-8, 0);  ge3d_lineto (3, y0+8, 0);
      ge3d_moveto (-6, y0-4, 0);  ge3d_lineto (-3, y0-8, 0);  ge3d_lineto (0, y0-4, 0);
      ge3d_moveto (0, y0+4, 0);  ge3d_lineto (3, y0+8, 0);  ge3d_lineto (6, y0+4, 0);

      // old icon lift (person in box)
      ge3d_rect (-8, y0-12, 8, y0+8);
      ge3d_moveto (-14, y0-12, 0);  ge3d_lineto (-12, y0-12, 0);
        ge3d_lineto (-12, y0+8, 0);  ge3d_lineto (-14, y0+8, 0);
      ge3d_moveto (14, y0-12, 0);  ge3d_lineto (12, y0-12, 0);
        ge3d_lineto (12, y0+8, 0);  ge3d_lineto (14, y0+8, 0);
      ge3d_moveto (0, y0+8, 0);  ge3d_lineto (0, y0+14, 0);
        ge3d_lineto (-4, y0+11, 0);  ge3d_lineto (4, y0+11, 0);  ge3d_lineto (0, y0+14, 0);
      ge3d_setlinewidth (2);
      ge3d_circle (0, y0+2, 2);
      ge3d_moveto (0, y0, 0);  ge3d_lineto (0, y0-8, 0);
      ge3d_moveto (-4, y0-3, 0);  ge3d_lineto (4, y0-3, 0);
      ge3d_moveto (-4, y0-12, 0);  ge3d_lineto (0, y0-8, 0);  ge3d_lineto (4, y0-12, 0);
*/
      // icon fly2
      y0 = hu_iconpos_ [_icon_fly2];
      ge3d_setlinewidth (2);
      ge3d_line (-2, y0, 0, -12, y0, 0);
      ge3d_line (2, y0, 0, 12, y0, 0);
      ge3d_line (0, y0-2, 0, 0, y0-12, 0);
      ge3d_line (0, y0+2, 0, 0, y0+12, 0);
      ge3d_setlinewidth (1);
      ge3d_circle (0, y0, 8);

      // dragged line
      if (hu_icon_ != _icon_none)  // && hu_icon_ != _icon_fly2)
      { /* if (hu_icon_ == _icon_lift)
             dragx_ = downx_;  // unused
        */
        ge3d_setlinecolor (1.0, 0, 0);  // red
        ge3d_line (downx_, downy_, 0, dragx_, dragy_, 0);
      }
    break;
  }

  ge3d_setlinestyle (-1);  // solid

} // drawui



// selectObject
// select an object (called on shift+click)
// if the object/group happens to be an anchor,
// the anchor is selected too

void HG3dInputHandler::selectObject (float fx, float fy)
{
  const StringArray* groupshit = 0;
  GeometricObject* hitobj = 0;
  QvNode* node = 0;
  QvWWWAnchor* anchor = 0;
  scene_->pickObject (fx, fy, hitobj, node, anchor, groupshit);
  // TODO: selection mechanism for VRML
  int redraw;

  // redraw, if selection changes
  // when no object was hit current selection is removed
  if (hitobj)
  {
    if (scene_->selectionMode () == SceneWindow::SelectObject)
      redraw = scene_->selectObj (hitobj);
    else  // SceneWindow::SelectGroup
      redraw = groupshit && scene_->selectObj (hitobj, groupshit->previousItem ());
  }
/*
  else if (node)
  {  // TODO
  }
*/
  else // no object hit
    redraw = scene_->selectObj (0);  // remove selection

  if (redraw)  // redraw when necessary
    scene_->redraw ();

} // selectObject



// selectAnchor
// select an anchor (called on click left)

void HG3dInputHandler::selectAnchor (float fx, float fy)
{
  const StringArray* groupshit;
  GeometricObject* hitobj = 0;
  QvNode* node = 0;
  QvWWWAnchor* anchor = 0;
  scene_->pickObject (fx, fy, hitobj, node, anchor, groupshit);
  // TODO: selection mechanism for VRML
  int redraw;

  // redraw, if selection changes
  // when no object was hit current selection is removed
  if (hitobj)
  {
    const SourceAnchor* anchor = hitobj->getNextAnchor (groupshit);
    if (anchor)
      redraw = scene_->selectObj (hitobj, anchor->groupName ());
    else
      redraw = scene_->selectObj (0);  // no anchor hit - remove selection
  }
  else if (anchor)
  {
    redraw = scene_->selectNode (anchor, anchor);  // select anchor
  }
  else  // no object or anchor hit
    redraw = scene_->selectObj (0) || scene_->selectNode (0);  // remove selection

  if (redraw)  // redraw when necessary
    scene_->redraw ();

} // selectAnchor



// drag_flipobj
// handles a mouse movement by (dx, dy) in model "flip object"
// assumes scene_ and camera_ non null

void HG3dInputHandler::drag_flipobj (float dx, float dy)
{
  int mousebutton = button_;
  int flipmode = scene_->flipObjectMode ();

  // mouse button significant only if no controlbutton (TRZ) is pressed
  if (flipmode == FlipMode::flip_translate ||
      flipmode == FlipMode::flip_rotate ||
      flipmode == FlipMode::flip_zoom)
    mousebutton = Event::none;  // mouse button irrelevant

  if (flipmode == FlipMode::flip_translate || mousebutton == Event::left)
  {
    camera_->translate (-dx, -dy, scene_->getWinAspect (), SpeedFactor_*FlipFocal_ * size_, scene_);
  }
  else if (flipmode == FlipMode::flip_rotate || mousebutton == Event::middle)
  {
    point3D center;
    scene_->getCenter (center);  // center of rotations is center of the scene
    camera_->rotate_camera (-dx * SpeedFactor_*FlipTurnHor_, -dy * SpeedFactor_*FlipTurnVert_, center);
  }
  else if (flipmode == FlipMode::flip_zoom || mousebutton == Event::right)
  {
    camera_->zoom_in (-dy * SpeedFactor_*FlipZoom_ * size_, scene_);
  }

  // redraw done by caller
} // drag_flipobj


/*
// move_fly_around 
// does one step of movement/rotation in mode "fly around"
// dx, dy control speed (+-1 at window border; larger if mouse dragged out of window)
// assumes scene_ and camera_ non null

void HG3dInputHandler::move_fly_around (float dx, float dy)
{
  // nonlinear mapping of speed: slower near center by x^3 (sign must be preserved)
  dx *= dx * dx;
  dy *= dy * dy;

  if (button_ == Event::left)  // zooming and horicontal rotation
  { 
    camera_->rotate_camera_right (dx * FA_TURNHOR);  // left to right (around position)
    camera_->zoom_in (dy * FA_ZOOM * size_);
  }
  else if (button_ == Event::middle)  // panning (+ delta_[xy] translates camera, - the scene)
  { camera_->translate (dx * FA_PANHOR, dy * FA_PANVERT, scene_->getwinaspect (), SpeedFactor_*WalkFocal_ * size_);
  }
  else if (button_ == Event::right)  // vertical rotation
  { camera_->rotate_camera_up (dy * FA_TURNVERT);  // up/down (around position)
    // dx (ignored) could be used for tilting the camera (if wished)
  }

  // redraw done by caller

} // move_fly_around
*/


// fly1_hold_button
// increases/decreases speed as long middle/right button_ is held
// speed is reset when flying is turned off
// always redraw (show new speed)

void HG3dInputHandler::fly1_hold_button (const Event& e)
{
  switch (button_)
  { 
    case Event::middle:  // increase speed
      do
      { flyspeed_ += SpeedFactor_*FlySpeedInc_;
        if (flyspeed_ > 1.0)
          flyspeed_ = 1.0;
        fly1_next_frame (e);
        scene_->redraw ();
      } while (!e.pending ());
    break;
    case Event::right:  // decrease speed
      do
      { flyspeed_ -= SpeedFactor_*FlySpeedInc_;
        if (flyspeed_ < -1.0)
          flyspeed_ = -1.0;
        fly1_next_frame (e);
        scene_->redraw ();
      } while (!e.pending ());
    break;
/*  // middle+right: stop (not supported by class InputHandler)
      if (flyspeed_)
      { flyspeed_ = 0;
        scene_->redraw ();
      }
*/
  }
} // fly1_hold_button



// fly1_next_frame
// does one step of movement/rotation in mode "fly I"
// event.pointer_[xy] and flyspeed_ determine the kind of movement
// returns 1 iff a redraw is necessary
// assumes gecontext_ and camera_ non null

int HG3dInputHandler::fly1_next_frame (const Event& e)
{
  if (!flying_)
    return 0;

  Window* win = e.rep ()->window_;
  if (!win)
    return 0;

  float x0 = win->width () / 2,
        y0 = win->height () / 2;  // window center
  float x = e.pointer_x () - x0,
        y = e.pointer_y () - y0;  // coordinates shifted to center

  int redraw = 0;

  if (x < -FlyDeadX_ || x > FlyDeadX_)  // horicontal turn
  { if (x > 0)  x -= FlyDeadX_;
    else        x += FlyDeadX_;
    x /= (x0 - FlyDeadX_);  // 0 < x < 1
    camera_->rotate_camera_right (x * SpeedFactor_*FlyTurnHor_);  // left to right (around position)
    redraw = 1;
  }

  if (y < -FlyDeadY_ || y > FlyDeadY_)  // vertical turn
  { if (y > 0)  y -= FlyDeadY_;
    else        y += FlyDeadY_;
    y /= (y0 - FlyDeadY_);  // 0 < y < 1
    camera_->rotate_camera_up (y * SpeedFactor_*FlyTurnVert_);  // up/down (around position)
    redraw = 1;
  }

  if (flyspeed_)  // zoom (nonlinear)
  {
    camera_->zoom_in (flyspeed_*flyspeed_*flyspeed_ * size_, scene_);
    redraw = 1;
  }

  return redraw;

} // fly1_next_frame


// fly2_hold_button
// moves towards/away from point of interest while middle/right button_ is held
// also turn camera towards -poinormal_
// redraws window if necessary
// assumes gecontext_ and camera_ non null

void HG3dInputHandler::fly2_hold_button (const Event& e, float fx, float fy)
{
  float ktran = SpeedFactor_*FlyToTran_;
  if (ktran > 0.99)
    ktran = 0.99;

  float krot = SpeedFactor_*FlyToRot_;
  if (krot > 0.99)
    krot = 0.99;

  switch (button_)
  {
    case Event::left:  // set new POI
      poiset_ = (scene_->pickObject (fx, fy, poitarget_, poinormal_) != 0);
// if (poiset_)
// cerr << "hit point " << poitarget_ << ", normal " << poinormal_ << endl;
// else
// cerr << "nothing of scene hit." << endl;
      scene_->redraw ();
    return;

    case Event::middle:  // towards POI
      // ktran, krot in range 0 to 1
    break;

    case Event::right:  // away from POI
      ktran = -ktran;
      krot = -krot;
    break;

    default:
    return;
  }

  if (!poiset_)
    return;

  float hither2 = camera_->gethither ();
  hither2 *= hither2;  // square distance of near clipping plane

  // default: translation only
  // shift  : both translation and rotation (adjusting)
  // control: rotation (adjusting) only

  int do_trans = 1;
  int do_rotat = 0;
  if (e.shift_is_down ())
    do_rotat = 1;
  if (e.control_is_down ())
  { do_trans = 0;
    do_rotat = 1;
  }

  do
  {
    if (do_rotat)
    {
      // rotate position around hit (towards -poinormal_)
      point3D position;
      vector3D pos_hit, direction;
      point3D lookat;
      vector3D look_pos;

      camera_->getposition (position);
      sub3D (position, poitarget_, pos_hit);
      camera_->getlookat (lookat);
      sub3D (lookat, position, look_pos);  // line of sight

      // ass.: |poinormal_| == 1
      float norm_p_h = norm3D (pos_hit);
      // direction = |pos_hit| * poinormal_ - pos_hit
      lcm3D (norm_p_h, poinormal_, -1, pos_hit, direction);

      pol3D (pos_hit, krot, direction, pos_hit);  // pos_hit += k * direction
      float temp = norm3D (pos_hit);  // new norm
      if (temp > 0.0)  // preserve norm of pos_hit
      { norm_p_h /= temp;
        scl3D (pos_hit, norm_p_h);
      }
      norm_p_h = temp;

      add3D (pos_hit, poitarget_, position);
      camera_->setposition (position);  // new position
    
      // rotate lookat around position (towards -poinormal_)
      // direction = -poinormal_ - look_pos / |look_pos|
      temp = norm3D (look_pos);  // norm of look_pos
      if (temp > 0.0)
      { temp = 1/temp;
        pol3D (poinormal_, temp, look_pos, direction);
        scl3D (direction, -1.0);
        pol3D (look_pos, krot, direction, look_pos);  // look_pos += k * direction
      }

      temp = norm3D (look_pos);  // new norm
      if (temp)  // set distance between position and lookat to 1.0
      { temp = 1/temp;
        scl3D (look_pos, temp);
      }

      // camera_->setlookat (poitarget_);  // just a test
      add3D (look_pos, position, lookat);
      camera_->setlookat (lookat);  // new lookat
    } // rotation

    if (do_trans)
    {
      // translation (towards poitarget_)
      // collision detection not done here, because would yield to strange results
      // and motion during fly to is easy to reverse anyway
      vector3D hit_pos;
      camera_->getposition (hit_pos);
      sub3D (poitarget_, hit_pos, hit_pos);  // hit_pos = POI hit - eye position
      if (ktran < 0 || (1-ktran)*(1-ktran)*dot3D (hit_pos, hit_pos) > hither2)
      { // move away or still away from near clipping plane
        scl3D (hit_pos, ktran);  // k * (POI - eye)
        camera_->translate (hit_pos, 0, 0, 0);  // no collision detection in "fly to"
        // camera_->translate (hit_pos, scene_, 1, 1);  // coll.det. with sliding, resp. near clip
      }
    } // translation

    scene_->redraw ();

  } while (!e.pending ());

} // fly2_hold_button



// hu_hold_button
// called when velocityControl option is true for walk or heads up
// icon tells which function to do (_icon...; necessary for walk)
// dx, dy are the distances from the current point to press position
// (whole window width/height is 1.0)

void HG3dInputHandler::hu_hold_button (const Event& e, int icon, float dx, float dy)
{
  float dx3 = dx*dx*dx;
  float dy3 = dy*dy*dy;

  do {

    switch (icon)
    {
      case _icon_eyes:  // 'eyes': turn left-right, bottom-top
        camera_->rotate_camera_right (dx * SpeedFactor_*VelocityTurnHor_ + dx3 * SpeedFactor_*VelocityTurnHor3_);
        camera_->rotate_camera_up (dy * SpeedFactor_*VelocityTurnVert_ + dy3 * SpeedFactor_*VelocityTurnVert3_);
      break;
      case _icon_body:  // 'walk':  move forward-back, turn left-right
        camera_->rotate_camera_right (dx * SpeedFactor_*VelocityTurnHor_ + dx3 * SpeedFactor_*VelocityTurnHor3_);
        camera_->zoom_in ((dy * SpeedFactor_*VelocityZoom_ + dy3 * SpeedFactor_*VelocityZoom3_) * size_, scene_);
      break;
      case _icon_lift: // 'pan' (earlier lift)
        camera_->translate (
          dx * SpeedFactor_*VelocityPan_ + dx3 * SpeedFactor_*VelocityPan3_,
          dy * SpeedFactor_*VelocityPan_ + dy3 * SpeedFactor_*VelocityPan3_,
          scene_->getWinAspect (), SpeedFactor_*WalkFocal_ * size_,
          scene_
        );
      break;
      // not called for 'fly to'-icon
    }

    scene_->redraw ();

    // XCopyArea command for swapbuffers in mesa library
    // (xmesa.c) caused an XNoExposeEvent pending here.
    // fixed in MesaLib: graphics_exposures flag (GCGraphicsExposures)
    // set to False for cleargc (see also copygc_ in xcanvas.c)
    // (see GraphicsExpose/NoExpose in X event reference)

  } while (!e.pending ());

} // hu_hold_button



void HG3dInputHandler::loadXdefaults (Style* style)
{
  double val_double;

  // speed factor affects all settings (internal ones and those
  // explicitly set by X-attribute); the multiplication is done
  // in code to allow setting of speedfactor in a dialog
  if ((style->find_attribute ("speedFactor", val_double) ||
       style->find_attribute ("SpeedFactor", val_double)) && val_double > 0)
    SpeedFactor_ = val_double;

  if (style->find_attribute ("FlipFocal", val_double) && val_double > 0)
    FlipFocal_ = val_double;
  if (style->find_attribute ("WalkFocal", val_double) && val_double > 0)
    WalkFocal_ = val_double;
  if (style->find_attribute ("FlipZoom", val_double))
    FlipZoom_ = val_double;
  if (style->find_attribute ("FlipTurnHor", val_double))
    FlipTurnHor_ = val_double;
  if (style->find_attribute ("FlipTurnVert", val_double))
    FlipTurnVert_ = val_double;

  if (style->find_attribute ("WalkZoom", val_double))
    WalkZoom_ = val_double;
  if (style->find_attribute ("WalkTurnHor", val_double))
    WalkTurnHor_ = val_double;
  if (style->find_attribute ("WalkTurnVert", val_double))
    WalkTurnVert_ = val_double;
  if (style->find_attribute ("WalkZoom3", val_double))
    WalkZoom3_ = val_double;
  if (style->find_attribute ("WalkTurnHor3", val_double))
    WalkTurnHor3_ = val_double;
  if (style->find_attribute ("WalkTurnVert3", val_double))
    WalkTurnVert3_ = val_double;

  if (style->find_attribute ("VelocityZoom", val_double))
    VelocityZoom_ = val_double;
  if (style->find_attribute ("VelocityPan", val_double))
    VelocityPan_ = val_double;
  if (style->find_attribute ("VelocityTurnHor", val_double))
    VelocityTurnHor_ = val_double;
  if (style->find_attribute ("VelocityTurnVert", val_double))
    VelocityTurnVert_ = val_double;
  if (style->find_attribute ("VelocityZoom3", val_double))
    VelocityZoom3_ = val_double;
  if (style->find_attribute ("VelocityPan3", val_double))
    VelocityPan3_ = val_double;
  if (style->find_attribute ("VelocityTurnHor3", val_double))
    VelocityTurnHor3_ = val_double;
  if (style->find_attribute ("VelocityTurnVert3", val_double))
    VelocityTurnVert3_ = val_double;

  if (style->find_attribute ("FlyDeadX", val_double))
    FlyDeadX_ = val_double;
  if (style->find_attribute ("FlyDeadY", val_double))
    FlyDeadY_ = val_double;
  if (style->find_attribute ("FlySpeedInc", val_double))
    FlySpeedInc_ = val_double;
  if (style->find_attribute ("FlyTurnHor", val_double))
    FlyTurnHor_ = val_double;
  if (style->find_attribute ("FlyTurnVert", val_double))
    FlyTurnVert_ = val_double;
  if (style->find_attribute ("FlyToTran", val_double) && val_double > 0.0 && val_double < 1.0)
    FlyToTran_ = val_double;
  if (style->find_attribute ("FlyToRot", val_double) && val_double > 0.0 && val_double < 1.0)
    FlyToRot_ = val_double;

} // loadXdefautls
