// @(#)root/gl:$Name:  $:$Id: TGLViewer.cxx,v 1.16 2005/09/06 09:26:40 brun Exp $
// Author:  Richard Maunder  25/05/2005

/*************************************************************************
 * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

// TODO: Function descriptions
// TODO: Class def - same as header!!!

#include "TGLViewer.h"
#include "TGLIncludes.h"
#include "TGLStopwatch.h"
#include "TGLDisplayListCache.h"

#include "TGLLogicalShape.h"
#include "TGLPhysicalShape.h"
#include "TGLStopwatch.h"
#include "TGLSceneObject.h" // For TGLFaceSet

#include "TBuffer3D.h"
#include "TBuffer3DTypes.h"

#include "TVirtualPad.h" // Remove when pad removed - use signal

#include "TColor.h"
#include "TError.h"

// For event type translation ExecuteEvent
#include "Buttons.h"
#include "GuiTypes.h"

// Remove - replace with TGLManager
#include "TVirtualGL.h"
#include "TGLRenderArea.h"

#include "KeySymbols.h"
#include "TContextMenu.h"

ClassImp(TGLViewer)

//______________________________________________________________________________
 TGLViewer::TGLViewer(TVirtualPad * pad, Int_t x, Int_t y, 
                     UInt_t width, UInt_t height) :
   fPad(pad),
   fContextMenu(0),
   fPerspectiveCamera(),
   fOrthoXOYCamera(TGLOrthoCamera::kXOY),
   fOrthoYOZCamera(TGLOrthoCamera::kYOZ),
   fOrthoXOZCamera(TGLOrthoCamera::kXOZ),
   fCurrentCamera(&fPerspectiveCamera),
   fInternalRebuild(kFALSE), 
   fAcceptedAllPhysicals(kTRUE),
   fInternalPIDs(kFALSE), 
   fNextInternalPID(1), // 0 reserved
   fComposite(0), fCSLevel(0),
   fAction(kNone), fStartPos(0,0), fLastPos(0,0), fActiveButtonID(0),
   fDrawStyle(kFill),
   fRedrawTimer(0),
   fNextSceneLOD(kHigh),
   fLightState(kLightMask), // All on
   fClipPlane(1.0, 0.0, 0.0, 0.0),
   fUseClipPlane(kFALSE),
   fDrawAxes(kFALSE),
   fInitGL(kFALSE),
   fDebugMode(kFALSE),
   fAcceptedPhysicals(0), 
   fRejectedPhysicals(0),
   fIsPrinting(kFALSE),
   fGLWindow(0)
{
   fRedrawTimer = new TGLRedrawTimer(*this);
   SetViewport(x, y, width, height);
}

//______________________________________________________________________________
 TGLViewer::~TGLViewer()
{
   delete fContextMenu;
   delete fRedrawTimer;
   fPad->ReleaseViewer3D();   
}

//______________________________________________________________________________
 Bool_t TGLViewer::PreferLocalFrame() const
{
   return kTRUE;
}

//______________________________________________________________________________
 void TGLViewer::BeginScene()
{
   if (!fScene.TakeLock(TGLScene::kModifyLock)) {
      return;
   }

   UInt_t destroyedLogicals = 0;
   UInt_t destroyedPhysicals = 0;

   TGLStopwatch stopwatch;
   if (gDebug>2 || fDebugMode) {
      stopwatch.Start();
   }

   // External rebuild?
   if (!fInternalRebuild) 
   {
      // Potentially using external physical IDs
      fInternalPIDs = kFALSE;

      // Reset camera interest to ensure we respond to
      // new scene range
      CurrentCamera().ResetInterest();

      // External rebuilds could potentially invalidate all logical and
      // physical shapes - including any modified physicals
      // Physicals must be removed first
      destroyedPhysicals = fScene.DestroyPhysicals(kTRUE); // include modified
      destroyedLogicals = fScene.DestroyLogicals();

      // Purge out the DL cache - not required once shapes do this themselves properly
      TGLDisplayListCache::Instance().Purge();
   } else {
      // Internal rebuilds - destroy all non-modified physicals no longer of
      // interest to camera - retain logicals
      destroyedPhysicals = fScene.DestroyPhysicals(kFALSE, &CurrentCamera()); // excluded modified
   }

   // Reset internal physical ID counter
   fNextInternalPID = 1;
   
   // Potentially accepting all physicals from external client
   fAcceptedAllPhysicals = kTRUE;

  // Reset tracing info
   fAcceptedPhysicals = 0;
   fRejectedPhysicals = 0;

   if (gDebug>2 || fDebugMode) {
      Info("TGLViewer::BeginScene", "destroyed %d physicals %d logicals in %f msec", 
            destroyedPhysicals, destroyedLogicals, stopwatch.End());
      fScene.Dump();
   }
}

//______________________________________________________________________________
 void TGLViewer::EndScene()
{
   fScene.ReleaseLock(TGLScene::kModifyLock);

   // External scene build
   if (!fInternalRebuild) {
      // Setup camera unless scene is empty
      if (!fScene.BoundingBox().IsEmpty()) {
         SetupCameras(fScene.BoundingBox());
      }
      RequestDraw();
   } else if (fInternalRebuild) {
      fInternalRebuild = kFALSE;
   }      

   if (gDebug>2 || fDebugMode) {
      Info("TGLViewer::EndScene", "Added %d, rejected %d physicals, accepted all:%s", fAcceptedPhysicals, 
                                       fRejectedPhysicals, fAcceptedAllPhysicals ? "Yes":"No");
      fScene.Dump();
   }
}

//______________________________________________________________________________
 Bool_t TGLViewer::RebuildScene()
{
   // If we accepted all offered physicals into the scene no point in 
   // rebuilding it
   if (fAcceptedAllPhysicals) {
      // For debug mode always force even if not required
      if (fDebugMode) {
         Info("TGLViewer::RebuildScene", "not required - all physicals previous accepted (FORCED anyway)");
      }
      else {
         if (gDebug>3) {
            Info("TGLViewer::RebuildScene", "not required - all physicals previous accepted");
         }
         return kFALSE;   
      }
   }
   // Update the camera interest (forced in debug mode) - if changed
   // scene should be rebuilt
   if (!CurrentCamera().UpdateInterest(fDebugMode)) {
      if (gDebug>3 || fDebugMode) {
         Info("TGLViewer::RebuildScene", "not required - no camera interest change");
      }
      return kFALSE;
   }
   
   // We are going to rebuild the scene - ensure any pending redraw timer cancelled now
   fRedrawTimer->Stop();

   if (gDebug>3 || fDebugMode) {
      Info("TGLViewer::RebuildScene", "required");
   }

   fInternalRebuild = kTRUE;
   
   TGLStopwatch timer;
   if (gDebug>2 || fDebugMode) {
      timer.Start();
   }

   // Request a scene fill
   // TODO: Just marking modified doesn't seem to result in pad repaint - need to check on
   //fPad->Modified();
   fPad->Paint();

   if (gDebug>2 || fDebugMode) {
      Info("TGLViewer::RebuildScene", "rebuild complete in %f", timer.End());
   }

   // Need to invalidate/redraw via timer as under Win32 we are already inside the 
   // GUI(DoRedraw) thread - direct invalidation will be cleared when leaving
   fRedrawTimer->RequestDraw(20, kMed);

   return kTRUE;
}

//______________________________________________________________________________
 Int_t TGLViewer::AddObject(const TBuffer3D & buffer, Bool_t * addChildren)
{
   // Add an object to the viewer, using internal physical IDs

   // If this is called we are generating internal physical IDs
   fInternalPIDs = kTRUE;
   Int_t sections = AddObject(fNextInternalPID, buffer, addChildren);   
   return sections;
}

//______________________________________________________________________________
// TODO: Cleanup addChildren to UInt_t flag for full termination - how returned?
 Int_t TGLViewer::AddObject(UInt_t physicalID, const TBuffer3D & buffer, Bool_t * addChildren)
{
   // Add an object to the viewer, using an external physical ID.

   // TODO: Break this up and make easier to understand. This is pretty convoluted
   // due to the large number of cases it has to deal with:
   // i) Exisiting physical and/or logical
   // ii) External provider can supply bounding box or not?
   // iii) Local/global reference frame
   // iv) Defered filling of some sections of the buffer
   // v) Internal or external physical IDs
   // vi) Composite components as special case
   //
   // The buffer filling means the function is re-entrant which adds to complication 

   if (physicalID == 0) {
      Error("TGLViewer::AddObject", "0 physical ID reserved");
      return TBuffer3D::kNone;
   }

   // Internal and external physical IDs cannot be mixed in a scene build
   if (fInternalPIDs && physicalID != fNextInternalPID) {
      Error("TGLViewer::AddObject", "invalid next physical ID - mix of internal + external IDs?");
      return TBuffer3D::kNone;
   }

   if (addChildren) {
      *addChildren = kFALSE;
   }
   
   // Scene should be modify locked
   if (fScene.CurrentLock() != TGLScene::kModifyLock) {
      Error("TGLViewer::AddObject", "expected scene to be in mofifed locked");
      // TODO: For the moment live with this - DrawOverlap() problems to discuss with Andrei
      // Just reject as pad will redraw anyway
      // assert(kFALSE);
      return TBuffer3D::kNone;
   }
   
   // Note that 'object' here is really a physical/logical pair described
   // in buffer + physical ID.

   // If adding component to a current partial composite do this now
   if (fComposite) {
      RootCsg::BaseMesh *newMesh = RootCsg::ConvertToMesh(buffer);
      // Solaris CC can't create stl pair with enumerate type
      fCSTokens.push_back(std::make_pair(static_cast<UInt_t>(TBuffer3D::kCSNoOp), newMesh));
      return TBuffer3D::kNone;
   }

   // TODO: Could be static and save possible double lookup?
   TGLLogicalShape * logical = fScene.FindLogical(reinterpret_cast<ULong_t>(buffer.fID));
   TGLPhysicalShape * physical = fScene.FindPhysical(physicalID);

   // Function can be called twice if extra buffer filling for logical 
   // is required - record last physical ID to detect
   static UInt_t lastPID = 0;

   // First attempt to add this physical 
   if (physicalID != lastPID) {
      // Existing physical
      if (physical) {
         assert(logical); // Have physical - should have logical
         
         if (addChildren) {
            // For internal PID we request all children even if we will reject them.
            // This ensures PID always represent same external entity.
            if (fInternalPIDs) {
               *addChildren = kTRUE;
            } else 
            // For external PIDs we check child interest as we may have reject children previously
            // with a different camera configuration
            {
               *addChildren = CurrentCamera().OfInterest(physical->BoundingBox());
            }
         }
         
         // Always increment the internal physical ID so they
         // match external object sequence
         if (fInternalPIDs) {
            fNextInternalPID++;
         }

         // We don't need anything more for this object
         return TBuffer3D::kNone; 
      }
      // New physical 
      else {
         // First test interest in camera - requires a bounding box
         TGLBoundingBox box;
         
         // If already have logical use it's BB
         if (logical) {
            box = logical->BoundingBox();
            //assert(!box.IsEmpty());
         }
         // else if bounding box in buffer valid use this
         else if (buffer.SectionsValid(TBuffer3D::kBoundingBox)) {
            box.Set(buffer.fBBVertex);
            //assert(!box.IsEmpty());

         // otherwise we need to use raw points to build a bounding box with
         // If raw sections not set it will be requested by ValidateObjectBuffer
         // below and we will re-enter here
         } else if (buffer.SectionsValid(TBuffer3D::kRaw)) {
            box.SetAligned(buffer.NbPnts(), buffer.fPnts);
            //assert(!box.IsEmpty());
         }
      
         // Box is valid?
         if (!box.IsEmpty()) {
            // Test transformed box with camera
            box.Transform(TGLMatrix(buffer.fLocalMaster));
            Bool_t ofInterest = CurrentCamera().OfInterest(box);
            if (addChildren) {
               // For internal PID we request all children even if we will reject them.
               // This ensures PID always represent same external entity.
               if (fInternalPIDs) {
                  *addChildren = kTRUE;
               } else 
               // For external PID request children if physical of interest
               {
                  *addChildren = ofInterest;
               }
            }            
            // Physical is of interest?
            if (!ofInterest) {
               ++fRejectedPhysicals;
               fAcceptedAllPhysicals = kFALSE;

               // Always increment the internal physical ID so they
               // match external object sequence
               if (fInternalPIDs) {
                  fNextInternalPID++;
               }
               return TBuffer3D::kNone;
            } 
         }
      }

      // Need any extra sections in buffer?
      Int_t extraSections = ValidateObjectBuffer(buffer, 
                                                 logical == 0); // Need logical?
      if (extraSections != TBuffer3D::kNone) {         
         return extraSections;
      } else {
         lastPID = physicalID; // Will not to re-test interest
      }
   }

   if(lastPID != physicalID)
   {
      assert(kFALSE);
   }
   // By now we should need to add a physical at least
   if (physical) {
      assert(kFALSE);
      return TBuffer3D::kNone; 
   }

   // Create logical if required
   if (!logical) {
      assert(ValidateObjectBuffer(buffer,true) == TBuffer3D::kNone); // Buffer should be ready
      logical = CreateNewLogical(buffer);
      if (!logical) { 
         assert(kFALSE);
         return TBuffer3D::kNone;
      }
      // Add logical to scene
      fScene.AdoptLogical(*logical);
   }

   // Finally create the physical, binding it to the logical, and add to scene
   physical = CreateNewPhysical(physicalID, buffer, *logical);

   if (physical) { 
      fScene.AdoptPhysical(*physical);
      ++fAcceptedPhysicals;
      if (gDebug>3 && fAcceptedPhysicals%1000 == 0) {
         Info("TGLViewer::AddObject", "added %d physicals", fAcceptedPhysicals);
      }
   } else {
      assert(kFALSE);
   }

   // Always increment the internal physical ID so they
   // match external object sequence
   if (fInternalPIDs) {
      fNextInternalPID++;
   }

   // Reset last physical ID so can detect new one
   lastPID = 0;
   return TBuffer3D::kNone;
}

//______________________________________________________________________________
 Bool_t TGLViewer::OpenComposite(const TBuffer3D & buffer, Bool_t * addChildren)
{
   assert(!fComposite);
   UInt_t extraSections = AddObject(buffer, addChildren);
   assert(extraSections == TBuffer3D::kNone);
   
   // If composite was created it is of interest - we want the rest of the
   // child components   
   if (fComposite) {
      return kTRUE;
   } else {
      return kFALSE;
   }
}

//______________________________________________________________________________
 void TGLViewer::CloseComposite()
{
   // If we have a partially complete composite build it now
   if (fComposite) {
      // TODO: Why is this member and here - only used in BuildComposite()
      fCSLevel = 0;

      RootCsg::BaseMesh *resultMesh = BuildComposite();
      fComposite->SetFromMesh(resultMesh);
      delete resultMesh;
      for (UInt_t i = 0; i < fCSTokens.size(); ++i) delete fCSTokens[i].second;
      fCSTokens.clear();
      fComposite = 0;
   }
}

//______________________________________________________________________________
 void TGLViewer::AddCompositeOp(UInt_t operation)
{
   fCSTokens.push_back(std::make_pair(operation, (RootCsg::BaseMesh *)0));
}

//______________________________________________________________________________
 Int_t TGLViewer::ValidateObjectBuffer(const TBuffer3D & buffer, Bool_t logical) const
{
   // kCore: Should always be filled
   if (!buffer.SectionsValid(TBuffer3D::kCore)) {
      assert(kFALSE);
      return TBuffer3D::kNone;
   }

   // Currently all physical parts (kBoundingBox / kShapeSpecific) of buffer are 
   // filled automatically if producer can - no need to ask 
   if (!logical) {
      return TBuffer3D::kNone;
   }

   // kRawSizes / kRaw: These are on demand based on shape type
   Bool_t needRaw = kFALSE;

   // We need raw tesselation in these cases:
   //
   // 1. Shape type is NOT kSphere / kTube / kTubeSeg / kCutTube / kComposite
   if (buffer.Type() != TBuffer3DTypes::kSphere  &&
       buffer.Type() != TBuffer3DTypes::kTube    &&
       buffer.Type() != TBuffer3DTypes::kTubeSeg &&
       buffer.Type() != TBuffer3DTypes::kCutTube && 
       buffer.Type() != TBuffer3DTypes::kComposite) {
      needRaw = kTRUE;
   }
   // 2. Sphere type is kSPHE, but the sphere is hollow and/or cut - we
   //    do not support native drawing of these currently
   else if (buffer.Type() == TBuffer3DTypes::kSphere) {
      const TBuffer3DSphere * sphereBuffer = dynamic_cast<const TBuffer3DSphere *>(&buffer);
      if (sphereBuffer) {
         if (!sphereBuffer->IsSolidUncut()) {
            needRaw = kTRUE;
         }
      } else {
         assert(kFALSE);
         return TBuffer3D::kNone;
      }
   }
   // 3. kBoundingBox is not filled - we generate a bounding box from 
   else if (!buffer.SectionsValid(TBuffer3D::kBoundingBox)) {
      needRaw = kTRUE;
   }
   // 3. kShapeSpecific is not filled - except in case of top level composite 
   else if (!buffer.SectionsValid(TBuffer3D::kShapeSpecific) && 
             buffer.Type() != TBuffer3DTypes::kComposite) {
      needRaw = kTRUE;
   }
   // 5. We are a component (not the top level) of a composite shape
   else if (fComposite) {
      needRaw = kTRUE;
   }

   if (needRaw && !buffer.SectionsValid(TBuffer3D::kRawSizes|TBuffer3D::kRaw)) {
      return TBuffer3D::kRawSizes|TBuffer3D::kRaw;
   } else {
      return TBuffer3D::kNone;
   }
}

//______________________________________________________________________________
 TGLLogicalShape * TGLViewer::CreateNewLogical(const TBuffer3D & buffer) const
{
   // Buffer should now be correctly filled
   assert(ValidateObjectBuffer(buffer,true) == TBuffer3D::kNone);

   TGLLogicalShape * newLogical = 0;

   switch (buffer.Type()) {
   case TBuffer3DTypes::kLine:
      newLogical = new TGLPolyLine(buffer, buffer.fID);
      break;
   case TBuffer3DTypes::kMarker:
      newLogical = new TGLPolyMarker(buffer, buffer.fID);
      break;
   case TBuffer3DTypes::kSphere: {
      const TBuffer3DSphere * sphereBuffer = dynamic_cast<const TBuffer3DSphere *>(&buffer);
      if (sphereBuffer) {
         // We can only draw solid uncut spheres natively at present
         if (sphereBuffer->IsSolidUncut()) {
            newLogical = new TGLSphere(*sphereBuffer, sphereBuffer->fID);
         } else {
            newLogical = new TGLFaceSet(buffer, buffer.fID);
         }
      }
      else {
         assert(kFALSE);
      }
      break;
   }
   case TBuffer3DTypes::kTube:
   case TBuffer3DTypes::kTubeSeg:
   case TBuffer3DTypes::kCutTube: {
      const TBuffer3DTube * tubeBuffer = dynamic_cast<const TBuffer3DTube *>(&buffer);
      if (tubeBuffer)
      {
         newLogical = new TGLCylinder(*tubeBuffer, tubeBuffer->fID);
      }
      else {
         assert(kFALSE);
      }
      break;
   }
   case TBuffer3DTypes::kComposite: {
      // Create empty faceset and record partial complete composite object
      // Will be populated with mesh in CloseComposite()
      assert(!fComposite);
      fComposite = new TGLFaceSet(buffer, buffer.fID);
      newLogical = fComposite;
      break;
   }
   default:
      newLogical = new TGLFaceSet(buffer, buffer.fID);
      break;
   }

   return newLogical;
}

//______________________________________________________________________________
 TGLPhysicalShape * TGLViewer::CreateNewPhysical(UInt_t ID, 
                                                    const TBuffer3D & buffer, 
                                                    const TGLLogicalShape & logical) const
{
   // Extract indexed color from buffer
   // TODO: Still required? Better use proper color triplet in buffer?
   Int_t colorIndex = buffer.fColor;
   if (colorIndex <= 1) colorIndex = 42; //temporary
   Float_t rgba[4] = { 0.0 };
   TColor *rcol = gROOT->GetColor(colorIndex);

   if (rcol) {
      rcol->GetRGB(rgba[0], rgba[1], rgba[2]);
   }
   
   // Extract transparency component - convert to opacity (alpha)
   rgba[3] = 1.f - buffer.fTransparency / 100.f;

   TGLPhysicalShape * newPhysical = new TGLPhysicalShape(ID, logical, buffer.fLocalMaster, 
                                                         buffer.fReflection, rgba);
   return newPhysical;
}

//______________________________________________________________________________
 RootCsg::BaseMesh *TGLViewer::BuildComposite()
{
   const CSPart_t &currToken = fCSTokens[fCSLevel];
   UInt_t opCode = currToken.first;

   if (opCode != TBuffer3D::kCSNoOp) {
      ++fCSLevel;
      RootCsg::BaseMesh *left = BuildComposite();
      RootCsg::BaseMesh *right = BuildComposite();
      //RootCsg::BaseMesh *result = 0;
      switch (opCode) {
      case TBuffer3D::kCSUnion:
         return RootCsg::BuildUnion(left, right);
      case TBuffer3D::kCSIntersection:
         return RootCsg::BuildIntersection(left, right);
      case TBuffer3D::kCSDifference:
         return RootCsg::BuildDifference(left, right);
      default:
         Error("BuildComposite", "Wrong operation code %d\n", opCode);
         return 0;
      }
   } else return fCSTokens[fCSLevel++].second;
}

//______________________________________________________________________________
 void TGLViewer::InitGL
   assert(!fInitGL);

   // GL initialisation 
   glEnable(GL_LIGHTING);
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_BLEND);
   glEnable(GL_CULL_FACE);
   glCullFace(GL_BACK);
   glClearColor(0.0, 0.0, 0.0, 0.0);
   glClearDepth(1.0);

   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
   Float_t lmodelAmb[] = {0.5f, 0.5f, 1.f, 1.f};
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodelAmb);
   
   TGLUtil::CheckError();
   fInitGL = kTRUE;
}

//______________________________________________________________________________
 void TGLViewer::SetupCameras(const TGLBoundingBox & box)
{
   if (fScene.IsLocked()) {
      Error("TGLViewer::SetupCameras", "expected kUnlocked, found %s", TGLScene::LockName(fScene.CurrentLock()));
      return;
   }

   fPerspectiveCamera.Setup(box);
   fOrthoXOYCamera.Setup(box);
   fOrthoYOZCamera.Setup(box);
   fOrthoXOZCamera.Setup(box);
}

//______________________________________________________________________________
 void TGLViewer::SetupLights()
{
   // Setup lights

   // Locate static light source positions - this is done once only
   // after the scene has been populated 
   if (!fScene.BoundingBox().IsEmpty()) {
      // Find camera offset to scene bounding box so lights can be
      // arranged round it

      // Apply camera so can extract the eye point
      fCurrentCamera->Apply(fScene.BoundingBox());
      TGLBoundingBox box = fScene.BoundingBox();
      TGLVector3 lightVector = fCurrentCamera->EyePoint() - box.Center();

      // Reset the modelview to lights are placed in fixed eye space
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();

      // Calculate a light Z distance - to center of box in eye coords
      // Pull forward slightly (0.85) to avoid to sharp a cutoff
      Double_t lightZ = lightVector.Mag() * 0.85;

      // Calculate a sphere radius to arrange lights round
      Double_t lightRadius = box.Extents().Mag() * 3.0;

      // 0: Front
      // 1: Top   
      // 2: Bottom
      // 3: Left
      // 4: Right
      Float_t pos0[] = {     0.0,              0.0,     0.0, 1.0};
      Float_t pos1[] = {     0.0,      lightRadius, -lightZ, 1.0};
      Float_t pos2[] = {     0.0,     -lightRadius, -lightZ, 1.0};
      Float_t pos3[] = {-lightRadius,          0.0, -lightZ, 1.0};
      Float_t pos4[] = { lightRadius,          0.0, -lightZ, 1.0};

      Float_t frontLightColor[] = {0.35, 0.35, 0.35, 1.0};
      Float_t sideLightColor[] = {0.7, 0.7, 0.7, 1.0};
      glLightfv(GL_LIGHT0, GL_POSITION, pos0);
      glLightfv(GL_LIGHT0, GL_DIFFUSE, frontLightColor);
      glLightfv(GL_LIGHT1, GL_POSITION, pos1);
      glLightfv(GL_LIGHT1, GL_DIFFUSE, sideLightColor);
      glLightfv(GL_LIGHT2, GL_POSITION, pos2);
      glLightfv(GL_LIGHT2, GL_DIFFUSE, sideLightColor);
      glLightfv(GL_LIGHT3, GL_POSITION, pos3);
      glLightfv(GL_LIGHT3, GL_DIFFUSE, sideLightColor);
      glLightfv(GL_LIGHT4, GL_POSITION, pos4);
      glLightfv(GL_LIGHT4, GL_DIFFUSE, sideLightColor);
   }

   // TODO: Could detect state change and only adjust if a change
   for (UInt_t light = 0; (1<<light) < kLightMask; light++) {
      if ((1<<light) & fLightState) {
         glEnable(GLenum(GL_LIGHT0 + light));
      } else {
         glDisable(GLenum(GL_LIGHT0 + light));
      }
   }
}

//______________________________________________________________________________
 void TGLViewer::DoDraw()
{
   // Draw out the the current viewer/scene

   // Locking mainly for Win32 mutli thread safety - but no harm in all using it
   // During normal draws a draw lock is taken in other thread (Win32) in RequestDraw()
   // to ensure thread safety. For PrintObjects repeated Draw() calls are made.
   // If no draw lock taken get one now
   if (fScene.CurrentLock() != TGLScene::kDrawLock) {
      if (!fScene.TakeLock(TGLScene::kDrawLock)) {
         Error("TGLViewer::DoDraw", "scene is %s", TGLScene::LockName(fScene.CurrentLock()));
      }
   }

   fRedrawTimer->Stop();

   TGLStopwatch timer;
   UInt_t drawn = 0;
   if (gDebug>2) {
      timer.Start();
   }

   // GL pre draw setup
   if (!fIsPrinting) PreDraw();

   SetupLights();

   // Apply current camera projection - always do this even if scene is empty and we don't draw, 
   // as scene will likely be rebuilt, requiring camera interest and caching needs to be established
   fCurrentCamera->Apply(fScene.BoundingBox());

   // Something to draw?
   if (!fScene.BoundingBox().IsEmpty()) {
      // Draw axes. Still get's clipped - need to find a way to disable clips
      // for this
      if (fDrawAxes) {
         fScene.DrawAxes();
      }

      // Apply any clipping plane
      if (fUseClipPlane) {
         glEnable(GL_CLIP_PLANE0);
         glClipPlane(GL_CLIP_PLANE0, fClipPlane.CArr());
      } else {
         glDisable(GL_CLIP_PLANE0);
      }

      if (fNextSceneLOD == kHigh) {
         // High quality (final pass) draws have unlimited time to complete
         drawn = fScene.Draw(*fCurrentCamera, fDrawStyle, fNextSceneLOD);
      } else {
         // Other (interactive) draws terminate after 100 msec
         drawn = fScene.Draw(*fCurrentCamera, fDrawStyle, fNextSceneLOD, 100.0);
      }

      // Debug mode - draw some extra boxes
      if (fDebugMode) {
         glDisable(GL_LIGHTING);
         CurrentCamera().DrawDebugAids();

         // Green scene bounding box
         glColor3d(0.0, 1.0, 0.0);
         fScene.BoundingBox().Draw();
         glEnable(GL_LIGHTING);
       }
   }

   PostDraw();

   if (gDebug>2) {
      Info("TGLViewer::DoDraw()", "Took %f msec", timer.End());
      if (gDebug>3) {
         TGLDisplayListCache::Instance().Dump();
      }
   }

   // Release draw lock on scene
   fScene.ReleaseLock(TGLScene::kDrawLock);

   Bool_t redrawReq = kFALSE;

   // Debug mode have forced rebuilds only
   if (!fDebugMode) {
      // Final draw pass
      if (fNextSceneLOD == kHigh) {
         RebuildScene();
      } else {
         // Final draw pass required
         redrawReq = kTRUE;
      }
   } else {
      // Final draw pass required?
      redrawReq = fNextSceneLOD != kHigh;
   }

   // Request final pass high quality redraw via timer
   if (redrawReq) {
      fRedrawTimer->RequestDraw(100, kHigh);
   }
}

//______________________________________________________________________________
 void TGLViewer::PreDraw()
{
   // GL work which must be done before each draw of scene
   MakeCurrent();

   // Initialise GL if not done
   if (!fInitGL) {
      InitGL();
   }

   // Setup GL for current draw style - fill, wireframe, outline
   // Any GL modifications need to be defered until drawing time - 
   // to ensure we are in correct thread/context under Windows
   // TODO: Could detect change and only mod if changed for speed
   switch (fDrawStyle) {
      case (kFill): {
         glEnable(GL_LIGHTING);
         glEnable(GL_CULL_FACE);
         glPolygonMode(GL_FRONT, GL_FILL);
         glClearColor(0.0, 0.0, 0.0, 1.0); // Black
         break;
      }
      case (kWireFrame): {
         glDisable(GL_CULL_FACE);
         glDisable(GL_LIGHTING);
         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
         glClearColor(0.0, 0.0, 0.0, 1.0); // Black
         break;
      }
      case (kOutline): {
         glEnable(GL_LIGHTING);
         glEnable(GL_CULL_FACE);
         glPolygonMode(GL_FRONT, GL_FILL);
         glClearColor(1.0, 1.0, 1.0, 1.0); // White
         break;
      }
      default: {
         assert(kFALSE);
      }
   }

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   TGLUtil::CheckError();
}

//______________________________________________________________________________
 void TGLViewer::PostDraw()
{
   // GL work which must be done after each draw of scene
   SwapBuffers();

   // Flush everything in case picking starts
   glFlush();

   TGLUtil::CheckError();
}

//______________________________________________________________________________
 void TGLViewer::MakeCurrent() const
{
   fGLWindow->MakeCurrent();
}

//______________________________________________________________________________
 void TGLViewer::SwapBuffers() const
{
   if (fScene.CurrentLock() != TGLScene::kDrawLock && 
      fScene.CurrentLock() != TGLScene::kSelectLock) {
      Error("TGLViewer::MakeCurrent", "scene is %s", TGLScene::LockName(fScene.CurrentLock()));   
   }
   fGLWindow->SwapBuffers();
}

//______________________________________________________________________________
 void TGLViewer::RequestDraw(UInt_t LOD)
{
   fNextSceneLOD = LOD;
   fRedrawTimer->Stop();
   
   // Take scene lock - to be revisited
   if (!fScene.TakeLock(TGLScene::kDrawLock)) {
      // If taking drawlock fails the previous draw is still in progress
      // set timer to do this one later
      if (gDebug>3) {
         Info("TGLViewer::DoRedraw", "scene drawlocked - requesting another draw");
      }
      fRedrawTimer->RequestDraw(100, fNextSceneLOD);
      return;
   }
   
   gVirtualGL->DrawViewer(this);
}

//______________________________________________________________________________
 Bool_t TGLViewer::DoSelect(const TGLRect & rect)
{
   // Select lock should already been taken in other thread in 
   // TGLViewer::DoSelect()
   if (fScene.CurrentLock() != TGLScene::kSelectLock) {
      Error("TGLViewer::Draw", "expected kSelectLock, found %s", TGLScene::LockName(fScene.CurrentLock()));
      return kFALSE;
   }

   MakeCurrent();

   TGLRect glRect(rect);
   WindowToGL(glRect);
   fCurrentCamera->Apply(fScene.BoundingBox(), &glRect);

   Bool_t changed = fScene.Select(*fCurrentCamera, fDrawStyle);

   // Release select lock on scene before invalidation
   fScene.ReleaseLock(TGLScene::kSelectLock);

   if (changed) {
      RequestDraw(kHigh);

      // Inform external client selection has been modified
      SelectionChanged();
   }

   return changed;
}

//______________________________________________________________________________
 void TGLViewer::RequestSelect(UInt_t x, UInt_t y)
{
   // Take select lock on scene immediately we enter here - it is released
   // in the other (drawing) thread - see TGLViewer::Select()
   // Removed when gVirtualGL removed
   if (!fScene.TakeLock(TGLScene::kSelectLock)) {
      return;
   }

   // TODO: Check only the GUI thread ever enters here & DoSelect.
   // Then TVirtualGL and TGLKernel can be obsoleted.
   TGLRect selectRect(x, y, 3, 3); // TODO: Constant somewhere
   gVirtualGL->SelectViewer(this, &selectRect); 
}
//______________________________________________________________________________
 void TGLViewer::SetViewport(Int_t x, Int_t y, UInt_t width, UInt_t height)
{
   if (fScene.IsLocked()) {
      Error("TGLViewer::SetViewport", "expected kUnlocked, found %s", TGLScene::LockName(fScene.CurrentLock()));
      return;
   }
   fViewport.Set(x, y, width, height);
   fCurrentCamera->SetViewport(fViewport);

   // Can't do this until gVirtualGL has been setup - change with TGLManager
   //RequestDraw();
}

//______________________________________________________________________________
 void TGLViewer::SetCurrentCamera(ECamera camera)
{
   if (fScene.IsLocked()) {
      Error("TGLViewer::SetCurrentCamera", "expected kUnlocked, found %s", TGLScene::LockName(fScene.CurrentLock()));
      return;
   }

   switch(camera) {
      case(kCameraPerspective): {
         fCurrentCamera = &fPerspectiveCamera;
         break;
      }
      case(kCameraXOY): {
         fCurrentCamera = &fOrthoXOYCamera;
         break;
      }
      case(kCameraYOZ): {
         fCurrentCamera = &fOrthoYOZCamera;
         break;
      }
      case(kCameraXOZ): {
         fCurrentCamera = &fOrthoXOZCamera;
         break;
      }
      default: {
         assert(kFALSE);
         break;
      }
   }

   // Ensure any viewport has been propigated to the current camera
   fCurrentCamera->SetViewport(fViewport);

   // And viewer is redrawn
   RequestDraw();
}

//______________________________________________________________________________
 void TGLViewer::ToggleLight(ELight light)
{
   // Toggle supplied light on/off

   // N.B. We can't directly call glEnable here as may not be in correct gl context
   // adjust mask and set when drawing
   if (light >= kLightMask) {
      assert(kFALSE);
      return;
   }

   fLightState ^= light;

   RequestDraw();
}

//______________________________________________________________________________
 void TGLViewer::ToggleAxes()
{
   fDrawAxes = !fDrawAxes;
   RequestDraw();
}

//______________________________________________________________________________
 void TGLViewer::ToggleClip()
{
   fUseClipPlane = !fUseClipPlane;
   RequestDraw();
}

//______________________________________________________________________________
 void TGLViewer::SetClipPlaneEq(const TGLPlane & eqn)
{
   fClipPlane.Set(eqn);
   RequestDraw();
}

//______________________________________________________________________________
 void TGLViewer::SetSelectedColor(const Float_t rgba[4])
{
   if (fScene.SetSelectedColor(rgba)) {
      RequestDraw();
   }
}

//______________________________________________________________________________
 void TGLViewer::SetColorOnSelectedFamily(const Float_t rgba[4])
{
   if (fScene.SetColorOnSelectedFamily(rgba)) {
      RequestDraw();
   }
}

//______________________________________________________________________________
 void TGLViewer::SetSelectedGeom(const TGLVertex3 & trans, const TGLVector3 & scale)
{
   if (fScene.SetSelectedGeom(trans, scale)) {
      RequestDraw();
   }
}

//______________________________________________________________________________
 void TGLViewer::SelectionChanged() 
{ 
   Emit("SelectionChanged()"); 
}

//______________________________________________________________________________
 Int_t TGLViewer::DistancetoPrimitive(Int_t /*px*/, Int_t /*py*/)
{
   // Can't track the indvidual objects in rollover. Just set the viewer as the
   // selected object, and return 0 (object identified) so we receive ExecuteEvent calls
   gPad->SetSelected(this);
   return 0;
}

//______________________________________________________________________________
 void TGLViewer::ExecuteEvent(Int_t event, Int_t px, Int_t py)
{
   /*enum EEventType {
   kNoEvent       =  0,
   kButton1Down   =  1, kButton2Down   =  2, kButton3Down   =  3, kKeyDown  =  4,
   kButton1Up     = 11, kButton2Up     = 12, kButton3Up     = 13, kKeyUp    = 14,
   kButton1Motion = 21, kButton2Motion = 22, kButton3Motion = 23, kKeyPress = 24,
   kButton1Locate = 41, kButton2Locate = 42, kButton3Locate = 43,
   kMouseMotion   = 51, kMouseEnter    = 52, kMouseLeave    = 53,
   kButton1Double = 61, kButton2Double = 62, kButton3Double = 63
   
   enum EGEventType {
   kGKeyPress, kKeyRelease, kButtonPress, kButtonRelease,
   kMotionNotify, kEnterNotify, kLeaveNotify, kFocusIn, kFocusOut,
   kExpose, kConfigureNotify, kMapNotify, kUnmapNotify, kDestroyNotify,
   kClientMessage, kSelectionClear, kSelectionRequest, kSelectionNotify,
   kColormapNotify, kButtonDoubleClick, kOtherEvent*/

   // Map our event EEventType (base/inc/Buttons.h) back to Event_t (base/inc/GuiTypes.h)
   // structure, and call appropriate HandleXXX() function 
   Event_t eventSt;
   eventSt.fX = px;
   eventSt.fY = py;

   switch (event) {
      case kButton1Down:
      case kButton1Up:
      {
         eventSt.fCode = kButton1;
         eventSt.fType = kButton1Down ? kButtonPress:kButtonRelease;
         HandleButton(&eventSt);
      }
      case kButton2Down:
      case kButton2Up:
      {
         eventSt.fCode = kButton2;
         eventSt.fType = kButton2Down ? kButtonPress:kButtonRelease;
         HandleButton(&eventSt);
      }
      case kButton3Down:
      case kButton3Up:
      {
         eventSt.fCode = kButton3;
         eventSt.fType = kButton3Down ? kButtonPress:kButtonRelease;
         HandleButton(&eventSt);
      }
      case kButton1Double:
      case kButton2Double:
      case kButton3Double:
      {
         eventSt.fCode = kButton1Double ? kButton1 : kButton2Double ? kButton2 : kButton3;
         eventSt.fType = kButtonDoubleClick;
         HandleDoubleClick(&eventSt);
      }
      case kButton1Motion:
      case kButton2Motion:
      case kButton3Motion:
      {
         eventSt.fCode = kButton1Motion ? kButton1 : kButton2Motion ? kButton2 : kButton3;
         eventSt.fType = kMotionNotify;
         HandleMotion(&eventSt);
      }
      case kKeyPress: // We only care about full key 'presses' not individual down/up
      {
         eventSt.fType = kKeyRelease;
         eventSt.fCode = px; // px contains key code - need modifiers from somewhere
         HandleKey(&eventSt);
      }
      default: 
      {
         assert(kFALSE);
      }
   }
};

//______________________________________________________________________________
 Bool_t TGLViewer::HandleEvent(Event_t *event)
{
   if (event->fType == kFocusIn) {
      assert(fAction == kNone);
      fAction = kNone;
   }
   if (event->fType == kFocusOut) {
      fAction = kNone;
   }

   return kTRUE;
}

//______________________________________________________________________________
 Bool_t TGLViewer::HandleButton(Event_t *event)
{
   if (fScene.IsLocked()) {
      if (gDebug>2) {
         Info("TGLViewer::HandleButton", "ignored - scene is %s", TGLScene::LockName(fScene.CurrentLock()));
      }
      return kFALSE;
   }

   // Only process one action/button down/up pairing - block others
   if (fAction != kNone) {
      if (event->fType == kButtonPress ||
          (event->fType == kButtonRelease && event->fCode != fActiveButtonID)) {
         return kFALSE;
      }
   }
   
   // Button DOWN
   if (event->fType == kButtonPress) {
      Bool_t grabPointer = kFALSE;

      // Record active button for release
      fActiveButtonID = event->fCode;

      // Record mouse start
      fStartPos.fX = fLastPos.fX = event->fX;
      fStartPos.fY = fLastPos.fY = event->fY;
      
      switch(event->fCode) {
         // LEFT mouse button
         case(kButton1): {
            if (event->fState & kKeyShiftMask) {
               RequestSelect(event->fX, event->fY);

               // TODO: If no selection start a box select
            } else {
               fAction = kRotate;
               grabPointer = kTRUE;
            }
            break;
         }
         // MID mouse button
         case(kButton2): {
            if (event->fState & kKeyShiftMask) {
               RequestSelect(event->fX, event->fY);
               // Start object drag
               if (fScene.GetSelected()) {
                  fAction = kDrag;
                  grabPointer = kTRUE;
               }
            } else {
               fAction = kTruck;
               grabPointer = kTRUE;
            }
            break;
         }
         // RIGHT mouse button
         case(kButton3): {
            // Shift + Right mouse - select+context menu
            if (event->fState & kKeyShiftMask) {
               RequestSelect(event->fX, event->fY);
               const TGLPhysicalShape * selected = fScene.GetSelected();
               if (selected) {
                  if (!fContextMenu) {
                     fContextMenu = new TContextMenu("glcm", "GL Viewer Context Menu");
                  }
                  selected->InvokeContextMenu(*fContextMenu, event->fX, event->fY);
               }
            } else {
               fAction = kDolly;
               grabPointer = kTRUE;
            }
            break;
         }
      }
   }
   // Button UP
   else if (event->fType == kButtonRelease) {
      // TODO: Check on Linux - on Win32 only see button release events
      // for mouse wheel
      switch(event->fCode) {
         // Buttons 4/5 are mouse wheel
         case(kButton4): {
            // Zoom out (adjust camera FOV)
            if (CurrentCamera().Zoom(-30, event->fState & kKeyControlMask, 
                                          event->fState & kKeyShiftMask)) { //TODO : val static const somewhere
               RequestDraw();
            }
            break;
         }
         case(kButton5): {
            // Zoom in (adjust camera FOV)
            if (CurrentCamera().Zoom(+30, event->fState & kKeyControlMask, 
                                          event->fState & kKeyShiftMask)) { //TODO : val static const somewhere
               RequestDraw();
            }
            break;
         }
      }
      fAction = kNone;
   }

   return kTRUE;
}

//______________________________________________________________________________
 Bool_t TGLViewer::HandleDoubleClick(Event_t *event)
{
   if (fScene.IsLocked()) {
      if (gDebug>3) {
         Info("TGLViewer::HandleDoubleClick", "ignored - scene is %s", TGLScene::LockName(fScene.CurrentLock()));
      }
      return kFALSE;
   }

   // Reset interactive camera mode on button double
   // click (unless mouse wheel)
   if (event->fCode != kButton4 && event->fCode != kButton5) {
      CurrentCamera().Reset();
      fStartPos.fX = fLastPos.fX = event->fX;
      fStartPos.fY = fLastPos.fY = event->fY;
      RequestDraw();
   }
   return kTRUE;
}

//______________________________________________________________________________
 Bool_t TGLViewer::HandleConfigureNotify(Event_t *event)
{
   if (fScene.IsLocked()) {
      if (gDebug>3) {
         Info("TGLViewer::HandleConfigure", "ignored - scene is %s", TGLScene::LockName(fScene.CurrentLock()));
      }
      return kFALSE;
   }

   if (event) {
      SetViewport(event->fX, event->fY, event->fWidth, event->fHeight);
   }
   return kTRUE;
}

//______________________________________________________________________________
 Bool_t TGLViewer::HandleKey(Event_t *event)
{
   if (fScene.IsLocked()) {
      if (gDebug>3) {
         Info("TGLViewer::HandleKey", "ignored - scene is %s", TGLScene::LockName(fScene.CurrentLock()));
      }
      return kFALSE;
   }

   char tmp[10] = {0};
   UInt_t keysym = 0;

   gVirtualX->LookupString(event, tmp, sizeof(tmp), keysym);
   
   Bool_t redraw = kFALSE;

   switch (keysym) {
   case kKey_Plus:
   case kKey_J:
   case kKey_j:
      redraw = CurrentCamera().Dolly(10, event->fState & kKeyControlMask, 
                                         event->fState & kKeyShiftMask); //TODO : val static const somewhere
      break;
   case kKey_Minus:
   case kKey_K:
   case kKey_k:
      redraw = CurrentCamera().Dolly(-10, event->fState & kKeyControlMask, 
                                          event->fState & kKeyShiftMask); //TODO : val static const somewhere
      break;
   case kKey_R:
   case kKey_r:
      fDrawStyle = kFill;
      redraw = kTRUE;
      break;
   case kKey_W:
   case kKey_w:
      fDrawStyle = kWireFrame;
      redraw = kTRUE;
      break;
   case kKey_T:
   case kKey_t:
      fDrawStyle = kOutline;
      redraw = kTRUE;
      break;
   case kKey_Up:
      redraw = CurrentCamera().Truck(fViewport.CenterX(), fViewport.CenterY(), 0, 5);
      break;
   case kKey_Down:
      redraw = CurrentCamera().Truck(fViewport.CenterX(), fViewport.CenterY(), 0, -5);
      break;
   case kKey_Left:
      redraw = CurrentCamera().Truck(fViewport.CenterX(), fViewport.CenterY(), -5, 0);
      break;
   case kKey_Right:
      redraw = CurrentCamera().Truck(fViewport.CenterX(), fViewport.CenterY(), 5, 0);
      break;
   // Toggle debugging mode
   case kKey_D:
   case kKey_d:
      fDebugMode = !fDebugMode;
      redraw = kTRUE;
      Info("OpenGL viewer debug mode : ", fDebugMode ? "ON" : "OFF");
      break;
   // Forced rebuild for debugging mode
   case kKey_Space:
      if (fDebugMode) {
         Info("OpenGL viewer FORCED rebuild", "");
         RebuildScene();
      }
   }

   if (redraw) {
      RequestDraw();
   }
   
   return kTRUE;
}

//______________________________________________________________________________
 Bool_t TGLViewer::HandleMotion(Event_t *event)
{
   if (fScene.IsLocked()) {
      if (gDebug>3) {
         Info("TGLViewer::HandleMotion", "ignored - scene is %s", TGLScene::LockName(fScene.CurrentLock()));
      }
      return kFALSE;
   }

   if (!event) {
      return kFALSE;
   }
   
   Bool_t redraw = kFALSE;
   
   Int_t xDelta = event->fX - fLastPos.fX;
   Int_t yDelta = event->fY - fLastPos.fY;
   
   // Camera interface requires GL coords - Y inverted
   if (fAction == kRotate) {
      redraw = CurrentCamera().Rotate(xDelta, -yDelta);
   } else if (fAction == kTruck) {
      redraw = CurrentCamera().Truck(event->fX, fViewport.Y() - event->fY, xDelta, -yDelta);
   } else if (fAction == kDolly) {
      redraw = CurrentCamera().Dolly(xDelta, event->fState & kKeyControlMask, 
                                                 event->fState & kKeyShiftMask);
   } else if (fAction == kDrag) {
      const TGLPhysicalShape * selected = fScene.GetSelected();
      if (selected) {
         TGLVector3 shift = CurrentCamera().ProjectedShift(selected->BoundingBox().Center(), xDelta, -yDelta);

         // Don't modify selected directly as scene needs to invalidate bounding box
         // hence will only give us a const handle on selected
         redraw = fScene.ShiftSelected(shift);

         // Inform external client selection has been modified
         SelectionChanged();
      }
   }

   fLastPos.fX = event->fX;
   fLastPos.fY = event->fY;
   
   if (redraw) {
      RequestDraw();
   }
   
   return kTRUE;
}

//______________________________________________________________________________
 Bool_t TGLViewer::HandleExpose(Event_t *)
{
   if (fScene.IsLocked()) {
      if (gDebug>3) {
         Info("TGLViewer::HandleExpose", "ignored - scene is %s", TGLScene::LockName(fScene.CurrentLock()));
      }
      return kFALSE;
   }

   RequestDraw(kHigh);
   return kTRUE;
}



ROOT page - Class index - Class Hierarchy - Top of the page

This page has been automatically generated. If you have any comments or suggestions about the page layout send a mail to ROOT support, or contact the developers with any questions or problems regarding ROOT.