/*
 *
 *  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   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:
 |	SoDirectionalLightDragger
 |
 |
 | Description:
 |  This is the source file for the SoDirectionalLightDragger.
 |  This is a composite dragger which allows independent rotation,
 |  and translation for a directional light.
 |  When working on a directional light, the translation serves only
 |  to position the dragger in space, but has no effect on the lights
 |  direction.
 |
 |  It is composed of an SoRotateSphericalDragger (for rotation),
 |  and an SoDragPointDragger (for translation),
 |
 |   Author(s): Paul Isaacs, David Mott
 |
 ______________  S I L I C O N   G R A P H I C S   I N C .  ____________
 _______________________________________________________________________
 */


#include <cstdio>
#include <Inventor/SbLinear.h>
#include <Inventor/SoDB.h>
#include <Inventor/SoPath.h>
#include <Inventor/sensors/SoFieldSensor.h>

#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoRotation.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/draggers/SoDragPointDragger.h>
#include <Inventor/draggers/SoRotateSphericalDragger.h>
#include <Inventor/draggers/SoDirectionalLightDragger.h>
#include <Inventor/projectors/SbSphereSectionProjector.h>

#include "geom/SoDirectionalLightDraggerGeom.h"


SO_KIT_SOURCE(SoDirectionalLightDragger);

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Constructor
//
SoDirectionalLightDragger::SoDirectionalLightDragger()
//
////////////////////////////////////////////////////////////////////////
{
    SO_KIT_CONSTRUCTOR(SoDirectionalLightDragger);

    isBuiltIn = TRUE;

    // This gives the dragger an overall material.  It is edited by lightManips
    // to make its dragger match the color of the light.  Any materials within 
    // other parts will override this one. 
    SO_KIT_ADD_CATALOG_ENTRY(material, SoMaterial, 
				TRUE, topSeparator, geomSeparator,TRUE);

    // The translator is kept under a separator along with a
    // rotation that is maintained as the inverse to the rotation of the
    // light. This means that using the rotator does not rotate the
    // coordinate system that we translate the base of the dragger in.
    SO_KIT_ADD_CATALOG_ENTRY(translatorSep, SoSeparator, 
				TRUE, topSeparator, geomSeparator,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translatorRotInv, SoRotation, 
				TRUE, translatorSep, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(translator, SoDragPointDragger, 
				TRUE, translatorSep, ,TRUE);
    SO_KIT_ADD_CATALOG_ENTRY(rotator, SoRotateSphericalDragger, 
				TRUE, topSeparator, geomSeparator,TRUE);

    // read geometry for shared parts
    if (SO_KIT_IS_FIRST_INSTANCE())
	readDefaultParts("directionalLightDragger.iv", 
			  geomBuffer, sizeof(geomBuffer) );

    SO_KIT_ADD_FIELD(translation, (0.0, 0.0, 0.0));
    SO_KIT_ADD_FIELD(rotation, (0.0, 0.0, 0.0, 1.0));

    SO_KIT_INIT_INSTANCE();

    // Set the overall material.
    // We need to use a copy of the resource, since the resource is shared
    // by all manips but we are going to edit ours.
    SoNode *resourceMtl 
	= SoNode::getByName("directionalLightOverallMaterial");
    setPartAsDefault("material", resourceMtl->copy() );

    // CREATE THE CHILD DRAGGERS:
    // 1 DragpointDragger
    // 1 RotateSphericalDragger

	SoDragPointDragger *trD;
	trD = SO_GET_ANY_PART( this,"translator", SoDragPointDragger );

	SoRotateSphericalDragger *roD;
	roD = SO_GET_ANY_PART( this, "rotator", SoRotateSphericalDragger );

    // Update the rotation and translation fields when the motionMatrix is set.
    addValueChangedCallback( &SoDirectionalLightDragger::valueChangedCB );

    // Updates the motionMatrix when the translationFactor field is set.
    translFieldSensor 
     = new SoFieldSensor( &SoDirectionalLightDragger::fieldSensorCB, this);
    translFieldSensor->setPriority( 0 );

    // Updates the motionMatrix when the rotation field is set.
    rotFieldSensor 
     = new SoFieldSensor( &SoDirectionalLightDragger::fieldSensorCB, this);
    rotFieldSensor->setPriority( 0 );

    setUpConnections( TRUE, TRUE );
}

////////////////////////////////////////////////////////////////////////
//
// Description:
//    Destructor
//
SoDirectionalLightDragger::~SoDirectionalLightDragger()
//
////////////////////////////////////////////////////////////////////////
{
    if (translFieldSensor )
	delete translFieldSensor;
    if (rotFieldSensor )
	delete rotFieldSensor;
}

//    detach/attach any sensors, callbacks, and/or field connections.
//    Called by:            start/end of SoBaseKit::readInstance
//    and on new copy by:   start/end of SoBaseKit::copy.
//    Classes that redefine must call setUpConnections(TRUE,TRUE) 
//    at end of constructor.
//    Returns the state of the node when this was called.
SbBool
SoDirectionalLightDragger::setUpConnections( SbBool onOff, SbBool doItAlways )
{
    if ( !doItAlways && connectionsSetUp == onOff)
	return onOff;

    if ( onOff ) {

	// We connect AFTER base class.
	SoDragger::setUpConnections( onOff, FALSE );

	// Set default parts and add callbacks to child draggers.
	SoDragger *trD = (SoDragger *) getAnyPart( "translator", FALSE );
	if (trD != NULL) {

	    // Set up the parts in the child dragger
	    SoNode *n;
	    n = SoNode::getByName("directionalLightTranslatorLineTranslator");
	    trD->setPartAsDefault("xTranslator.translator", n );
	    trD->setPartAsDefault("yTranslator.translator", n );
	    trD->setPartAsDefault("zTranslator.translator", n );

	    n = SoNode::getByName(
			    "directionalLightTranslatorLineTranslatorActive");
	    trD->setPartAsDefault("xTranslator.translatorActive", n );
	    trD->setPartAsDefault("yTranslator.translatorActive", n );
	    trD->setPartAsDefault("zTranslator.translatorActive", n );

	    n = SoNode::getByName("directionalLightTranslatorPlaneTranslator");
	    trD->setPartAsDefault("yzTranslator.translator", n );
	    trD->setPartAsDefault("xzTranslator.translator", n );
	    trD->setPartAsDefault("xyTranslator.translator", n );

	    n = SoNode::getByName(
			    "directionalLightTranslatorPlaneTranslatorActive");
	    trD->setPartAsDefault("yzTranslator.translatorActive", n );
	    trD->setPartAsDefault("xzTranslator.translatorActive", n );
	    trD->setPartAsDefault("xyTranslator.translatorActive", n );

	    registerChildDragger( trD );
	}

	SoRotateSphericalDragger *roD 
	    = (SoRotateSphericalDragger *) getAnyPart( "rotator", FALSE );
	if (roD != NULL) {

	    // Give it a projector that moves freely in the radial direction.
	    // We need this because our geometry is just a little stick, not a 
	    // big ol' ball
	    SbSphereSectionProjector *ssp = new SbSphereSectionProjector();
	    ssp->setRadialFactor( 1.0 );
	    roD->setProjector(ssp);

	    // Set up the parts in the child dragger
	    roD->setPartAsDefault("rotator", 
			    "directionalLightRotatorRotator");
	    roD->setPartAsDefault("rotatorActive", 
			    "directionalLightRotatorRotatorActive");
	    roD->setPartAsDefault("feedback", 
			    "directionalLightRotatorFeedback" );
	    roD->setPartAsDefault("feedbackActive", 
			  "directionalLightRotatorFeedbackActive");

	    registerChildDragger( roD );

	}

	// Call the sensor CBs to make things are up-to-date.
	fieldSensorCB( this, NULL );

	// Connect the field sensors
	if (rotFieldSensor->getAttachedField() != &rotation)
	    rotFieldSensor->attach( &rotation );
	if (translFieldSensor->getAttachedField() != &translation)
	    translFieldSensor->attach( &translation );
    }
    else {

	// We disconnect BEFORE base class.

	// remove callbacks from the child draggers
        SoDragger *trD = (SoDragger *) getAnyPart( "translator", FALSE );
	if (trD)
        unregisterChildDragger( trD );

        SoDragger *roD = (SoDragger *) getAnyPart( "rotator", FALSE );
	if (roD)
        unregisterChildDragger( roD );


	// Disconnect the field sensors.
	if (rotFieldSensor->getAttachedField())
	    rotFieldSensor->detach();
	if (translFieldSensor->getAttachedField())
	    translFieldSensor->detach();

	SoDragger::setUpConnections( onOff, FALSE );
    }

    return !(connectionsSetUp = onOff);
}


void
SoDirectionalLightDragger::valueChangedCB( void *, SoDragger *inDragger )
{
    SoDirectionalLightDragger *d = (SoDirectionalLightDragger *) inDragger;
    SbMatrix motMat = d->getMotionMatrix();

    SbVec3f    trans, scale;
    SbRotation rot, scaleOrient;
    motMat.getTransform( trans, rot, scale, scaleOrient );

    // Disconnect the field sensors
    d->translFieldSensor->detach();
    d->rotFieldSensor->detach();

    if ( d->translation.getValue() != trans )
	d->translation = trans;
    if ( d->rotation.getValue() != rot )
	d->rotation = rot;

    // Make the rotation inside the "translaterRotInv" be the inverse 
    // of the new rotation.
    SbRotation newInv = rot.inverse();
    SoRotation *rotInv = (SoRotation *) d->getAnyPart("translatorRotInv",TRUE);
    if (rotInv->rotation.getValue() != newInv)
	rotInv->rotation = newInv;

    // Reconnect the field sensors
    d->translFieldSensor->attach( &(d->translation) );
    d->rotFieldSensor->attach( &(d->rotation) );
}

void
SoDirectionalLightDragger::fieldSensorCB(void *inDragger, SoSensor *)
{
   SoDirectionalLightDragger *dragger = (SoDirectionalLightDragger *) inDragger;

    SbMatrix motMat = dragger->getMotionMatrix();
    dragger->workFieldsIntoTransform(motMat);

    dragger->setMotionMatrix(motMat);
}

void
SoDirectionalLightDragger::setDefaultOnNonWritingFields()
{
    // This node may change after construction, but we still
    // don't want to write it out.
    translatorRotInv.setDefault(TRUE);

    // Try not to write out the sub-draggers.
	translator.setDefault(TRUE);
	rotator.setDefault(TRUE);

    // Call the base class...
    SoDragger::setDefaultOnNonWritingFields();
}
