/*
 * Copyright (C) 1997-2009, R3vis Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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, or visit http://www.gnu.org/copyleft/lgpl.html.
 *
 * Original Contributor:
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *   http://www.r3vis.com/
 * Additional Contributor(s):
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rmlites.c,v 1.8 2005/08/06 15:30:39 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.8 $
 * $Log: rmlites.c,v $
 * Revision 1.8  2005/08/06 15:30:39  wes
 * Reverted to RM_FALSE for localviewer in rmDefaultLighting. Turning off
 * localviewer results in about a 50% increase in frame rate in one of
 * our test cases.
 *
 * Revision 1.7  2005/06/26 18:51:23  wes
 * In rmDefaultLighting, changed default setting of localview to RM_TRUE.
 *
 * Revision 1.6  2005/02/19 16:22:50  wes
 * Distro sync and consolidation.
 *
 * Revision 1.5  2005/01/23 17:04:03  wes
 * Copyright update to 2005.
 *
 * Revision 1.4  2004/03/10 01:45:10  wes
 * Enabled use of global default spotlight exponent when a new light
 * is created with rmLightNew.
 *
 * Revision 1.3  2004/01/16 16:45:12  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.2  2003/02/02 02:07:15  wes
 * Updated copyright to 2003.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.6  2003/01/16 22:21:17  wes
 * Updated all source files to reflect new organization of header files:
 * all header files formerly located in include/rmaux, include/rmi, include/rmv
 * are now located in include/rm.
 *
 * Revision 1.5  2002/04/30 19:32:22  wes
 * Updated copyright dates.
 *
 * Revision 1.4  2001/05/26 14:35:10  wes
 * Doc mods.
 *
 * Revision 1.3  2001/03/31 17:12:38  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.2  2000/04/20 16:29:47  wes
 * Documentation additions/enhancements, some code rearragement.
 *
 * Revision 1.1.1.1  2000/02/28 21:29:40  wes
 * OpenRM 1.2 Checkin
 *
 * Revision 1.1.1.1  2000/02/28 17:18:48  wes
 * Initial entry - pre-RM120 release, source base for OpenRM 1.2.
 *
 */

#include <rm/rm.h>
#include "rmprivat.h"

/*
 * ----------------------------------------------------
 * @Name rmLightNew
 @pstart
 RMlight * rmLightNew (void)
 @pend

 @astart
 No arguments.
 @aend

 @dstart

 Creates a new RMlight object, returning a handle to the new object to
 the caller.

 The RMlight object is initialized with these values:

 1. Light type set to RM_LIGHT_DIRECTIONAL (see rmLightSetType).

 2. Light direction set to {0.0, 0.0, 1.0} (see rmLightSetXYZ).

 3. Light ambient, diffuse and specular colors set to {0, 0, 0},
 {0.75, 0.75, 0.75} and {1.0, 1.0, 1.0} respectively (see
 rmLightSetColor).

 4. Spotlight direction, and cutoff set to {0,0,-1} and 90 degrees,
 respectively (see rmLightSetSpotDirection and rmLightSetSpotCutoff).
 Note that these parameters have no meaning with a light source that
 is not a spotlight.

 5. Light attenuation coefficients consist of a constant, linear and
 quadratic terms. These are initialized to 1.0, 0.0 and 0.0,
 respectively (see rmLightSetAttenuation).

 6. The light is enabled (see rmLightSetEnable) with RM_TRUE.

 Notes:

 The process of lighting in RM consists of a number of interrelated
 factors. One is the presence of light sources in the scene as scene
 parameters assigned to RMnodes. Another is the specification of a
 light model, another scene parameter that specifies whether or not
 lighting is two-sided (illumination of both front- and back-facing
 polygons by ignoring the sign of the dot product of the surface
 normal with the direction to/of the light source). Third, objects to
 be illuminated must have 3D surface normals. The absence of any of
 these three components will cause lighting/shading to "not work."

 Note that setting attributes in an RMlight does not change them in
 the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLight).
 
 @dend
 * ----------------------------------------------------
 */
RMlight *
rmLightNew (void)
{
    extern float      RM_DEFAULT_LIGHT_SPOT_CUTOFF;
    extern float      RM_DEFAULT_LIGHT_CONSTANT_ATTENUATION;
    extern float      RM_DEFAULT_LIGHT_LINEAR_ATTENUATION;
    extern float      RM_DEFAULT_LIGHT_QUADRATIC_ATTENUATION;
    extern float      RM_DEFAULT_LIGHT_SPOT_EXPONENT; 
    extern RMenum     RM_DEFAULT_LIGHT_ENABLE;
    extern RMenum     RM_DEFAULT_LIGHT_TYPE;
    extern RMvertex3D RM_DEFAULT_LIGHT_XYZ;
    extern RMcolor4D  RM_DEFAULT_LIGHT_AMBIENT_COLOR;
    extern RMcolor4D  RM_DEFAULT_LIGHT_DIFFUSE_COLOR;
    extern RMcolor4D  RM_DEFAULT_LIGHT_SPECULAR_COLOR;
    extern RMvertex3D RM_DEFAULT_LIGHT_SPOT_DIRECTION;
    
    RMlight *t = (RMlight *)malloc(sizeof(RMlight));

    if (t == NULL)
	return(NULL);
    
    memset(t, 0, sizeof(RMlight));

    /* assign default values */
    rmLightSetType(t, RM_DEFAULT_LIGHT_TYPE);
    rmLightSetXYZ(t, &RM_DEFAULT_LIGHT_XYZ);
    rmLightSetColor(t, &RM_DEFAULT_LIGHT_AMBIENT_COLOR, &RM_DEFAULT_LIGHT_DIFFUSE_COLOR, &RM_DEFAULT_LIGHT_SPECULAR_COLOR);
    rmLightSetSpotExponent(t, RM_DEFAULT_LIGHT_SPOT_EXPONENT);
    rmLightSetSpotDirection(t, &RM_DEFAULT_LIGHT_SPOT_DIRECTION);
    rmLightSetSpotCutoff(t, RM_DEFAULT_LIGHT_SPOT_CUTOFF);
    rmLightSetAttenuation (t, RM_DEFAULT_LIGHT_CONSTANT_ATTENUATION, RM_DEFAULT_LIGHT_LINEAR_ATTENUATION, RM_DEFAULT_LIGHT_QUADRATIC_ATTENUATION);
    rmLightSetEnable (t, RM_DEFAULT_LIGHT_ENABLE);

    return(t);
}


/*
 * ----------------------------------------------------
 * @Name rmLightDelete
 @pstart
 void rmLightDelete (RMlight *toDelete)
 @pend

 @astart
 RMlight *toDelete - a handle to an RMlight object.
 @aend

 @dstart
 
 Frees resources associated with an RMlight object. This routine is
 the opposite of rmLightNew().
 
 @dend
 * ----------------------------------------------------
 */
void
rmLightDelete (RMlight *t)
{
    if (RM_ASSERT(t, "rmLightDelete() error: the input RMlight is NULL.") == RM_WHACKED)
	return;

    free((void *)t);
}


/*
 * ----------------------------------------------------
 * @Name rmLightSetType
 @pstart
 RMenum rmLightSetType (RMlight *toModify,
	                RMenum newType)
 @pend

 @astart 
 RMlight *toModify - a handle to an RMlight object (modified).

 RMenum newType - an RMenum value specifying the new light type for
    the RMlight object. Must be one of RM_LIGHT_POINT,
    RM_LIGHT_DIRECTIONAL, or RM_LIGHT_SPOT (input).
 @aend

 @dstart

 RM light sources may be directional, spot or point light sources, and
 this routine is used to set the type of an RMlight object to one of
 those light source types. Returns RM_CHILL upon success, or
 RM_WHACKED upon failure.

 RMlight objects represent light sources. Of the parameters in an
 RMlight object, some are used regardless of light source type, while
 others have meaning only for certain types of lights.

 The ambient, diffuse and specular color attributes are used by all
 light source types (see rmLightSetColor). Also, the light attenuation
 coefficients are used by all light source types (see
 rmLightSetAttenuation).

 All light sources have a 3D coordinate associated with them, set with
 rmLightSetXYZ. The position of Point light sources (RM_LIGHT_POINT)
 and spotlight sources (RM_LIGHT_SPOT) is specified with
 rmLightSetXYZ.  Directional light sources have their orientation
 vector specified with rmLightSetXYZ.

 Note that setting attributes in an RMlight does not change them in
 the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLight).
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightSetType (RMlight *t,
	        RMenum newType)
{
    if (RM_ASSERT(t, "rmLightSetType() error: the input RMlight is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if ((newType == RM_LIGHT_POINT) || (newType == RM_LIGHT_DIRECTIONAL) || (newType == RM_LIGHT_SPOT))
    {
	t->ltype = newType;
    }
    else
    {
	rmError("rmLightSetType() error: the newType enumerator is not one of RM_LIGHT_POINT, RM_LIGHT_DIRECTIONAL or RM_LIGHT_SPOT");
	return(RM_WHACKED);
    }
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightGetType
 @pstart
 RMenum rmLightGetType (const RMlight *toQuery)
 @pend

 @astart
 const RMlight *toQuery - a handle to an RMlight object (input).
 @aend

 @dstart

 Upon success, returns to the caller the RMlight light source type;
 RM_LIGHT_POINT, RM_LIGHT_SPOT or RM_LIGHT_DIRECTIONAL. Otherwise,
 returns RM_WHACKED.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightGetType (const RMlight *t)
{
    if (RM_ASSERT(t, "rmLightGetType() error: the input RMlight is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    return(t->ltype);
}


/*
 * ----------------------------------------------------
 * @Name rmLightSetXYZ
 @pstart
 RMenum rmLightSetXYZ (RMlight *toModify,
	               const RMvertex3D *newXYZ)
 @pend

 @astart
 RMlight *toModify - a handle to an RMlight object (modified).

 const RMvertex3D *newXYZ - a handle to an RMvertex3D object (input).
 @aend

 @dstart

 This routine is used to set the XYZ attribute of a light source
 object, controlling the position of RM_LIGHT_POINT and RM_LIGHT_SPOT
 sources, or the direction of RM_LIGHT_DIRECTIONAL objects. Copies
 from the caller-supplied RMvertex3D object into the RMlight object.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.
 
 Note that setting attributes in an RMlight does not change them in
 the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLight).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightSetXYZ (RMlight *t,
	       const RMvertex3D *newXYZ)
{
    if ((RM_ASSERT(t, "rmLightSetXYZ() error: the input RMlight is NULL") == RM_WHACKED) ||
	(RM_ASSERT(newXYZ, "rmLightSetXYZ() error: the input newXYZ pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    t->lightXYZ = *newXYZ;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightGetXYZ
 @pstart
 RMenum rmLightGetXYZ (const RMlight *toQuery,
	               RMvertex3D *retXYZ)
 @pend

 @astart
 const RMlight *toQuery - a handle to an RMlight object (input).

 RMvertex3D *retXYZ - a handle to an RMvertex3D object (modified).
 @aend

 @dstart 

 Upon success, copies the light source's XYZ position/orientation
 attribute from the RMlight object into the caller-supplied memory,
 and returns RM_CHILL. Upon failure, returns RM_WHACKED.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightGetXYZ (const RMlight *t,
	       RMvertex3D *retXYZ)
{
    if ((RM_ASSERT(t, "rmLightGetXYZ() error: the input RMlight is NULL") == RM_WHACKED) ||
	(RM_ASSERT(retXYZ, "rmLightGetXYZ() error: the input retXYZ pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    *retXYZ = t->lightXYZ;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightSetColor
 @pstart
 RMenum rmLightSetColor (RMlight *toModify,
		         const RMcolor4D *newAmbientColor,
			 const RMcolor4D *newDiffuseColor,
			 const RMcolor4D *newSpecularColor)
 @pend

 @astart
 RMlight *toModify - a handle to an RMlight object (modified).

 const RMcolor4D *newAmbientColor, *newDiffuseColor, *newSpecularColor
    - handles to RMcolor4D objects specifying the RGBA colors for the
    light source ambient, diffuse and specular light terms.  Use of
    NULL for some or all terms is acceptable (input).
 @aend

 @dstart

 In RM (and OpenGL), the final shade computed at a vertex is a
 function of the vertex color, the current material properties and the
 light source(s).  OpenGL uses a Phong-type shading model at each
 vertex that computes the dot product of the surface normal at the
 vertex (specified by the application) with the direction to each
 light source (directional light sources require less work at this
 stage than positional light sources).  This dot product to weight the
 diffuse and specular reflectance terms of the shading equation.

 The Phong shading equation has terms for three types of light
 reflectance: ambient, diffuse and specular. It is possible for
 applications to specify the ambient, diffuse and specular terms
 (colors) for both the light sources as well as the objects in the
 scene to implement custom scenes like a yellow light shining on a
 green ball.

 This routine is used to set the ambient, diffuse and specular color
 terms for a specific light source.

 Note that the "global ambient light" level is set in the RMlightModel
 object, not in the RMlight object (see rmLightModelNew).

 It is possible for applications to selectively modify a particular
 reflectence color term - by specifying NULL for one of the input
 color vectors, that term in the RMlight object is not modified. In
 other words, only those non-NULL color terms are modified in the
 RMlight object.
 
 Upon success, this routine copies from the caller-supplied RMcolor4D
 objects into the RMlight object and returns RM_CHILL. Otherwise,
 RM_WHACKED is returned.

 Note that setting attributes in an RMlight does not change them in
 the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLight).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightSetColor (RMlight *t,
		 const RMcolor4D *newAmbientColor,
		 const RMcolor4D *newDiffuseColor,
		 const RMcolor4D *newSpecularColor)
{
    if (RM_ASSERT(t, "rmLightSetAmbientColor() error: the input RMlight is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (newAmbientColor != NULL)
	t->ambientLightColor = *newAmbientColor;
    
    if (newDiffuseColor != NULL)
	t->diffuseLightColor = *newDiffuseColor;
    
    if (newSpecularColor != NULL)
	t->specularLightColor = *newSpecularColor;
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightGetColor
 @pstart
 RMenum rmLightGetColor (const RMlight *toQuery,
		         RMcolor4D *retAmbientColor,
			 RMcolor4D *retDiffuseColor,
			 RMcolor4D *retSpecularColor)
 @pend

 @astart
 const RMlight *toQuery - a handle to an RMlight object (input).

 RMcolor4D *retAmbientColor, *retDiffuseColor, *retSpecularColor -
    handles to RMcolor4D objects (modified). Use of NULL for some or
    all terms is acceptable (input).
 @aend

 @dstart
 
 Use this routine to obtain one or more light source color vectors
 from an RMlight object. Upon success, one or more color vectors is
 copied from the RMlight object into caller-supplied memory, and
 RM_CHILL is returned. Otherwise, RM_WHACKED is returned.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightGetColor (const RMlight *t,
		 RMcolor4D *retAmbientColor,
		 RMcolor4D *retDiffuseColor,
		 RMcolor4D *retSpecularColor)
{
    if (RM_ASSERT(t, "rmLightGetColor() error: the input RMlight is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (retAmbientColor != NULL)
	*retAmbientColor = t->ambientLightColor;
    
    if (retDiffuseColor != NULL)
	*retDiffuseColor = t->diffuseLightColor;
    
    if (retSpecularColor != NULL)
	*retSpecularColor = t->specularLightColor;
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightSetAttenuation
 @pstart
 RMenum rmLightSetAttenuation (RMlight *toModify,
		               float newConstantAttenuation,
			       float newLinearAttenuation,
			       float newQuadraticAttenuation)
 @pend

 @astart
 RMlight *toModify - a handle to an RMlight object (modified).

 float newConstantAttenuation, newLinearAttenuation,
    newQuadraticAttenuation - floating point values specifying the
    coefficients for constant, linear and quadratic light attenuation,
    or decay, for an RMlight source (input).
 @aend

 @dstart

 In the real world, light intensity decays as a function of distance
 from the light source. The further away from the light source, the
 weaker the light. The rate at which light decays is largely a
 function of the medium through which the light energy passes. In a
 vaccuum, like space, light decays slowly. In another medium, like
 water, light decays more rapidly as the light energy is absorbed by
 the participating medium.

 Applications have some measure of control over how light energy
 decays as a function of distance from the light source through the
 manipulation of "light attenuation factors." The equation used to
 compute attenuation of light intensity at some point in space is:

 L = 1 / (Kc + Kl * d + Kq * d^2)

 where d is the distance to the light source.

 By default, that constant term (Kc) is set to 1.0, and the linear
 (Kl) and quadratic (Kq) terms are set to zero; light energy does not
 decay at all. Most OpenGL implementations are optimized for this
 condition.

 Upon success, the three attenuation coefficients are copied into the
 RMlight object, and RM_CHILL is returned. Otherwise, RM_WHACKED is
 returned.
 
 Note that setting attributes in an RMlight does not change them in
 the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLight).
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightSetAttenuation (RMlight *t,
		       float newConstantAttenuation,
		       float newLinearAttenuation,
		       float newQuadraticAttenuation)
{
    if (RM_ASSERT(t, "rmLightSetAttenuation() error: the input RMlight is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    t->constantAttenuation = newConstantAttenuation;
    t->linearAttenuation = newLinearAttenuation;
    t->quadraticAttenuation = newQuadraticAttenuation;
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightGetAttenuation
 @pstart
 RMenum rmLightGetAttenuation (const RMlight *toQuery,
		               float *retConstantAttenuation,
			       float *retLinearAttenuation,
			       float *retQuadraticAttenuation)
 @pend

 @astart
 const RMlight *toQuery - a handle to an RMlight object (input).

 float *retConstantAttenuation, *retLinearAttenuation,
    *retQuadraticAttenuation - handle to caller-supplied floats
    (modified).
 @aend

 @dstart

 Upon success, copies the three light attenuation factors into
 caller-supplied memory and returns RM_CHILL. Otherwise, RM_WHACKED is
 returned.

 Note that all three input parameters must be non-NULL, otherwise an
 error condition is produced.

 See rmLightSetAttenuation for more details about these attributes.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightGetAttenuation (const RMlight *t,
		       float *retConstantAttenuation,
		       float *retLinearAttenuation,
		       float *retQuadraticAttenuation)
{
    if ((RM_ASSERT(t, "rmLightGetAttenuation() error: the input RMlight is NULL") == RM_WHACKED) ||
	(RM_ASSERT(retConstantAttenuation, "rmLightGetAttenuation() error: the retConstantAttenuation pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(retLinearAttenuation, "rmLightGetAttenuation() error: the retLinearAttenuation pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(retQuadraticAttenuation, "rmLightGetAttenuation() error: the retQuadraticAttenuation pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    *retConstantAttenuation = t->constantAttenuation;
    *retLinearAttenuation = t->linearAttenuation;
    *retQuadraticAttenuation = t->quadraticAttenuation;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightSetEnable
 @pstart
 RMenum rmLightSetEnable (RMlight *toModify,
		          RMenum newValue)
 @pend

 @astart
 RMlight *toModify - a handle to an RMlight object (modified).

 RMenum newValue - an RMenum value, must be either RM_TRUE or
    RM_FALSE.
 @aend

 @dstart

 Light sources may be individually turned on or off by using this
 routine. To turn a light on, use RM_TRUE, and to turn it off, use
 RM_FALSE.

 Upon success, the newValue attribute is copied into the RMlight
 object, and RM_CHILL is returned, otherwise RM_WHACKED is returned.

 Note that setting attributes in an RMlight does not change them in
 the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLight).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightSetEnable (RMlight *t,
		  RMenum newValue)
{
    if (RM_ASSERT(t, "rmLightSetEnable() error: the input RMlight pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if (newValue == RM_TRUE || newValue == RM_FALSE)
	t->enabled = newValue;
    else
    {
	rmError("rmLightSetEnable() error: the input newValue enumerator is niether RM_TRUE nor RM_FALSE.");
	return(RM_WHACKED);
    }
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightGetEnable
 @pstart
 RMenum rmLightGetEnable (const RMlight *toQuery)
 @pend

 @astart
 const RMlight *toQuery - a handle to an RMlight object (input).
 @aend

 @dstart

 Upon success, returns to the caller either RM_TRUE or RM_FALSE,
 indicating that the light source is either turned on or turned off,
 respectively.  Otherwise, returns RM_WHACKED.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightGetEnable (const RMlight *t)
{
    if (RM_ASSERT(t, "rmLightGetEnable() error: the input RMlight object is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    return(t->enabled);
}


/*
 * ----------------------------------------------------
 * @Name  rmLightSetSpotDirection
 @pstart
 RMenum rmLightSetSpotDirection (RMlight *toModify,
			         const RMvertex3D *newSpotDirection)
 @pend

 @astart
 RMlight *toModify - a handle to an RMlight object (modified).

 const RMvertex3D *newSpotDirection - a handle to an RMvertex3D object
    (input).
 @aend

 @dstart

 Spotlight geometry is specified with both a position (rmLightSetXYZ)
 and a direction (rmLightSetSpotDirection). This routine is used to
 set the 3D orientation, or direction, of the spotlight light source.

 Upon success, this routine will copy the 3D vector from the
 caller-supplied RMvertex3D object into the RMlight object and return
 RM_CHILL. Upon failure, RM_WHACKED is returned.
 
 Note that setting attributes in an RMlight does not change them in
 the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLight).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightSetSpotDirection (RMlight *t,
			 const RMvertex3D *newSpotDirection)
{
    if ((RM_ASSERT(t, "rmLightSetSpotDirection() error: the input RMlight is NULL") == RM_WHACKED) ||
	(RM_ASSERT(newSpotDirection, "rmLightSetSpotDirection() error: the input newSpotDirection pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    t->spotDirection = *newSpotDirection;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightGetSpotDirection
 @pstart
 RMenum rmLightGetSpotDirection (const RMlight *toModify,
			         RMvertex3D *retDirection)
 @pend

 @astart 
 const RMlight *toModify - a handle to an RMlight object (input).
 
 RMvertex3D *retDirection - a handle to an RMvertex3D object
    (modified, return). 
 @aend

 @dstart

 Spotlights are specified with both a position (rmLightSetXYZ) and a
 direction (rmLightSetSpotDirection).  Use this routine to obtain the
 direction vector of a spotlight source.

 Upon success, this routine copies the spotlight direction vector from
 the RMlight object into the caller-supplied memory and returns
 RM_CHILL. Upon failure, RM_WHACKED is returned.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightGetSpotDirection (const RMlight *t,
			 RMvertex3D *retDirection)
{
    if ((RM_ASSERT(t, "rmLightGetSpotDirection() error: the input RMlight is NULL") == RM_WHACKED) ||
	(RM_ASSERT(retDirection, "rmLightGetSpotDirection() error: the input retDirection pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    *retDirection = t->spotDirection;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightSetSpotCutoff
 @pstart 
 RMenum rmLightSetSpotCutoff (RMlight *toModify,
		              float newValue)
 @pend

 @astart
 RMlight *toModify - a handle to an RMlight object (modified).

 float newValue - a floating point value in the range [0..90] (or the
    special value of 180) specifying the spread angle of the
    spotlight (input).
 @aend

 @dstart

 The spotlight light source consists of a number of
 parameters. Spotlight position is it's location in 3-space
 (rmLightSetXYZ). The direction in which the spotlight is pointed is
 it's orientation (rmLightSetSpotDirection). The light color consists
 of ambient, diffuse and specular colors (rmLightSetColor). The rate
 at which light decays as a function of distance from the light source
 position is specified with constant, linear and quadratic
 coefficients (rmLightSetAttenuation).  The spread angle of the
 spotlight is a measure of the half-angle, in degrees, of the apex of
 a cone formed by the spotlight, with the light positioned at the apex
 of the cone and oriented towards the base of the cone
 (rmLightSetSpotCutoff).  Finally, the rate at which light decays, or
 falls off, as a function of distance from the vector formed by the
 center of the spotlight cone (rmLightSetSpotExponent).

 This routine is used to set the value that controls the spread angle
 of the spotlight light source. The value must be in the range 0 to
 90, or the special value 180. This value specifies the half-angle of
 the cone apex formed by the illumination volume of the spotlight
 source. The spotlight source is positioned at the apex of this cone,
 and shines in the direction of the base of the cone. The greater this
 value, the larger the illumination volume. A value of 45 (degrees)
 specifies an illumination volume with a cone apex of 90 degrees.

 The special value of 180.0 specifies a spotlight source that shines
 in all directions.

 Upon success, the spotlight cutoff attribute of the RMlight object is
 modified, and RM_CHILL is returned to the caller. Otherwise,
 RM_WHACKED is returned.
 
 Note that setting attributes in an RMlight does not change them in
 the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLight).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightSetSpotCutoff (RMlight *t,
		      float newValue)
{
    if (RM_ASSERT(t, "rmLightSetSpotCutoff() error: the input RMlight is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    t->spotCutoff = newValue;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightGetSpotCutoff
 @pstart
 RMenum rmLightGetSpotCutoff (const RMlight *toQuery,
		              float *retValue)
 @pend

 @astart
 const RMlight *toQuery - a handle to an RMlight object (input).

 float *retValue - a handle to a caller-supplied float (modified).
 @aend

 @dstart

 Use this routine to obtain the spotlight cutoff value from an RMlight
 object. Upon success, the spotlight cutoff value is copied into the
 caller-supplied memory and RM_CHILL is returned. Otherwise,
 RM_WHACKED is returned.

 See rmLightSetSpotCutoff for more details about the meaning of the
 spotlight cutoff attribute.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightGetSpotCutoff (const RMlight *t,
		      float *retValue)
{
    if ((RM_ASSERT(t, "rmLightGetSpotCutoff() error: the input RMlight is NULL") == RM_WHACKED) ||
	(RM_ASSERT(retValue, "rmLightSetSpotCutoff() error: the return float pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    *retValue = t->spotCutoff;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightSetSpotExponent
 @pstart
 RMenum rmLightSetSpotExponent (RMlight *toModify,
		                float newValue)
 @pend

 @astart
 RMlight *toModify - a handle to an RMlight object (modified).

 float newValue - a non-negative floating point value (input).
 @aend

 @dstart

 The spotlight light source consists of a number of
 parameters. Spotlight position is it's location in 3-space
 (rmLightSetXYZ). The direction in which the spotlight is pointed is
 it's orientation (rmLightSetSpotDirection). The light color consists
 of ambient, diffuse and specular colors (rmLightSetColor). The rate
 at which light decays as a function of distance from the light source
 position is specified with constant, linear and quadratic
 coefficients (rmLightSetAttenuation).  The spread angle of the
 spotlight is a measure of the half-angle, in degrees, of the apex of
 a cone formed by the spotlight, with the light positioned at the apex
 of the cone and oriented towards the base of the cone
 (rmLightSetSpotCutoff).  Finally, the rate at which light decays, or
 falls off, as a function of distance from the vector formed by the
 center of the spotlight cone (rmLightSetSpotExponent).

 This routine is used to set the spotlight exponent value, or the rate
 at which light decays as a function of distance from the center of
 the illumination cone. By default, this parameter is set to zero,
 indicating uniform light distribution (no decay) within the
 illumination cone. Since this attribute specifies an exponent, light
 decay falls off more rapidly as the value for this parameter
 increases. Linear decay (inverse distance) is specified using a value
 of 1.0 for this parameter; a value of 2.0 specifies that light falls
 off as the inverse of the square of the distance to the center of the
 illumination cone.

 Upon success, this routine copies the caller-supplied newValue
 floating point value into the RMlight object's spotlight exponent
 attribute and returns RM_CHILL, otherwise RM_WHACKED is returned.
 
 Note that setting attributes in an RMlight does not change them in
 the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLight).
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightSetSpotExponent (RMlight *t,
		        float newValue)
{
    if (RM_ASSERT(t, "rmLightSetSpotExponent() error: the input RMlight is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    t->spotExponent = newValue;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightGetSpotExponent
 @pstart
 RMenum rmLightGetSpotExponent (const RMlight *toQuery,
		                float *retValue)
 @pend

 @astart
 const RMlight *toQuery - a handle to an RMlight object (input).

 float *retValue - a handle to a caller-supplied float (modified).
 @aend

 @dstart

 Use this routine to obtain the spotlight exponent attribute of an
 RMlight object. Upon success, the spotlight exponent attribute of the
 RMlight object is copied into caller-supplied memory, and RM_CHILL is
 returned. Otherwise, RM_WHACKED is returned.

 See rmLightSetSpotExponent for more details about the meaning and use
 of this attribute.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightGetSpotExponent (const RMlight *t,
		        float *retValue)
{
    if ((RM_ASSERT(t, "rmLightGetSpotExponent() error: the input RMlight is NULL") == RM_WHACKED) ||
	(RM_ASSERT(retValue, "rmLightSetSpotExponent() error: the return float pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    *retValue = t->spotExponent;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightModelNew
 @pstart
 RMlightModel * rmLightModelNew (void)
 @pend

 @astart
 No arguments.
 @aend

 @dstart

 Creates a new RMlightModel object, initializes it with default
 values, and returns to handle to the caller. NULL is returned upon
 failure.

 Lighting in RM (and OpenGL) is a function of light sources and the
 lighting environment. The RMlight objects contain information about
 the specific light sources, such as their position, light decay
 properties, color and so forth. The RMlightModel object controls
 global lighting parameters, including global ambient illumination,
 two-sided illumination control and selection of one of two algorithms
 used to compute specular reflection values.

 Global ambient illumination is the amount of light that is present
 everywhere in the scene, and represents the combination of overall
 composite luminance with the degree to which light is scattered.  By
 default, the ambient color assigned to the RMlightModel object is
 {0.,0.,0.}, indicating the absence of global ambient light. During
 shading, the global ambient term is applied to all vertices,
 regardless of the amount of ambient light specified in an RMlight
 object.  This term can be thought of as another independent light
 source in the scene (see rmLightModelSetAmbient).

 Two-sided illumination is used to create the effect of a
 bi-directional light source. By default, two-sided lighting is turned
 off (see rmLightModelSetTwoSided).

 Specular reflections take into account the surface normal, the
 direction to the light source from a vertex, and the direction to the
 viewer from the vertex. When the "local viewer" attribute is set to
 RM_TRUE, the direction to the viewer from each vertex is calculated
 during shading.  This results in more realistic shading, but at added
 computational expense. When set to RM_FALSE, the position of the
 viewer is fixed at (0,0,0) in eye coordinates (not world
 coordinates) for the purposes of shading.

 Note: OpenGL 1.2 adds a new term to the lighting model that can be
 used to control the computation specular reflection color. This
 feature has not yet been integrated into RM.

 Applications need to create both a light source and a light model in
 order for lighting to work properly.
 
 @dend
 * ----------------------------------------------------
 */
RMlightModel  *
rmLightModelNew (void)
{
    RMlightModel *t;

    t = (RMlightModel *)malloc(sizeof(RMlightModel));

    if (t == NULL)
    {
	rmError("rmLightModelNew() error: unable to malloc a new RMlightModel object. ");
	return(NULL);
    }
    
    memset(t, 0, sizeof(RMlightModel));

    /* default ambient is (0,0,0,0) */
    t->twoSideEnable = RM_FALSE;
    t->localViewerEnable = RM_FALSE;

    return(t);
}


/*
 * ----------------------------------------------------
 * @Name rmLightModelDelete
 @pstart
 RMenum rmLightModelDelete (RMlightModel *toDelete)
 @pend

 @astart
 RMlightModel *toDelete - a handle to an RMlightModel object to be
    deleted (modified). 
 @aend

 @dstart

 Releases resources associated with an RMlightModel object. Returns
 RM_CHILL upon success, or RM_WHACKED upon failure. This routine is
 the opposite of rmLightModelNew.

 @dend
 * ---------------------------------------------------- 
 */
RMenum
rmLightModelDelete (RMlightModel *toDelete)
{
    if (RM_ASSERT(toDelete, "rmLightModelDelete() error: the input RMlightModel pointer is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    
    free((void *)toDelete);

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightModelSetAmbient
 @pstart
 RMenum rmLightModelSetAmbient (RMlightModel *toModify,
			        const RMcolor4D *newAmbientColor)
 @pend

 @astart
 RMlightModel *toModify - a handle to an RMlightModel object (modified).

 const RMcolor4D *newAmbientColor - a handle to an RMcolor4D object
    (input).
 @aend

 @dstart

 Upon success, copies the contents of the RMcolor4D object to the
 RMlightModel object, and returns RM_CHILL. Otherwise, RM_WHACKED is
 returned.

 Color attributes are specified with floating point values in the
 range 0..1, where 0 is "off" and 1 is "on." The RMcolor4D object
 represents red, green, blue and alpha.

 See rmLightModelNew for more information about the global ambient
 light model attribute.
 
 Note that setting attributes in an RMlightModel does not change them
 in the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLightModel).
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightModelSetAmbient (RMlightModel *toModify,
			const RMcolor4D *newAmbientColor)
{
    if ((RM_ASSERT(toModify, "rmLightModelSetAmbient() error: the input RMlightModel pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(newAmbientColor, "rmLightModelSetAmbient() error: the input newAmbientColor pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    toModify->globalAmbient = *newAmbientColor;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightModelGetAmbient
 @pstart
 RMenum rmLightModelGetAmbient (const RMlightModel *toQuery,
			        RMcolor4D *retAmbientColor)
 @pend

 @astart
 const RMlightModel *toQuery - a handle to an RMlightModel object
    (input).

 RMcolor4D *retAmbientColor - a handle to an RMcolor4D object
    (modified).
 @aend

 @dstart

 Upon success, this routine will copy the global ambient light
 attribute from the RMlightModel object into the caller-supplied
 RMcolor4D object, and return RM_CHILL. Otherwise, RM_WHACKED is
 returned.

 See rmLightModelNew for more information about the global ambient
 light model attribute.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightModelGetAmbient (const RMlightModel *toQuery,
			RMcolor4D *retAmbientColor)
{
    if ((RM_ASSERT(toQuery, "rmLightModelGetAmbient() error: the input RMlightModel pointer is NULL") == RM_WHACKED) ||
	(RM_ASSERT(retAmbientColor, "rmLightModelGetAmbient() error: the input retAmbientColor pointer is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    *retAmbientColor = toQuery->globalAmbient;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightModelSetTwoSided
 @pstart
 RMenum rmLightModelSetTwoSided (RMlightModel *toModify,
			         RMenum newValue)
 @pend

 @astart
 RMlightModel *toModify - a handle to an RMlightModel object
    (modified).

 RMenum newValue - an RMenum value, should be either RM_TRUE or
    RM_FALSE (input).
 @aend

 @dstart

 This routine sets the two-sided lighting attribute of an RMlightModel
 object. When RM_TRUE is specified, two-sided lighting is activitated
 for this light model. When set to RM_FALSE, two-sided lighting is
 disabled, and single-sided lighting takes effect.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 Note that setting attributes in an RMlightModel does not change them
 in the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLightModel).
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightModelSetTwoSided (RMlightModel *toModify,
			 RMenum newValue)
{
    if (RM_ASSERT(toModify, "rmLightModelSetTwoSided() error: the input RMlightModel pointer is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (newValue == RM_TRUE || newValue == RM_FALSE)
	toModify->twoSideEnable = newValue;
    else
    {
	rmWarning("rmLightModelTwoSided warning: the input RMenum is neither RM_TRUE nor RM_FALSE");
	return(RM_WHACKED);
    }

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightModelGetTwoSided
 @pstart
 RMenum rmLightModelGetTwoSided (const RMlightModel *toQuery)
 @pend

 @astart
 const RMlightModel *toQuery - a handle to an RMlightModel object
    (input).
 @aend

 @dstart

 Upon success, returns the setting of the two-sided lighting attribute
 of an RMlightModel object, either RM_TRUE or RM_FALSE. Otherwise,
 RM_WHACKED is returned.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightModelGetTwoSided (const RMlightModel *toQuery)
{
    if (RM_ASSERT(toQuery, "rmLightModelGetTwoSided() error: the input RMlightModel pointer is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    return((RMenum)toQuery->twoSideEnable);
}


/*
 * ----------------------------------------------------
 * @Name rmLightModelSetLocalViewer
 @pstart
 RMenum rmLightModelSetLocalViewer (RMlightModel *toModify,
			            RMenum newValue)
 @pend

 @astart
 RMlightModel *toModify - a handle to an RMlightModel object
    (modified).

 RMenum newValue - an RMenum value, must be either RM_TRUE or RM_FALSE
    (input).
 @aend

 @dstart

 This routine will set the "local viewer" attribute of the
 RMlightModel object to the value specified by the caller and return
 RM_CHILL upon success. Otherwise, RM_WHACKED is returned.

 See rmLightModelNew for more information about the "local viewer"
 attribute.
 
 Note that setting attributes in an RMlightModel does not change them
 in the scene graph. They must be assigned as scene parameters to an
 RMnode before they will be used in rendering (see
 rmNodeSetSceneLightModel).
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightModelSetLocalViewer (RMlightModel *toModify,
			    RMenum newValue)
{
    if (RM_ASSERT(toModify, "rmLightModelSetLocalViewer() error: the input RMlightModel pointer is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if (newValue == RM_TRUE || newValue == RM_FALSE)
	toModify->localViewerEnable = newValue;
    else
    {
	rmError(" rmLightModelSetLocalViewer() error: the input enumerator is neither RM_TRUE nor RM_FALSE ");
	return(RM_WHACKED);
    }
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmLightModelGetLocalViewer
 @pstart
 RMenum rmLightModelGetLocalViewer (const RMlightModel *toQuery)
 @pend

 @astart
 const RMlightModel *toQuery - a handle to an RMlightModel object
    (input).
 @aend

 @dstart

 Upon success, returns the "local viewer" attribute of the
 RMlightModel object. Otherwise, RM_WHACKED is returned.

 See rmLightModelNew for more information about the "local viewer"
 attribute of the RMlightModel object.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmLightModelGetLocalViewer (const RMlightModel *toQuery)
{
    if (RM_ASSERT(toQuery, "rmLightModelGetLocalViewer() error: the input RMlightModel pointer is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    return((RMenum)toQuery->localViewerEnable);
}


/*
 * ----------------------------------------------------
 * @Name rmDefaultLighting
 @pstart
 RMenum rmDefaultLighting (RMnode *toModify)
 @pend

 @astart
 RMnode *toModify - a handle to an RMnode (modified).
 @aend

 @dstart

 This convenience routine is used to create a default set of lights
 and a default lighting model, and to assign them as scene parameters
 to an RMnode. The new lights and lighting model have effect over all
 children of the specified RMnode.

 The default lighting configuration consists of:

 1. One directional light source at 0,10,3 (overhead and slightly
    behind the viewer) with no ambient term, diffuse set to
    (0.7,0.7,0.7,1.0) and specular set to (0.5,0.5,0.5,1.0).  A second
    light source is available by uncommenting one line of code inside
    this routine.

 2. A light model with global ambient light set to (0.2,0.2,0.2,1.0),
    two-sided lighting disabled and disabling of "local viewer"
    specular reflection calculations.

 The net result is a lighting model that works reasonably well for
 most scenes both in terms of performance as well as illumination
 characteristics. While this convenience function is useful,
 developers are encouraged to examine this routine because it is a
 good example of how to create and modify light sources and light
 models, as well as assign them as scene parameters to an RMnode.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmDefaultLighting (RMnode *n)
{
    /* light source zero is a from-the-top directional light */
    RMlight   *l0, *l1;
    RMcolor4D  diffuse = {0.7, 0.7, 0.7, 1.0};
    RMcolor4D  specular = {0.5, 0.5, 0.5, 1.0};
    RMcolor4D  diffuse2 = {0.3, 0.3, 0.5, 1.0};
    RMvertex3D pos2 = {0.0, -3.0, 1.0};
    RMvertex3D position = {0.0, 10.0, 3.0}; 

    l0 = rmLightNew();
    if (l0 == NULL)
	return(RM_WHACKED);

    rmLightSetType(l0, RM_LIGHT_DIRECTIONAL); 
    rmLightSetColor(l0, NULL, &diffuse, &specular); 
    rmLightSetXYZ (l0, &position);

    /*
     * light source one is a from-the-bottom directional light with
     * reduced emphasis on diffuse and specular. the more dim
     * diffuse light is kind of blue.
     */
    l1 = rmLightNew();
    if (l1 == NULL)
	return(RM_WHACKED);
    
    rmLightSetType(l1, RM_LIGHT_DIRECTIONAL); 
    rmLightSetColor(l1, NULL, &diffuse2, &specular);
    rmLightSetXYZ(l1, &pos2);

    rmNodeSetSceneLight(n, RM_LIGHT0, l0);
    /*
     * uncomment the following line to add the second light source
     * into the scene. we leave it out by default for performance reasons,
     * but having it readily available for experimentation has proven useful.
     *
     * rmNodeSetSceneLight(n, RM_LIGHT1, l1);   
     */

    /*
     * when the lights are added as scene parameters, RM makes a copy
     * of them, and we don't need our RMlight objects anymore, so we
     * need to delete them.
     */

    rmLightDelete(l0);
    rmLightDelete(l1); 
    
    /* set up the light model/lighting environment */
    {
	RMcolor4D     defAmbient =  {0.2, 0.2, 0.2, 1.0};
	RMlightModel *lm = rmLightModelNew();

	if (lm == NULL)
	    return(RM_WHACKED);
	
	rmLightModelSetAmbient(lm, &defAmbient);
	rmLightModelSetTwoSided (lm, RM_FALSE);
/*	rmLightModelSetLocalViewer(lm, RM_TRUE); */
	rmLightModelSetLocalViewer(lm, RM_FALSE);
	rmNodeSetSceneLightModel(n, lm);

	rmLightModelDelete (lm);
    }
    return(RM_CHILL);
}
/* EOF */
