// @(#)root/gl:$Id$ // 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. * *************************************************************************/ #include "TGLPhysicalShape.h" #include "TGLLogicalShape.h" #include "TGLPShapeRef.h" #include "TGLCamera.h" #include "TGLRnrCtx.h" #include "TGLIncludes.h" #include "TGLScene.h" #include "TColor.h" #include "TROOT.h" #include // For debug tracing #include "TClass.h" #include "TError.h" /** \class TGLPhysicalShape \ingroup opengl Concrete physical shape - a GL drawable. Physical shapes are the objects the user can actually see, select, move in the viewer. It is a placement of the associated local frame TGLLogicaShape into the world frame. The draw process is: Load attributes - material colors etc Load translation matrix - placement Load gl name (for selection) Call our associated logical shape Draw() to draw placed shape The physical shape supports translation, scaling and rotation, selection, color changes, and permitted modification flags etc. A physical shape cannot modify or be bound to another (or no) logical shape - hence const & handle. It can perform mutable reference counting on the logical to enable purging. Physical shape also maintains a list of references to it and provides notifications of change and destruction. See class TGLPShapeRef which needs to be sub-classes for real use. See base/src/TVirtualViewer3D for description of common external 3D viewer architecture and how external viewer clients use it. */ ClassImp(TGLPhysicalShape) //////////////////////////////////////////////////////////////////////////////// /// Construct a physical shape using arguments: /// - ID - unique drawable id. /// - logicalShape - bound logical shape /// - transform - transform for placement of logical drawing /// - invertedWind - use inverted face polygon winding? /// - rgba - basic four component (RGBA) diffuse color TGLPhysicalShape::TGLPhysicalShape(UInt_t id, const TGLLogicalShape & logicalShape, const TGLMatrix & transform, Bool_t invertedWind, const Float_t rgba[4]) : fLogicalShape (&logicalShape), fNextPhysical (0), fFirstPSRef (0), fID (id), fTransform (transform), fManip (kManipAll), fSelected (0), fInvertedWind (invertedWind), fModified (kFALSE), fIsScaleForRnr(kFALSE) { fLogicalShape->AddRef(this); UpdateBoundingBox(); // Initialise color InitColor(rgba); } //////////////////////////////////////////////////////////////////////////////// /// Construct a physical shape using arguments: /// - id - unique drawable id. /// - logicalShape - bound logical shape /// - transform - 16 Double_t component transform for placement of logical drawing /// - invertedWind - use inverted face polygon winding? /// - rgba - basic four component (RGBA) diffuse color TGLPhysicalShape::TGLPhysicalShape(UInt_t id, const TGLLogicalShape & logicalShape, const Double_t * transform, Bool_t invertedWind, const Float_t rgba[4]) : fLogicalShape (&logicalShape), fNextPhysical (0), fFirstPSRef (0), fID (id), fTransform (transform), fManip (kManipAll), fSelected (0), fInvertedWind (invertedWind), fModified (kFALSE), fIsScaleForRnr(kFALSE) { fLogicalShape->AddRef(this); // Temporary hack - invert the 3x3 part of matrix as TGeo sends this // in opp layout to shear/translation parts. Speak to Andrei about best place // to fix - probably when filling TBuffer3D - should always be OGL convention? fTransform.Transpose3x3(); UpdateBoundingBox(); // Initialise color InitColor(rgba); } //////////////////////////////////////////////////////////////////////////////// /// Destroy the physical shape. TGLPhysicalShape::~TGLPhysicalShape() { // If destroyed from the logical shape itself the pointer has already // been cleared. if (fLogicalShape) fLogicalShape->SubRef(this); // Remove all references. while (fFirstPSRef) { fFirstPSRef->SetPShape(0); } } //////////////////////////////////////////////////////////////////////////////// /// Add reference ref. void TGLPhysicalShape::AddReference(TGLPShapeRef* ref) { assert(ref != 0); ref->fNextPSRef = fFirstPSRef; fFirstPSRef = ref; } //////////////////////////////////////////////////////////////////////////////// /// Remove reference ref. void TGLPhysicalShape::RemoveReference(TGLPShapeRef* ref) { assert(ref != 0); Bool_t found = kFALSE; if (fFirstPSRef == ref) { fFirstPSRef = ref->fNextPSRef; found = kTRUE; } else { TGLPShapeRef *shp1 = fFirstPSRef, *shp2; while ((shp2 = shp1->fNextPSRef) != 0) { if (shp2 == ref) { shp1->fNextPSRef = shp2->fNextPSRef; found = kTRUE; break; } shp1 = shp2; } } if (found) { ref->fNextPSRef = 0; } else { Error("TGLPhysicalShape::RemoveReference", "Attempt to un-ref an unregistered shape-ref."); } } //////////////////////////////////////////////////////////////////////////////// /// Call this after modifying the physical so that the information /// can be propagated to the object referencing it. void TGLPhysicalShape::Modified() { fModified = kTRUE; TGLPShapeRef * ref = fFirstPSRef; while (ref) { ref->PShapeModified(); ref = ref->fNextPSRef; } } //////////////////////////////////////////////////////////////////////////////// /// Update our internal bounding box (in global frame). void TGLPhysicalShape::UpdateBoundingBox() { fBoundingBox.Set(fLogicalShape->BoundingBox()); fBoundingBox.Transform(fTransform); fIsScaleForRnr = fTransform.IsScalingForRender(); if (fLogicalShape->GetScene()) fLogicalShape->GetScene()->InvalidateBoundingBox(); } //////////////////////////////////////////////////////////////////////////////// /// Initialise the colors, using basic RGBA diffuse material color supplied void TGLPhysicalShape::InitColor(const Float_t rgba[4]) { // TODO: Make a color class fColor[0] = rgba[0]; fColor[1] = rgba[1]; fColor[2] = rgba[2]; fColor[3] = rgba[3]; fColor[4] = fColor[5] = fColor[6] = 0.0f; //ambient fColor[8] = fColor[9] = fColor[10] = 0.7f; //specular fColor[12] = fColor[13] = fColor[14] = 0.0f; //emission fColor[7] = fColor[11] = fColor[15] = 1.0f; //alpha fColor[16] = 60.0f; //shininess } //////////////////////////////////////////////////////////////////////////////// /// Set full color attributes - see OpenGL material documentation /// for full description. /// 0->3 diffuse, 4->7 ambient, 8->11 specular, 12->15 emission, 16 shininess void TGLPhysicalShape::SetColor(const Float_t color[17]) { // TODO: Make a color class for (UInt_t i = 0; i < 17; i++) { fColor[i] = color[i]; } Modified(); } //////////////////////////////////////////////////////////////////////////////// /// Set full color attributes to all physicals sharing the same /// logical with this object. void TGLPhysicalShape::SetColorOnFamily(const Float_t color[17]) { TGLPhysicalShape* pshp = const_cast(fLogicalShape->GetFirstPhysical()); while (pshp) { pshp->SetColor(color); pshp = pshp->fNextPhysical; } } //////////////////////////////////////////////////////////////////////////////// /// Set color from ROOT color index and transparency [0,100]. void TGLPhysicalShape::SetDiffuseColor(const Float_t rgba[4]) { for (Int_t i=0; i<4; ++i) fColor[i] = rgba[i]; Modified(); } //////////////////////////////////////////////////////////////////////////////// /// Set color from RGBA quadruplet. void TGLPhysicalShape::SetDiffuseColor(const UChar_t rgba[4]) { for (Int_t i=0; i<4; ++i) fColor[i] = rgba[i]/255.0f; Modified(); } //////////////////////////////////////////////////////////////////////////////// /// Set color from standard ROOT representation, that is color index /// + transparency in range [0, 100]. void TGLPhysicalShape::SetDiffuseColor(Color_t ci, UChar_t transparency) { if (ci < 0) ci = 1; TColor* c = gROOT->GetColor(ci); if (c) { fColor[0] = c->GetRed(); fColor[1] = c->GetGreen(); fColor[2] = c->GetBlue(); fColor[3] = 1.0f - 0.01*transparency; } Modified(); } //////////////////////////////////////////////////////////////////////////////// /// Setup colors - avoid setting things not required /// for current draw flags. void TGLPhysicalShape::SetupGLColors(TGLRnrCtx & rnrCtx, const Float_t* color) const { if (color == 0) color = fColor; switch (rnrCtx.DrawPass()) { case TGLRnrCtx::kPassWireFrame: { // Wireframe needs basic color only glColor4fv(color); break; } case TGLRnrCtx::kPassFill: case TGLRnrCtx::kPassOutlineFill: { // Both need material colors // Set back diffuse only for clipping where inner (back) faces // are shown. Don't set shininess or specular as we want // back face to appear as 'flat' as possible as crude visual // approximation to proper capped clipped solid glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color); glMaterialfv(GL_FRONT, GL_AMBIENT, color + 4); glMaterialfv(GL_FRONT, GL_SPECULAR, color + 8); glMaterialfv(GL_FRONT, GL_EMISSION, color + 12); glMaterialf(GL_FRONT, GL_SHININESS, color[16]); // Some objects use point/line graphics. Material mode disabled. glColor4fv(color); break; } case TGLRnrCtx::kPassOutlineLine: { // Outline also needs grey wireframe but respecting // transparency of main diffuse color. TGLUtil::ColorAlpha(rnrCtx.ColorSet().Outline(), 0.5f*color[3]); break; } default: { assert(kFALSE); } } } //////////////////////////////////////////////////////////////////////////////// /// Draw physical shape, using LOD flags, potential from display list cache void TGLPhysicalShape::Draw(TGLRnrCtx & rnrCtx) const { // Debug tracing if (gDebug > 4) { Info("TGLPhysicalShape::Draw", "this %ld (class %s) LOD %d", (Long_t)this, IsA()->GetName(), rnrCtx.ShapeLOD()); } // If LOD is pixel or less can draw pixel(point) directly, skipping // any logical call, caching etc. if (rnrCtx.ShapeLOD() == TGLRnrCtx::kLODPixel) { if (!rnrCtx.IsDrawPassOutlineLine()) { glColor4fv(fColor); glBegin(GL_POINTS); glVertex3dv(&fTransform.CArr()[12]); glEnd(); } return; } if (gDebug > 4) { Info("TGLPhysicalShape::Draw", "this %ld (class %s) LOD %d", (Long_t)this, IsA()->GetName(), rnrCtx.ShapeLOD()); } glPushMatrix(); glMultMatrixd(fTransform.CArr()); if (fIsScaleForRnr) glEnable(GL_NORMALIZE); if (fInvertedWind) glFrontFace(GL_CW); if (rnrCtx.Highlight()) { glPushAttrib(GL_LIGHTING_BIT | GL_DEPTH_BUFFER_BIT); glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); if (rnrCtx.HighlightOutline()) { static const Int_t offsets[20][2] = { {-1,-1}, { 1,-1}, { 1, 1}, {-1, 1}, { 1, 0}, { 0, 1}, {-1, 0}, { 0,-1}, { 0,-2}, { 2, 0}, { 0, 2}, {-2, 0}, {-2,-2}, { 2,-2}, { 2, 2}, {-2, 2}, { 0,-3}, { 3, 0}, { 0, 3}, {-3, 0} }; static const Int_t max_off = TGLUtil::GetScreenScalingFactor() > 1.5 ? 20 : 12; const TGLRect& vp = rnrCtx.RefCamera().RefViewport(); for (int i = 0; i < max_off; ++i) { glViewport(vp.X() + offsets[i][0], vp.Y() + offsets[i][1], vp.Width(), vp.Height()); fLogicalShape->DrawHighlight(rnrCtx, this); } glViewport(vp.X(), vp.Y(), vp.Width(), vp.Height()); } else { fLogicalShape->DrawHighlight(rnrCtx, this); } glPopAttrib(); } else { SetupGLColors(rnrCtx); if (rnrCtx.IsDrawPassOutlineLine()) TGLUtil::LockColor(); fLogicalShape->Draw(rnrCtx); if (rnrCtx.IsDrawPassOutlineLine()) TGLUtil::UnlockColor(); } if (fInvertedWind) glFrontFace(GL_CCW); if (fIsScaleForRnr) glDisable(GL_NORMALIZE); glPopMatrix(); } //////////////////////////////////////////////////////////////////////////////// /// Calculate shape-lod, suitable for use under /// projection defined by 'rnrCtx', taking account of which local /// axes of the shape support LOD adjustment, and the global /// 'sceneFlags' passed. /// /// Returned shapeLOD component is from 0 (kLODPixel - lowest /// quality) to 100 (kLODHigh - highest quality). /// /// Scene flags are not used. LOD quantization is not done. RnrCtx /// is not modified as this is called via lodification stage of /// rendering. void TGLPhysicalShape::CalculateShapeLOD(TGLRnrCtx& rnrCtx, Float_t& pixSize, Short_t& shapeLOD) const { TGLLogicalShape::ELODAxes lodAxes = fLogicalShape->SupportedLODAxes(); if (lodAxes == TGLLogicalShape::kLODAxesNone) { // Shape doesn't support LOD along any axes return special // unsupported LOD draw/cache flag. // TODO: Still ... could check for kLODPixel when very small, // by using diagonal from bounding-box and some special camera foo. pixSize = 100; // Make up something / irrelevant. shapeLOD = TGLRnrCtx::kLODHigh; return; } std::vector boxViewportDiags; const TGLBoundingBox & box = BoundingBox(); const TGLCamera & camera = rnrCtx.RefCamera(); if (lodAxes == TGLLogicalShape::kLODAxesAll) { // Shape supports LOD along all axes - basis LOD hint on diagonal of viewport // projection rect round whole bounding box boxViewportDiags.push_back(camera.ViewportRect(box).Diagonal()); } else if (lodAxes == (TGLLogicalShape::kLODAxesY | TGLLogicalShape::kLODAxesZ)) { // Shape supports LOD along Y/Z axes (not X). LOD hint based on longest // diagonal (largest rect) of either of the X axis end faces boxViewportDiags.push_back(camera.ViewportRect(box, TGLBoundingBox::kFaceLowX).Diagonal()); boxViewportDiags.push_back(camera.ViewportRect(box, TGLBoundingBox::kFaceHighX).Diagonal()); } else if (lodAxes == (TGLLogicalShape::kLODAxesX | TGLLogicalShape::kLODAxesZ)) { // Shape supports LOD along X/Z axes (not Y). See above for Y/Z boxViewportDiags.push_back(camera.ViewportRect(box, TGLBoundingBox::kFaceLowY).Diagonal()); boxViewportDiags.push_back(camera.ViewportRect(box, TGLBoundingBox::kFaceHighY).Diagonal()); } else if (lodAxes == (TGLLogicalShape::kLODAxesX | TGLLogicalShape::kLODAxesY)) { // Shape supports LOD along X/Y axes (not Z). See above for Y/Z boxViewportDiags.push_back(camera.ViewportRect(box, TGLBoundingBox::kFaceLowZ).Diagonal()); boxViewportDiags.push_back(camera.ViewportRect(box, TGLBoundingBox::kFaceHighZ).Diagonal()); } else { // Don't bother to implement LOD calc for shapes supporting LOD along single // axis only. Not needed at present + unlikely case - but could be done based // on longest of projection of 4 edges of BBox along LOD axis. However this would // probably be more costly than just using whole BB projection (as for all axes) Error("TGLPhysicalShape::CalcPhysicalLOD", "LOD calculation for single axis not implemented presently"); shapeLOD = TGLRnrCtx::kLODMed; return; } // Find largest of the projected diagonals Double_t largestDiagonal = 0.0; for (UInt_t i = 0; i < boxViewportDiags.size(); i++) { if (boxViewportDiags[i] > largestDiagonal) { largestDiagonal = boxViewportDiags[i]; } } pixSize = largestDiagonal; if (largestDiagonal <= 1.0) { shapeLOD = TGLRnrCtx::kLODPixel; } else { // TODO: Get real screen size - assuming 2000 pixel screen at present // Calculate a non-linear sizing hint for this shape based on diagonal. // Needs more experimenting with... UInt_t lodApp = static_cast(std::pow(largestDiagonal,0.4) * 100.0 / std::pow(2000.0,0.4)); if (lodApp > 1000) lodApp = 1000; shapeLOD = (Short_t) lodApp; } } //////////////////////////////////////////////////////////////////////////////// /// Factor in scene/vierer LOD and Quantize ... forward to /// logical shape. void TGLPhysicalShape::QuantizeShapeLOD(Short_t shapeLOD, Short_t combiLOD, Short_t& quantLOD) const { quantLOD = fLogicalShape->QuantizeShapeLOD(shapeLOD, combiLOD); } //////////////////////////////////////////////////////////////////////////////// /// Request creation of context menu on shape, attached to 'menu' at screen position /// 'x' 'y' void TGLPhysicalShape::InvokeContextMenu(TContextMenu & menu, UInt_t x, UInt_t y) const { // Just defer to our logical at present fLogicalShape->InvokeContextMenu(menu, x, y); }