/*
 *
 *  Copyright (C) 2000 Silicon Graphics, Inc.  All Rights Reserved. 
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  Further, this software is distributed without any warranty that it is
 *  free of the rightful claim of any third person regarding infringement
 *  or the like.  Any license provided herein, whether implied or
 *  otherwise, applies only to this software file.  Patent licenses, if
 *  any, provided herein do not apply to combinations of this program with
 *  other software, or any other product whatsoever.
 * 
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 *  Mountain View, CA  94043, or:
 * 
 *  http://www.sgi.com 
 * 
 *  For further information regarding this notice, see: 
 * 
 *  http://oss.sgi.com/projects/GenInfo/NoticeExplan/
 *
 */

/*
 * Copyright (C) 1990,91,92   Silicon Graphics, Inc.
 *
 _______________________________________________________________________
 ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
 |
 |   $Revision: 1.1.1.1 $
 |
 |   Classes:
 |	SoSceneManager
 |
 |   Author(s): David Mott, Alain Dumesny
 |
 ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
 _______________________________________________________________________
 */

#include <Inventor/SoDB.h>
#include <Inventor/SbTime.h>
#include <Inventor/SbLinear.h>
#include <Inventor/SbViewportRegion.h>
#include <Inventor/errors/SoDebugError.h>
#include <Inventor/nodes/SoNode.h>
#include <Inventor/SoSceneManager.h>
#include <Inventor/fields/SoSFTime.h>
#include <Inventor/sensors/SoSensor.h>
#include <Inventor/sensors/SoNodeSensor.h>
#include <Inventor/sensors/SoOneShotSensor.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/actions/SoHandleEventAction.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>

#include <SoDebug.h>

// Sensor which touches realTime as soon as possible after a redraw
SoSFTime	*SoSceneManager::realTime = NULL;
SoOneShotSensor	*SoSceneManager::realTimeSensor = NULL;
SbBool		SoSceneManager::updateRealTime = TRUE;


////////////////////////////////////////////////////////////////////////
//
// Constructor 
//
SoSceneManager::SoSceneManager()
//
////////////////////////////////////////////////////////////////////////
{
    bkgColor.setValue(0,0,0);
    bkgIndex = 0;
    rgbMode = TRUE;
    graphicsInitNeeded = TRUE;
    
    // inventor specific variables
    scene   	    = NULL;
    raCreatedHere   = TRUE;
    renderAction    = new SoGLRenderAction(SbVec2s(1,1));
    heaCreatedHere  = TRUE;
    handleEventAction = new SoHandleEventAction(SbVec2s(1,1));
    renderCB	    = NULL;
    renderCBData    = NULL;
    active	    = FALSE;
    needToSendVP    = TRUE;
    
    // create the scene sensor (used for automatic rendering)
    // do not attach the sensor here. That's done when we
    // become active, and the user supplies a redraw callback.
    sceneSensor = new SoNodeSensor;
    sceneSensor->setData((void *) this);
#ifdef DEBUG
    if (SoDebug::GetEnv("IV_DEBUG_SENSORS")) {
	SoDebug::NamePtr("sceneChangeSensor", sceneSensor);
    }
#endif

    // Assume default priority now. We'll set the sensor priority
    // before we attach, just in case the user changes the priority
    // before that occurs.
    setRedrawPriority(getDefaultRedrawPriority());
    
    // setup the sensor to touch real time after a redraw
    if (! realTimeSensor) {
	realTime = (SoSFTime *) SoDB::getGlobalField("realTime");
	realTimeSensor = new SoOneShotSensor;
	realTimeSensor->setFunction((SoSensorCB *)
				 &SoSceneManager::realTimeSensorCB);
#ifdef DEBUG
	if (SoDebug::GetEnv("IV_DEBUG_SENSORS")) {
	    SoDebug::NamePtr("realTimeSensor", realTimeSensor);
	}
#endif
    }
}

////////////////////////////////////////////////////////////////////////
//
// Destructor
//
SoSceneManager::~SoSceneManager()
//
////////////////////////////////////////////////////////////////////////
{
    // delete actions
    if (raCreatedHere)
	delete renderAction;
	
    if (heaCreatedHere)
	delete handleEventAction;
    
    // detach the scene
    setSceneGraph(NULL);
    delete sceneSensor;
}

////////////////////////////////////////////////////////////////////////
//
// Set a new render action, for instance for highlighting.
//
// public
//
void
SoSceneManager::setGLRenderAction(SoGLRenderAction *ra)
//
////////////////////////////////////////////////////////////////////////
{ 
#ifdef DEBUG
    if (ra == NULL)
	SoDebugError::post("SoSceneManager::setGLRenderAction",
			"a NULL render action was passed. This is bad!");
#endif

    // Make sure the viewport region is set
    // (be paranoid and check for NULL)
    if (renderAction != NULL) {
	SbViewportRegion rgn(renderAction->getViewportRegion());
	ra->setViewportRegion(rgn);
    }
    
    // Get rid of the old render action if it was created here.
    if (raCreatedHere) {
	delete renderAction;
	raCreatedHere = FALSE;
    }
    
    // Set to the new render action.
    renderAction = ra;
}

////////////////////////////////////////////////////////////////////////
//
// Set a new handle event action. Passing NULL will shut off event
// handling.
//
// public
//
void
SoSceneManager::setHandleEventAction(SoHandleEventAction *hea)
//
////////////////////////////////////////////////////////////////////////
{
    // Make sure the viewport region is set
    if ((handleEventAction != NULL) && (hea != NULL)) {
	SbViewportRegion rgn(handleEventAction->getViewportRegion());
	hea->setViewportRegion(rgn);
    }
    
    // Get rid of the old render action if it was created here.
    if (heaCreatedHere) {
	delete handleEventAction;
	heaCreatedHere = FALSE;
    }
    
    // Set to the new render action.
    handleEventAction = hea;
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//  	Make the new user supplied scene graph the rendering root.
//
// use: virtual public
//
void
SoSceneManager::setSceneGraph(SoNode *newScene)
//
////////////////////////////////////////////////////////////////////////
{
    SbBool currentlyActive = isActive();
    deactivate();
    
    // ref the new scene
    if (newScene != NULL)
	newScene->ref();

    // check if there already is a scene graph
    if (scene != NULL)
	scene->unref();

    // now set the new scene graph
    scene = newScene;
    
    if (currentlyActive)
	activate();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//  	Return the user supplied scene graph.
//
// use: virtual public
//
SoNode *
SoSceneManager::getSceneGraph() const
//
////////////////////////////////////////////////////////////////////////
{ return scene; }
	    

////////////////////////////////////////////////////////////////////////
//
// Description:
//  	Process the passed X event.
//
// use: virtual protected
//
SbBool
SoSceneManager::processEvent(const SoEvent *event)
//
////////////////////////////////////////////////////////////////////////
{
    if ((scene != NULL) && (handleEventAction != NULL)) {    
	handleEventAction->setEvent(event);
	handleEventAction->apply(scene);
	
	return handleEventAction->isHandled();
    }
    else
	return FALSE;
}

////////////////////////////////////////////////////////////////////////
//
// Initialize the window for rendering.
// A window MUST be set before this is called.
//
// use: public
//
void
SoSceneManager::reinitialize()
//
////////////////////////////////////////////////////////////////////////
{
    graphicsInitNeeded = TRUE;
    
    // we have a new window, so re-init the render action
    renderAction->invalidateState();
}


////////////////////////////////////////////////////////////////////////
//
//  Set the size of the window.
//
//  use: public
//
void
SoSceneManager::setWindowSize(const SbVec2s &newSize)
//
////////////////////////////////////////////////////////////////////////
{
    SbViewportRegion rgn(renderAction->getViewportRegion());
    rgn.setWindowSize(newSize);
    
    if (renderAction != NULL)
	renderAction->setViewportRegion(rgn);
	
    if (handleEventAction != NULL)
	handleEventAction->setViewportRegion(rgn);
    
    // make sure to call glViewport() with the new size
    needToSendVP = TRUE;
}

////////////////////////////////////////////////////////////////////////
//
//  Set the size of the window.
//
//  use: public
//
const SbVec2s &
SoSceneManager::getWindowSize() const
//
////////////////////////////////////////////////////////////////////////
{
    return renderAction->getViewportRegion().getWindowSize();
}

////////////////////////////////////////////////////////////////////////
//
//  Set the size of the viewport within the window.
//
//  use: public
//
void
SoSceneManager::setSize(const SbVec2s &newSize)
//
////////////////////////////////////////////////////////////////////////
{
    SbViewportRegion rgn(renderAction->getViewportRegion());
    const SbVec2s &origin = rgn.getViewportOriginPixels();
    rgn.setViewportPixels(origin, newSize);
           
    if (renderAction != NULL)
	renderAction->setViewportRegion(rgn);
    
    if (handleEventAction != NULL)
	handleEventAction->setViewportRegion(rgn);
}

////////////////////////////////////////////////////////////////////////
//
//  Set origin relative to the window.
//
//  use: public
//
void
SoSceneManager::setOrigin(const SbVec2s &newOrigin)
//
////////////////////////////////////////////////////////////////////////
{
    SbViewportRegion rgn(renderAction->getViewportRegion());
    const SbVec2s &size = rgn.getViewportSizePixels();
    rgn.setViewportPixels(newOrigin, size);
           
    if (renderAction != NULL)
	renderAction->setViewportRegion(rgn);
    
    if (handleEventAction != NULL)
	handleEventAction->setViewportRegion(rgn);
}

////////////////////////////////////////////////////////////////////////
//
//  Get viewport size.
//
//  use: public
//
const SbVec2s &
SoSceneManager::getSize() const
//
////////////////////////////////////////////////////////////////////////
{
    return renderAction->getViewportRegion().getViewportSizePixels();
}

////////////////////////////////////////////////////////////////////////
//
//  Get origin relative to the window.
//
//  use: public
//
const SbVec2s &
SoSceneManager::getOrigin() const
//
////////////////////////////////////////////////////////////////////////
{
    return renderAction->getViewportRegion().getViewportOriginPixels();
}

////////////////////////////////////////////////////////////////////////
//
//  Set the viewport region instead of setting size and origin separately.
//
//  use: public
//
void
SoSceneManager::setViewportRegion(const SbViewportRegion &rgn)
//
////////////////////////////////////////////////////////////////////////
{
    if (renderAction != NULL)
	renderAction->setViewportRegion(rgn);
    
    if (handleEventAction != NULL)
	handleEventAction->setViewportRegion(rgn);
}

////////////////////////////////////////////////////////////////////////
//
//  Get the viewport region.
//
//  use: public
//
const SbViewportRegion &
SoSceneManager::getViewportRegion() const
//
////////////////////////////////////////////////////////////////////////
{
    return renderAction->getViewportRegion();
}

////////////////////////////////////////////////////////////////////////
//
//  Set to RGB mode or color map mode.
//
//  use: public
//
void
SoSceneManager::setRGBMode(SbBool onOrOff)
//
////////////////////////////////////////////////////////////////////////
{
    rgbMode = onOrOff;

    // the render action determines color map/rgb at initialization
//???pauli    renderAction->reinitialize();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Sets antialiasing on GL rendering action.
//
// use: public
//
void
SoSceneManager::setAntialiasing(SbBool smoothing, int numPasses)
//
////////////////////////////////////////////////////////////////////////
{
    renderAction->setSmoothing( smoothing );
    renderAction->setNumPasses( numPasses );
    
    // Set render action callback for multiple pass rendering.  This
    // makes sure to clear background to correct color between frames.
    if ( numPasses > 1 )
	renderAction->setPassCallback(antialiasingCallback, this);
    else
	renderAction->setPassCallback(NULL, NULL);
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Gets antialiasing on GL rendering action.
//
// use: public
//
void
SoSceneManager::getAntialiasing(SbBool &smoothing, int &numPasses) const
//
////////////////////////////////////////////////////////////////////////
{
    smoothing = renderAction->isSmoothing();
    numPasses = renderAction->getNumPasses();
}

////////////////////////////////////////////////////////////////////////
//
// This routine is called to render the scene graph.
// A window MUST be set before this is called.
//
// use: public
//
void
SoSceneManager::render(SbBool clearWindow, SbBool clearZbuffer)
//
////////////////////////////////////////////////////////////////////////
{
    // reinitialize if necessary
    if (graphicsInitNeeded) {
	int numBits[1];
	glGetIntegerv(GL_DEPTH_BITS, numBits);
	needZbuffer = (numBits[0] != 0); // FALSE for overlay windows !
	if (needZbuffer)
	    glDepthFunc(GL_LEQUAL); // needed for hidden line rendering
	graphicsInitNeeded = FALSE;
    }
    
    //
    // when the window changes size, we need to call glViewport() before
    // we can do a color clear.
    //
    if (needToSendVP) {
	const SbViewportRegion &theRegion = renderAction->getViewportRegion();
	SbVec2s size   = theRegion.getViewportSizePixels();
	SbVec2s origin = theRegion.getViewportOriginPixels();
	glViewport(origin[0], origin[1], size[0], size[1]);
	needToSendVP = FALSE;
    }
    
    //
    // clear to the background color and clear the zbuffer
    //
    if (clearWindow) {
	if (rgbMode)
	     glClearColor(bkgColor[0], bkgColor[1], bkgColor[2], 0);
	else glClearIndex(bkgIndex);
	
	// clear the color+zbuffer at the same time if we can
	if (needZbuffer && clearZbuffer)
	    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	else
	    glClear(GL_COLOR_BUFFER_BIT);
    }
    // check to see if only the zbuffer is needed...
    else if (needZbuffer && clearZbuffer)
	glClear(GL_DEPTH_BUFFER_BIT);
    
    // render the scene graph!
    if (scene != NULL)
    	renderAction->apply(scene);
    
    // sensor doesn't need to fire again if it's still scheduled
    sceneSensor->unschedule();
    
    // schedule the realTime One shot sensor to update the real time
    // as soon as we can now that we have rendered the scene. This will
    // enable us to render things that are animating with a consistent
    // time across mutiple renderAreas, while providing the maximum
    // optainable frame rate (much better than a hard coded 30 or 60 
    // times/sec timer sensor).
    if (updateRealTime)
	realTimeSensor->schedule();
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Callback to update the realTime global field.
//
// Use: static private

void
SoSceneManager::realTimeSensorCB()
//
////////////////////////////////////////////////////////////////////////
{
    realTime->setValue(SbTime::getTimeOfDay());
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//	Enables/Disable the realTime update after each redraw
//
// Use: static public

void
SoSceneManager::enableRealTimeUpdate(SbBool flag)
//
////////////////////////////////////////////////////////////////////////
{
    if (updateRealTime == flag)
	return;
    
    updateRealTime = flag;
    
    if (updateRealTime)
	realTimeSensor->schedule();
    else
	realTimeSensor->unschedule();
}

////////////////////////////////////////////////////////////////////////
//
//  The scene manager will only employ sensors while it is active.
//
//  use: public, virtual
//
void
SoSceneManager::activate()
{
    // attach sceneSensor to top node for redrawing purpose
    // only if the user has specified a redraw callback (i.e.
    // auto-redraw is enabled)
    if (renderCB != NULL) {
	if (scene != NULL && sceneSensor->getAttachedNode() == NULL) {
	    sceneSensor->setFunction((SoSensorCB *)
			 &SoSceneManager::sceneSensorCallback);
	    sceneSensor->attach(scene);
	}
    }
    active = TRUE;
}
void
SoSceneManager::deactivate()
{
    sceneSensor->detach();
    sceneSensor->setFunction(NULL);
    active = FALSE;
}

////////////////////////////////////////////////////////////////////////
//
//  Set the rendering callback. The app can supply this to enable
//  automatic redrawing of the scene. This callback should perform
//  window specific graphics initialization, then call the scene manager
//  render() method.
//
//  use: public
//
void
SoSceneManager::setRenderCallback(
    SoSceneManagerRenderCB *f,
    void *userData)
//
////////////////////////////////////////////////////////////////////////
{
    renderCB = f;
    renderCBData = userData;
    
    if (f != NULL) {
	// if we are active, attach the scene sensor
	if (active) {
	    if (scene != NULL && sceneSensor->getAttachedNode() == NULL) {
		sceneSensor->setFunction((SoSensorCB *)
			     &SoSceneManager::sceneSensorCallback);
		sceneSensor->attach(scene);
	    }
	}
    }
    else {
	// detach the scene sensor (whether active or not)
	sceneSensor->detach();
	sceneSensor->setFunction(NULL);
    }
}

////////////////////////////////////////////////////////////////////////
//
// Priority of the redraw sensor
//
// public 
//
void
SoSceneManager::setRedrawPriority(uint32_t priority)
//
////////////////////////////////////////////////////////////////////////
{
    sceneSensor->setPriority(priority);
}

////////////////////////////////////////////////////////////////////////
//
// Priority of the redraw sensor
//
// public 
//
uint32_t
SoSceneManager::getRedrawPriority() const
//
////////////////////////////////////////////////////////////////////////
{
    return sceneSensor->getPriority();
}


//
////////////////////////////////////////////////////////////////////////
// static callbacks stubs
////////////////////////////////////////////////////////////////////////
//

void
SoSceneManager::antialiasingCallback( void *r )
{
    SoSceneManager *mgr = (SoSceneManager *) r;
    
    if (mgr->rgbMode)
	 glClearColor(mgr->bkgColor[0], mgr->bkgColor[1], mgr->bkgColor[2], 0);
    else glClearIndex( mgr->bkgIndex );
    
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}

void
SoSceneManager::sceneSensorCallback(SoSceneManager *mgr, SoSensor *)
{
#ifdef DEBUG
    if (mgr->renderCB == NULL) {
	SoDebugError::post("SoSceneManager::sceneSensorCallback",
	"Callback was fired,  but auto-redraw is turned off.");
	return;
    }
    if (! mgr->isActive()) {
	SoDebugError::post("SoSceneManager::sceneSensorCallback",
	"Callback was fired,  but scene manager not active.");
	return;
    }
#endif

    // redraw that scene graph!
    mgr->redraw(); 
}

//
////////////////////////////////////////////////////////////////////////
// real short methods
////////////////////////////////////////////////////////////////////////
//

void
SoSceneManager::redraw()
{
    if (renderCB != NULL)
	(*renderCB)(renderCBData, this);
}

void
SoSceneManager::scheduleRedraw()
{
    // The sceneSensor will only schedule itself if a CB func is set.
    // We only set the sensor CB func if autoRedraw is on and we are active.
    // Thus, there are no flags to check here. Just call schedule, and
    // the sensor will do the right thing.
    sceneSensor->schedule();
}

void
SoSceneManager::setBackgroundColor(const SbColor &c)
{
    bkgColor = c;
    if (isRGBMode())
	scheduleRedraw();
}

void
SoSceneManager::setBackgroundIndex(int index)
{
    bkgIndex = index;
    if (! isRGBMode())
	scheduleRedraw();
}
