/*
 * Scene3DView.cpp
 *
 * Copyright (C) 1999 Stephen F. White
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see the file "COPYING" for details); if 
 * not, write to the Free Software Foundation, Inc., 675 Mass Ave, 
 * Cambridge, MA 02139, USA.
 */

#include "stdafx.h"

#include "swt.h"

#include "Scene3DView.h"
#include "Scene.h"
#include "Path.h"
#include "Node.h"
#include "RenderState.h"
#include "Matrix.h"
#include "SFRotation.h"
#include "SFVec3f.h"
#include "EulerAngles.h"
#include "InputDeviceTime.h"
#include "Util.h"
#include "NodeViewpoint.h"
#include "NodeTransform.h"
#include "NodeNurbsSurface.h"
#include "NodeNurbsCurve.h"
#include "NodeNurbsGroup.h"

static int 
inputDeviceTimerCallback(void *data)
{
    return((Scene3DView *) data)->readInputDevice();
}

static int 
walkTimerCallback(void *data)
{
    return((Scene3DView *) data)->walk();
}

Scene3DView::Scene3DView(Scene *scene, SWND parent)
  : SceneView(scene, parent)
{
    _dc = NULL;
    _glc = NULL;
    _trackMouse = false;
    _cursorArrow = swLoadCursor(SW_CURSOR_ARROW);
    _cursorZoom = swLoadCursor(SW_CURSOR_DBL_ARROW_VERT);
    _cursorRotate = swLoadCursor(SW_CURSOR_CIRCLE);
    _cursorRoll = swLoadCursor(SW_CURSOR_ROLL);
    _cursorMove = swLoadCursor(SW_CURSOR_CROSS);
    _cursorNone = swLoadCursor(SW_CURSOR_NONE);
    _button1down = false;
    _button2down = false;
    _fieldEdit = false;
    _inputDeviceTimer = NULL;
    _walkTimer = NULL;
    if (TheApp->getNumberInputDevices() != 0)
      _inputDeviceTimer = swSetTimer(_wnd, INPUTDEVICE_TIME, 
                                     inputDeviceTimerCallback, this);
    _mouseX = INT_MIN;
    _mouseY = INT_MIN;
    _hitHandle = false;
    _offset.x = 0;
    _offset.y = 0;
    _offset.z = 0;
    _walk.x = 0;
    _walk.y = 0;
    _walk.z = 0;
    _walking = false;
    _width = 0;
    _height = 0;
}

Scene3DView::~Scene3DView()
{
    if (TheApp->getNumberInputDevices() != 0)
        if (_inputDeviceTimer != NULL)
            swKillTimer(_inputDeviceTimer);
    if (_glc != NULL)
        swDestroyGLContext(_glc);
    _glc = NULL;
    swDestroyDC(_dc);
    // early/double destroy of window is protected by destroySelf()
    swDestroyWindow(_wnd);

    swDestroyCursor(_cursorArrow);
    swDestroyCursor(_cursorZoom);
    swDestroyCursor(_cursorRotate);
    swDestroyCursor(_cursorRoll);
    swDestroyCursor(_cursorMove);
    swDestroyCursor(_cursorNone);
}

void Scene3DView::OnUpdate(SceneView *sender, int type, Hint *hint)
{
    // inputdevice reading routine need information if about field editing
    switch (type) {
      CASE_UPDATE(UPDATE_START_FIELD_EDIT)
        _fieldEdit = true;
        break;
      CASE_UPDATE(UPDATE_STOP_FIELD_EDIT) 
        if (_fieldEdit) {
            _fieldEdit = false;
            // restart inputdevice reading timer 
            if (TheApp->getNumberInputDevices() != 0)
                _inputDeviceTimer = swSetTimer(_wnd, INPUTDEVICE_TIME,
                                               inputDeviceTimerCallback, this);
        }
        swInvalidateWindow(_wnd);
        break;
      CASE_UPDATE(UPDATE_FIELD)
        {
        NodeUpdate *nodeUpdate = (NodeUpdate *) hint;
        if (nodeUpdate->node != NULL)
            nodeUpdate->node->update();
        }
        swInvalidateWindow(_wnd);
        break;
      CASE_UPDATE(UPDATE_REDRAW)
        swInvalidateWindow(_wnd);
        break;
      CASE_UPDATE(UPDATE_SELECTION)
        swInvalidateWindow(_wnd);
        break;
    }
}

void Scene3DView::OnDraw(int /* x */, int /* y */,
                         int /* width */, int /* height */) 
{
    int width, height;
    if (!_dc) _dc = swCreateDC(_wnd);
    if (!_glc) _glc = swCreateGLContext(_dc);
    swMakeCurrent(_dc, _glc);
    swGetSize(_wnd, &width, &height);
    glViewport(0, 0, width, height);
    TheApp->setEyeMode(EM_NONE);
    if (TheApp->isAnaglyphStereo()) {
        glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
        glClearAccum(0.0,0.0,0.0,0.0); 
        glClearColor(0.0,0.0,0.0,0.0);
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    }
    if ((TheApp->canQuadBufferStereo()) || (TheApp->useStereo())) {
       if (TheApp->useStereo())
           TheApp->setEyeMode(EM_LEFT);
       if (TheApp->canQuadBufferStereo())
            glDrawBuffer(GL_BACK_LEFT);
       if (TheApp->isAnaglyphStereo()) {
           glClear(GL_ACCUM_BUFFER_BIT);
           if (TheApp->getStereoType() == RED_GREEN_ANAGLYPH_STEREO)
               glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
           else if (TheApp->getStereoType() == RED_BLUE_ANAGLYPH_STEREO)
               glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE);
           else 
               glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
       }
       _scene->drawScene(false,0,0);
       if (TheApp->useStereo())
           if ((_mouseX != INT_MIN) && (_mouseY != INT_MIN))
               _scene->draw3dCursor(_mouseX, _mouseY);
       if (TheApp->isAnaglyphStereo())
           glAccum(GL_ACCUM, 1.0f);

       if (TheApp->useStereo())
           TheApp->setEyeMode(EM_RIGHT);

       if (TheApp->canQuadBufferStereo())
           glDrawBuffer(GL_BACK_RIGHT);
       if (TheApp->isAnaglyphStereo()) {
           glClear(GL_ACCUM_BUFFER_BIT);
           if (TheApp->getStereoType() == GREEN_RED_ANAGLYPH_STEREO)
               glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
           else if (TheApp->getStereoType() == BLUE_RED_ANAGLYPH_STEREO)
               glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE);
           else
               glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);        
       } 
       _scene->drawScene(false,0,0);
       if (TheApp->useStereo())
           if ((_mouseX != INT_MIN) && (_mouseY != INT_MIN))
               _scene->draw3dCursor(_mouseX, _mouseY);
       if (TheApp->isAnaglyphStereo())
           glAccum(GL_ACCUM, 1.0f);
       }
    else
       {
       _scene->drawScene(false,0,0);
       }
    if (TheApp->isAnaglyphStereo())
        glAccum(GL_RETURN,1.0);
    swSwapBuffers(_dc, _glc);
}

void Scene3DView::OnSize(int width, int height) 
{
    _width = width;
    _height = height;
    swInvalidateWindow(_wnd);
}

void Scene3DView::OnKeyDown(int key, int x, int y, int modifiers) 
{
    switch (key) {
      case SW_UP:
        _scene->moveCamera(0.0f, 0.0f, -1.0f);
        swInvalidateWindow(_wnd);
        break;
      case SW_DOWN:
        _scene->moveCamera(0.0f, 0.0f, 1.0f);
        swInvalidateWindow(_wnd);
        break;
      case SW_KEY_LEFT:
        _scene->turnCamera(0.0f, 1.0f, 0.0f, 0.10f);
        swInvalidateWindow(_wnd);
        break;
      case SW_KEY_RIGHT:
        _scene->turnCamera(0.0f, 1.0f, 0.0f, -0.10f);
        swInvalidateWindow(_wnd);
        break;
      case SW_MOUSE4:
        _scene->moveCamera(0.0f, 0.0f, 0.1f);
        swInvalidateWindow(_wnd);
        break;
      case SW_MOUSE5:
        _scene->moveCamera(0.0f, 0.0f, -0.1f);
        swInvalidateWindow(_wnd);
        break;       
      default:
        SceneView::OnKeyDown(key, x, y, modifiers);
        break;
    }
    TheApp->interact();
}

void Scene3DView::OnMButtonDown(int x, int y, int modifiers) 
{
    _backedUp = false;
    int width, height;
    swGetSize(_wnd, &width, &height);

    swMakeCurrent(_dc, _glc);
    swSetFocus(_wnd);
    if (_scene->getMouseNavigationMode())
        if (TheApp->GetMouseMode() == MOUSE_WALK)
            swSetCursor(_wnd, _cursorRotate);
        else
            swSetCursor(_wnd, _cursorZoom);
    else if (modifiers & SW_ALT) 
        if (_button1down)
            swSetCursor(_wnd, _cursorZoom);
        else
            swSetCursor(_wnd, _cursorMove);
    else if (TheApp->GetMouseMode() == MOUSE_WALK) {
        startWalking(false);
        swSetCursor(_wnd, _cursorMove);
    } else {
        Path *path = _scene->pick(x, height - y);
        if (path) {
            if (path->getPathLen() == 0)
                _scene->removeSelectedHandles();
            Node *selection =  _scene->getSelection()->getNode();
            int handle = _scene->getLastSelectedHandle();
            if (!selection->validHandle(handle))
                _scene->removeSelectedHandle(handle);
            swInvalidateWindow(_wnd);
        }
    }

    _trackMouse = true;
    swSetCapture(_wnd);
    _trackX = x;
    _trackY = y;

    _button2down = true;
    swInvalidateWindow(_wnd);
    TheApp->interact();
}

void Scene3DView::OnRButtonDown(int x, int y, int modifiers) 
{
    // to be filled later...
}

void Scene3DView::OnLButtonDown(int x, int y, int modifiers) 
{
    Path *path;
    _backedUp = false;
    int width, height;

    swGetSize(_wnd, &width, &height);

    swMakeCurrent(_dc, _glc);
    swSetFocus(_wnd);

    _hitHandle = false;
    bool cursorChanged = false;

    if ((modifiers & SW_CONTROL)) {
        cursorChanged = true;
        if (modifiers & SW_SHIFT) 
            swSetCursor(_wnd, _cursorMove);
        else if (TheApp->GetMouseMode() == MOUSE_ROLL)
            swSetCursor(_wnd, _cursorRoll);
        else {
            _scene->setTurnPoint();
            if (TheApp->GetMouseMode() == MOUSE_WALK)
                startWalking(true);
            swSetCursor(_wnd, _cursorRotate);
        }
    } else if (modifiers & SW_ALT) {
        cursorChanged = true;
        if (_button2down)
           swSetCursor(_wnd, _cursorZoom);
        else if (TheApp->GetMouseMode() == MOUSE_ROLL)
            swSetCursor(_wnd, _cursorRoll);
        else {
            _scene->setTurnPoint();
           swSetCursor(_wnd, _cursorRotate);
        }
    } else if (modifiers & SW_SHIFT) {
        cursorChanged = true;
        swSetCursor(_wnd, _cursorZoom);
    } else {
        path = _scene->pick(x, height - y);
        int newHandle = -1;
        if (path) {
            if (path->getPathLen() == 0)
                _scene->removeSelectedHandles();                
            else  {
                newHandle = _scene->getLastSelectedHandle();
                if (_scene->isNewSelectedHandle())
                    _scene->removeSelectedHandles();
            }
            Node *node = _scene->getSelection()->getNode();
            if (!_scene->getMouseNavigationMode())
                if ((!(node->getNodeClass() & VIEWPOINT_NODE)) || 
                    (path->getPathLen() != 0)) {
                    _scene->setSelection(path);
                    _scene->UpdateViews(this, UPDATE_SELECTION);
                    node = _scene->getSelection()->getNode();
                }

            if (newHandle != -1) 
                _scene->setSelectedHandle(newHandle);
            swInvalidateWindow(_wnd);

            if (newHandle == -1)
                _hitHandle = false;
            else {
                int handle = newHandle;
                swInvalidateWindow(_wnd);
    
                _hitHandle = true;
    
                int constrain = _scene->getConstrain();
                int field;
                Vec3f v = node->getHandle(handle, &constrain, &field);
    
                _backedUp = false;
    
                // now calculate an offset in object space from the mouse 
                // to the actual handle position
    
                glPushMatrix();
                Vec3f o;
                float px, py, pz;
                _scene->transform(_scene->getSelection());
                node->transformForHandle(handle);
                _scene->projectPoint(v.x, v.y, v.z, &px, &py, &pz);
                _scene->unProjectPoint((float) x, (float) height - (float) y, 
                                       pz, &o.x, &o.y, &o.z);
                glPopMatrix();
    
                _offset = v - o;
            }
        } else
            _scene->removeSelectedHandles(); 
    }
    if (!_hitHandle) {
        if (!_button2down) {
            _scene->setTurnPoint();
            if (TheApp->GetMouseMode() == MOUSE_WALK)
                startWalking(true);
        }
        if (!cursorChanged)
            if (_button2down)
                swSetCursor(_wnd, _cursorMove);
            else
                swSetCursor(_wnd, _cursorRotate);
    }
    _trackMouse = true;
    swSetCapture(_wnd);
    _trackX = x;
    _trackY = y;
    _button1down = true;
    swInvalidateWindow(_wnd);
    TheApp->interact();
}

void Scene3DView::OnMouseEnter()
{
    _scene->setHasFocus();
    _scene->use3dCursor(true);
    if (TheApp->useStereo())
        swSetCursor(_wnd, _cursorNone);
    swInvalidateWindow(_wnd);
} 

void Scene3DView::OnMouseLeave()
{
    _scene->deleteHasFocus();
    _scene->use3dCursor(false);
// M$Windows generates MouseLeave events constantly (every frame ?)
#ifndef _WIN32
    if (TheApp->useStereo())
        swSetCursor(_wnd, _cursorArrow);
#endif
    swInvalidateWindow(_wnd);
} 

void Scene3DView::OnMouseMove(int x, int y, int modifiers) 
{
#ifdef _WIN32
    if (!(_trackMouse) || (_hitHandle))
        if (TheApp->useStereo())
            swSetCursor(_wnd, _cursorNone);
#endif

    int width, height;

    swMakeCurrent(_dc, _glc);
    swGetSize(_wnd, &width, &height);

    if (TheApp->useStereo() && _scene->use3dCursor()) {
       _mouseX = x;
       _mouseY = y;
       swInvalidateWindow(_wnd);
    }

    if (_trackMouse) {
        TheApp->interact();
        float dx = (float) x - _trackX;
        float dy = (float) y - _trackY;
        if (modifiers & SW_CONTROL) {
            if (modifiers & SW_SHIFT) {
                swSetCursor(_wnd, _cursorMove);
                _scene->moveCamera(-dx * 0.05f, dy * 0.05f, 0.0f);
                _trackX = x;
                _trackY = y;
                swInvalidateWindow(_wnd);
            } else if (TheApp->GetMouseMode() == MOUSE_ROLL) {
                swSetCursor(_wnd, _cursorRoll);
                _scene->rollCamera(-DEG2RAD(dx * 0.5f));
                _trackX = x;
                _trackY = y;
                swInvalidateWindow(_wnd);
            } else {
                swSetCursor(_wnd, _cursorRotate);
                _scene->orbitCamera(-DEG2RAD(dx * 0.5f), -DEG2RAD(dy * 0.5f));
                _trackX = x;
                _trackY = y;
                swInvalidateWindow(_wnd);
            }
        } else if (modifiers & SW_SHIFT) {
            swSetCursor(_wnd, _cursorZoom);
            _scene->moveCamera(0.0f, 0.0f, dy * 0.25f);
            _trackX = x;
            _trackY = y;
            swInvalidateWindow(_wnd);
        } else if (modifiers & SW_ALT) {
            /* SGI style ALT key navigation */
            if (_button1down && _button2down) {
                swSetCursor(_wnd, _cursorZoom);
                _scene->moveCamera(0.0f, 0.0f, dy * 0.25f);
                _trackX = x;
                _trackY = y;
                swInvalidateWindow(_wnd);
            } else if (_button1down) {
                swSetCursor(_wnd, _cursorRotate);
                _scene->orbitCamera(-DEG2RAD(dx * 0.5f), -DEG2RAD(dy * 0.5f));
                _trackX = x;
                _trackY = y;
                swInvalidateWindow(_wnd);
            } else if (_button2down) {
                swSetCursor(_wnd, _cursorMove);
                _scene->moveCamera(-dx * 0.05f, dy * 0.05f, 0.0f);
                _trackX = x;
                _trackY = y;
                swInvalidateWindow(_wnd);
            }
        } else if ((_scene->getSelectedHandlesSize() != 0) && !_button2down) {
            Node *node = _scene->getSelection()->getNode();
            int constrain = _scene->getConstrain();
            int field;
            int handle = _scene->getLastSelectedHandle();

            Vec3f old = node->getHandle(handle, &constrain, &field);
            if (field == -1) {
                 _scene->removeSelectedHandles();
                 return;
            }
            if (handle == -1) {
                 return;
            }
            Vec3f v = old;
            if (constrain == CONSTRAIN_NONE) {
                float px, py, pz;
                glPushMatrix();
                glLoadIdentity();
                _scene->transform(_scene->getSelection());
                node->transformForHandle(handle);
                _scene->projectPoint(old.x, old.y, old.z, &px, &py, &pz);
                _scene->unProjectPoint((float) x, (float) height - y, pz, 
                                       &v.x, &v.y, &v.z);
                glPopMatrix();
                v += _offset;
            } else {
                float x1, y1, z1;
                float x2, y2, z2;
                Matrix mat;
                glPushMatrix();
                glLoadIdentity();
                _scene->transform(_scene->getSelection());
                glGetFloatv(GL_MODELVIEW_MATRIX, mat);
                node->transformForHandle(handle);
                _scene->unProjectPoint((float) x, (float) height - y, 0.0f, 
                                       &x1, &y1, &z1);
                _scene->unProjectPoint((float) x, (float) height - y, 1.0f, 
                                       &x2, &y2, &z2);

                glPopMatrix();

                x1 += _offset.x;
                y1 += _offset.y;
                z1 += _offset.z;
                x2 += _offset.x;
                y2 += _offset.y;
                z2 += _offset.z;

                switch (constrain) {
                  case CONSTRAIN_X:
                    y1 -= v.y;
                    z1 -= v.z;
                    y2 -= v.y;
                    z2 -= v.z;
                    v.x = constrainLine(y1, z1, x1, y2, z2, x2);
                    break;
                  case CONSTRAIN_Y:
                    x1 -= v.x;
                    z1 -= v.z;
                    x2 -= v.x;
                    z2 -= v.z;
                    v.y = constrainLine(x1, z1, y1, x2, z2, y2);
                    break;
                  case CONSTRAIN_Z:
                    x1 -= v.x;
                    y1 -= v.y;
                    x2 -= v.x;
                    y2 -= v.y;
                    v.z = constrainLine(x1, y1, z1, x2, y2, z2);
                    break;
                  case CONSTRAIN_XY:
                    v.x = constrainLine(y1 - v.y, z1 - v.z, x1, 
                                        y2 - v.y, z2 - v.z, x2);
                    v.y = constrainLine(x1 - v.x, z1 - v.z, y1, 
                                        x2 - v.x, z2 - v.z, y2);

                    break;
                  case CONSTRAIN_ZX:
                    v.x = constrainLine(y1 - v.y, z1 - v.z, x1, 
                                        y2 - v.y, z2 - v.z, x2);
                    v.z = constrainLine(x1 - v.x, y1 - v.y, z1, 
                                        x2 - v.x, y2 - v.y, z2);
                    break;
                  case CONSTRAIN_YZ:
                    v.y = constrainLine(x1 - v.x, z1 - v.z, y1, 
                                        x2 - v.x, z2 - v.z, y2);
                    v.z = constrainLine(x1 - v.x, y1 - v.y, z1, 
                                        x2 - v.x, y2 - v.y, z2);
                    break;
                  case CONSTRAIN_SPHERE:
                    v = constrainSphere(v, dx, dy, mat);
                    _trackX = x;
                    _trackY = y;
                    break;
                }
            }
            Vec3f diff = v - old;
            for (int i = 0; i < _scene->getSelectedHandlesSize(); i++) {
                int handle = _scene->getSelectedHandle(i);
                if (v != old) {
                    Vec3f oldv = node->getHandle(handle, &constrain, &field);
                    if (!_backedUp) {
                        _scene->backupFieldsStart();
                        if (node->getType() == VRML_NURBS_SURFACE) {
                            NodeNurbsSurface *surf = (NodeNurbsSurface *) node;
                            surf->backupFieldsAppend(field);
                            if (_scene->getXSymetricMode()) {
                                NodeNurbsGroup *nurbsGroup = 
                                      surf->findNurbsGroup();
                                if (nurbsGroup != NULL)
                                    nurbsGroup->backupFieldsAppend(node, field);
                            }
                        } else if (node->getType() == VRML_NURBS_CURVE) {
                            NodeNurbsCurve *curve = (NodeNurbsCurve *) node;
                            curve->backupFieldsAppend(field);
                        } else
                            _scene->backupFieldsAppend(node, field);
                        _scene->backupFieldsDone();
                        _backedUp = true;
                    }
                    node->setHandle(handle, oldv + diff);
                }
                swInvalidateWindow(_wnd);
            }
        } else if (_button1down && _button2down) {
            swSetCursor(_wnd, _cursorMove);
            _scene->moveCamera(-dx * 0.05f, dy * 0.05f, 0.0f);
            _trackX = x;
            _trackY = y;
            swInvalidateWindow(_wnd);
        } else if (_button1down && (TheApp->GetMouseMode() == MOUSE_ROLL)) {
            swSetCursor(_wnd, _cursorRoll);
            _scene->rollCamera(-DEG2RAD(dx * 0.5f));
            _trackX = x;
            _trackY = y;
            swInvalidateWindow(_wnd);
        } else if (_button1down)
            {
            if (TheApp->GetMouseMode() == MOUSE_WALK) {
                if (_width != 0)
                    _walk.x = dx / _width;
                _walk.y = 0;
                if (_height != 0)
                    _walk.z = dy / _height;
                swSetCursor(_wnd, _cursorRotate);
            } else {
                swSetCursor(_wnd, _cursorRotate);
                _scene->orbitCamera(-DEG2RAD(dx * 0.5f), -DEG2RAD(dy * 0.5f));
                _trackX = x;
                _trackY = y;
            }
            swInvalidateWindow(_wnd);
        } else if (_button2down) {
            if (TheApp->GetMouseMode() == MOUSE_WALK) {
                if (_width != 0)
                    _walk.x = dx / _width;
                if (_height != 0)
                    _walk.y = - dy / _height;
                _walk.z = 0;
                swSetCursor(_wnd, _cursorMove);
            } else {
                swSetCursor(_wnd, _cursorZoom);
                _scene->moveCamera(0.0f, 0.0f, dy * 0.25f);
                _trackX = x;
                _trackY = y;
            }
            swInvalidateWindow(_wnd);
        }
    }
}

void Scene3DView::OnLButtonUp(int x, int y, int modifiers) 
{
    if (!_button2down) {
        _trackMouse = false;
        _scene->setTurnPoint();
        if (TheApp->useStereo())
            swSetCursor(_wnd, _cursorNone);
        else 
            swSetCursor(_wnd, _cursorArrow);
        swReleaseCapture(_wnd);
    }
    _button1down = false;
    stopWalking();
    if (_button2down) {
       swMakeCurrent(_dc, _glc);
       swSetFocus(_wnd);

       if (modifiers & SW_ALT)
          swSetCursor(_wnd, _cursorMove);

       _trackMouse = true;
       swSetCapture(_wnd);
       _trackX = x;
       _trackY = y;
    }

}

void Scene3DView::OnMButtonUp(int x, int y, int modifiers) 
{
    if (!_button1down) {
        _trackMouse = false;
        if (TheApp->useStereo())
            swSetCursor(_wnd, _cursorNone);
        else 
            swSetCursor(_wnd, _cursorArrow);
        swReleaseCapture(_wnd);
    }
    _trackMouse = false;
    if (TheApp->useStereo())
        swSetCursor(_wnd, _cursorNone);
    else
        swSetCursor(_wnd, _cursorArrow);
    swReleaseCapture(_wnd);
    _button2down = false;
    stopWalking();
    if (_button1down) {
       swMakeCurrent(_dc, _glc);
       swSetFocus(_wnd);

       if (modifiers & SW_ALT)
          swSetCursor(_wnd, _cursorRotate);

       _trackMouse = true;
       swSetCapture(_wnd);
       _trackX = x;
       _trackY = y;
    }
}

Quaternion &Scene3DView::getQuat(const Path* path)
   {
   static Quaternion ret; 
   // initialise with unit one quaternion
   ret = Quaternion(0.0, 0.0, 0.0, 1.0);
   const NodeList *nodes = path->getNodes();

   for (int i = nodes->size() - 2; i >= 0; i--) {
       if (nodes->get(i)->getType() == VRML_TRANSFORM) {
          NodeTransform *transform = (NodeTransform*)nodes->get(i);
          Quaternion q = transform->getQuat();
          q.normalize();
          ret=ret * q;
       }
   }
   return ret;
}

void
Scene3DView::startWalking(bool walkForward)
{
    _walkForward = walkForward;
    startWalking();
}

void
Scene3DView::startWalking(void)
{
    _walkTimer = swSetTimer(_wnd, 20, walkTimerCallback, this);
    _walking = true;
    _scene->startWalking();
}
 
void
Scene3DView::stopWalking(void) {
    if (_walking) {
        if (_walkTimer != NULL)
            swKillTimer(_walkTimer);
        _walkTimer = NULL;
        _walking = false;
    }
}

int
Scene3DView::walk()
{
    _scene->walkCamera(_walk, _walkForward);
    startWalking();
    return 0;
}

void Scene3DView::Transform3D(const Path* path,InputDevice* inputDevice)
{
    glPushMatrix();
    glLoadIdentity();
    Quaternion viewrot=_scene->getCamera()->getOrientation();
    viewrot.normalize();
    NodeTransform* transform=(NodeTransform*)path->getNode();
    TransformMode* tm=_scene->getTransformMode();
    Quaternion oldrot=transform->getQuat();
    float norm_oldrot=oldrot.norm();
    oldrot.normalize();
    Quaternion scenerot=getQuat(path);
    Quaternion newrot;
    bool rotation_changed=false;
    bool translation_changed=false;
    bool scale_changed=false;
    bool center_changed=false;
    if (tm->hasRotation()) {
       rotation_changed=true; 
       if (tm->isLocal()) {
          Quaternion inputrot=inputDevice->get_localQuaternion(tm);
          newrot=inputrot*oldrot;
       } else {
          Quaternion inputrot=inputDevice->get_quaternion(tm);
          newrot=oldrot*
                 (scenerot*viewrot.conj()*inputrot*viewrot*scenerot.conj());
       }
       transform->setQuat(newrot*norm_oldrot);
       _scene->setField(transform, transform->rotation_Field(), 
                        new SFRotation(newrot*norm_oldrot));
    }
    if (tm->hasTranslation()) {
       Vec3f v=inputDevice->get_vector(tm);
       if ((v[0]!=0) && (v[1]!=0) && (v[2]!=0))
          translation_changed=true;
       Vec3f xyz;
       if (!tm->isLocal()) {
          xyz=viewrot*scenerot.conj()*oldrot.conj()*v;
          v[0]= xyz.x;
          v[1]= xyz.y;
          v[2]= xyz.z;
       } else {
          xyz=(scenerot*v)*scenerot.conj();
          v[0]=-xyz.x;
          v[1]= xyz.y;
          v[2]=-xyz.z;
       }
       transform->setHandle(TRANSLATION,v);
    } else if (tm->tmode==TM_SCALE) {
       Vec3f oldscale=transform->getScale();
       Vec3f v=_scene->constrainVec(inputDevice->get_vector(tm));
       if ((v[0]!=0) || (v[1]!=0) || (v[2]!=0))
          scale_changed=true;
       transform->setHandle(SCALE,v+oldscale);
    } else if (tm->tmode==TM_UNIFORM_SCALE) {
       Vec3f oldscale=transform->getScale();
       Vec3f v=inputDevice->get_vector(tm);
       float mult = 1.0f;
       if ((v[0]!=0) || (v[1]!=0) || (v[2]!=0)) {
          scale_changed=true;
          if ((v[0] !=0) && (oldscale[0]!=0))
              mult = (oldscale[0]+v[0])/oldscale[0];       
          else if ((v[1] !=0) && (oldscale[1]!=0))
              mult = (oldscale[1]+v[1])/oldscale[1];       
          else 
              mult = (oldscale[2]+v[2])/oldscale[2];       
       }
       transform->setHandle(SCALE,oldscale*mult);
    } else if (tm->tmode==TM_CENTER) {
       Vec3f v=_scene->constrainVec(inputDevice->get_vector(tm));
       if ((v[0]!=0) && (v[1]!=0) && (v[2]!=0))
          center_changed=true;
       transform->setHandle(CENTER,(viewrot*oldrot.conj())*v);
    }
    // Backup of inputdevice data is maybe memory murder 8-(
    if (translation_changed)
       _scene->backupField(path->getNode(), transform->rotation_Field());
    if (rotation_changed)
       _scene->backupField(path->getNode(), transform->translation_Field());
    if (scale_changed)
       _scene->backupField(path->getNode(), transform->scale_Field());
    if (center_changed)
       _scene->backupField(path->getNode(), transform->center_Field());
    if (   (translation_changed) || (rotation_changed) 
        || (scale_changed) || (center_changed))
       swInvalidateWindow(_wnd);
    glPopMatrix();
}

void Scene3DView::Handle3D(const Path* path,InputDevice* inputDevice, 
                           int handle)
{
    glPushMatrix();
    glLoadIdentity();
    int constrain = CONSTRAIN_NONE;
    int field;
    Node* node = path->getNode();
    Vec3f old = node->getHandle(handle, &constrain, &field);
    if (field==-1) {
//        _scene->removeOldSelectedHandles();
        return;
    }
    Vec3f vNew = old;
    _scene->transform(_scene->getSelection());
    node->transformForHandle(handle);
    TransformMode* tm=_scene->getTransformMode();
    Quaternion viewrot=_scene->getCamera()->getOrientation();
    Quaternion oldrot(0,0,0,1);
    oldrot.normalize();
    Quaternion scenerot=getQuat(path);
    Quaternion newrot;
    if (tm->hasTranslation()) {
        Vec3f v=inputDevice->get_vector(tm);
        Vec3f xyz;
        if (!tm->isLocal()) {
            xyz=viewrot*scenerot.conj()*oldrot.conj()*v;
            v[0]= xyz.x;
            v[1]= xyz.y;
            v[2]= xyz.z;
        } else {
            xyz=(scenerot*v)*scenerot.conj();
            v[0]=-xyz.x;
            v[1]= xyz.y;
            v[2]=-xyz.z;
        }
        if (_scene->getXonly() || (_scene->getConstrain() == CONSTRAIN_NONE))
            vNew.x+=v[0];
        if (_scene->getYonly() || (_scene->getConstrain() == CONSTRAIN_NONE))
            vNew.y+=v[1];
        if (_scene->getZonly() || (_scene->getConstrain() == CONSTRAIN_NONE))
            vNew.z+=v[2];
    }
    if (vNew != old) {
        if (!_backedUp) {
            _scene->backupField(node, field);
            _backedUp = true;
        }
        node->setHandle( handle, vNew);
    } 
    glPopMatrix();
}

void Scene3DView::Navigate3D(InputDevice* inputDevice)
{
    glPushMatrix();
    glLoadIdentity();
    NodeViewpoint *camera=_scene->getCamera();
    Quaternion viewrot=camera->getOrientation();
    Quaternion newrot;
    bool rotation_changed=false;
    bool translation_changed=false;
    TransformMode* tm=_scene->getTransformMode();
    if (!inputDevice->getHeadNavigation()) 
       {
       TransformMode tmTracker(TM_TRANSLATE,TM_3D,TM_NEAR_FAR);
       tm=&tmTracker;
       }
    if (tm->hasRotation()) {
       rotation_changed=true; 
       Quaternion inputrot=inputDevice->get_quaternion(tm);
       inputrot.z=-inputrot.z;
       newrot=inputrot*viewrot; 
       camera->setOrientation(newrot);
    }
    if (tm->hasTranslation()) {
       Vec3f v= inputDevice->get_vector(tm);
       if ((v[0]!=0) && (v[1]!=0) && (v[2]!=0))
          translation_changed=true;
       Vec3f xyz;
       if ((inputDevice->isTracker()) || (inputDevice->isWand())) {
           xyz=newrot.conj()*(viewrot*v);
       } else
           xyz=viewrot*v;
       v[0]=xyz.x;
       v[1]=xyz.y;
       v[2]=xyz.z;
       Vec3f vcamera=camera->getPosition();
       v[0]=v[0]+vcamera.x;
       v[1]=v[1]+vcamera.y;
       v[2]=v[2]+vcamera.z;
       camera->setPosition(v);
    }
    _scene->applyCamera();
    swInvalidateWindow(_wnd);
    // Backup of inputdevice data is maybe memory murder 8-(
    if (translation_changed)
       _scene->backupField(camera, camera->position_Field());
    if (rotation_changed)
       _scene->backupField(camera, camera->orientation_Field());
    glPopMatrix();
}

// read from a array of InputDevice devices and move/rotate selected thing

int 
Scene3DView::readInputDevice(void)
{
    if ((TheApp->getNumberInputDevices()>0) && (!_fieldEdit)) {
      if (_scene->getHasFocus()) {
        int i;
        for (i=0;i<TheApp->getNumberInputDevices();i++)
          if (TheApp->getInputDevice(i)->hasReadDelay())
            TheApp->getInputDevice(i)->prepareRead();
        for (i=0;i<TheApp->getNumberInputDevices();i++) {
          InputDevice* inputDevice=TheApp->getInputDevice(i);
          if (inputDevice==NULL)
             fprintf(stderr,"internal error in Scene3DView::readInputDevice\n");
          if (inputDevice->readInputDevice() && !(inputDevice->allzero())) {
             TheApp->interact();
             const Path *path = _scene->getSelection();
             Node *node = path->getNode();
             Path *transform = NULL;
             if (_scene->getInputDeviceNavigationMode() &&
                  !inputDevice->isWand())
                Navigate3D(inputDevice);
             else if ((node->getType() == VRML_TRANSFORM) && 
                      !inputDevice->isHead())
                Transform3D(path, inputDevice);
             else if ((_scene->getSelectedHandlesSize() != 0) && 
                      !inputDevice->isHead()) {
                for (int j = 0; j < _scene->getSelectedHandlesSize(); j++) {
                   int handle = _scene->getSelectedHandle(j);
                   Handle3D(path,inputDevice, handle);
                }
             } else if ((node==_scene->getRoot()) && !inputDevice->isWand())
                Navigate3D(inputDevice);
             else if ((node->getType()==VRML_VIEWPOINT) && 
                      !inputDevice->isWand())
                Navigate3D(inputDevice);
             else if (( (transform = _scene->searchTransform()) != NULL)
                      &&
                      (!inputDevice->sendalways())) {
                _scene->setSelection(transform);
                _scene->UpdateViews(this, UPDATE_SELECTION);
                _scene->removeSelectedHandles();
                Transform3D(_scene->getSelection(),inputDevice);
                transform=NULL;
            } else if (inputDevice->isHead())
                Navigate3D(inputDevice);
            }
         }
      }
#ifndef _WIN32  
      if (_inputDeviceTimer) {
          swKillTimer(_inputDeviceTimer);
          _inputDeviceTimer=NULL;
      }
#endif
      _inputDeviceTimer = swSetTimer(_wnd, INPUTDEVICE_TIME, 
                                     inputDeviceTimerCallback, this);

    }
    return(0);
}

float Scene3DView::constrainLine(float x1, float y1, float z1, 
                                 float x2, float y2, float z2) const
{
    float dx = x1 - x2;
    float dy = y1 - y2;
    if (dx == 0.0f && dy == 0.0f) 
        return z2;
    float alpha = (x1 * dx + y1 * dy) / (dx * dx + dy * dy);

    return (1.0f - alpha) * z1 + alpha * z2;
}

void Scene3DView::constrainPlane(float x1, float y1, float z1, 
                                 float x2, float y2, float z2, 
                                 float *x, float *y) const
{
    if(EQUALF(z1, z2)) {
        *x = *y = 0.0f;
        return;
    }

    float alpha = -z1 / (z2 - z1);
    
    *x = (1.0f - alpha) * x1 + alpha * x2;
    *y = (1.0f - alpha) * y1 + alpha * y2;
}

Vec3f Scene3DView::constrainSphere(const Vec3f &euler, float dx, float dy,
                                   const Matrix &mat)
{
    SFRotation sfr(euler, 0);
    Quaternion cam = _scene->getCamera()->getOrientation();

    Quaternion ry(Vec3f(0.0f, 1.0f, 0.0f), DEG2RAD(dx));
    Quaternion rx(Vec3f(1.0f, 0.0f, 0.0f), DEG2RAD(dy));
    Quaternion r = sfr.getQuat() * cam.conj() * ry * rx * cam;
    return SFRotation(r).getEulerAngles(0);
}

