/*ScianLights.c
  Lights
  Eric Pepke
  May 21, 1991
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianColors.h"
#include "ScianLists.h"
#include "ScianArrays.h"
#include "ScianWindows.h"
#include "ScianSpaces.h"
#include "ScianDraw.h"
#include "ScianObjWindows.h"
#include "ScianVisWindows.h"
#include "ScianVisObjects.h"
#include "ScianIcons.h"
#include "ScianIDs.h"
#include "ScianControls.h"
#include "ScianButtons.h"
#include "ScianErrors.h"
#include "ScianDialogs.h"
#include "ScianSliders.h"
#include "ScianTextBoxes.h"
#include "ScianStyle.h"
#include "ScianComplexControls.h"
#include "ScianLights.h"
#include "ScianMethods.h"
#include "ScianScripts.h"
#include "ScianEvents.h"
#include "ScianObjFunctions.h"
#include "ScianSymbols.h"

ObjPtr lightClass;			/*Class of individual light*/
int lightsNum = 0;
ObjPtr commonLights = NULLOBJ;	
Bool lastTwoSided = false;		/*Last change to twoSided*/
int nTotalLights = 0;

#define TOTALLIGHTS	8		/*Total number of possible lights*/
#define NLIGHTS		2		/*Number of lights available by default*/

lightSpec defLights[] =
    {
	{(M_PI_4 * 1.2), M_PI_4, {1.0, 1.0, 1.0}, 1.0, false},
	/*{M_PI_2, M_PI_4, {0.7, 1.0, 1.0}, 0.5, false},*/
	{-M_PI_4, - 0.7 * M_PI_4, {0.7, 1.0, 1.0}, 0.5, false},
	{M_PI, M_PI_4, {1.0, 0.0, 0.5}, 0.2, true},
	{0.0, M_PI_2, {1.0, 1.0, 0.6}, 0.3, true}
    };

#ifdef GRAPHICS
/*Don't change this without checking references*/
float lightModel[] = {
			AMBIENT, 0.2, 0.2, 0.2,
#ifdef TWOSIDE
			TWOSIDE, 0.0,
#endif
			LMNULL};
#endif

#ifdef PROTO
void TwoSided(Bool whether)
#else
void TwoSided(whether)
Bool whether;
#endif
/*Goes to two sided lighting on whether*/
{
#ifdef GRAPHICS
    {
	if (whether != lastTwoSided)
	{
	    int k;
	    lastTwoSided = whether;
	    if (whether)
	    {
		/*Bind all the reverse lights*/
		for (k = 0; k < nTotalLights; ++k)
		{
		    if (rgbp)
		    {
			lmbind(LIGHT0 + k + nTotalLights, k * 2 + 2);
		    }
		}
	    }
	    else
	    {
		/*Unbind all the reverse lights*/
		for (k = 0; k < nTotalLights; ++k)
		{
		    if (rgbp)
		    {
		    	lmbind(LIGHT0 + k + nTotalLights, 0);
		    }
		}
	    }
	}
    }
#endif
}

#ifdef PROTO
void StartLights(ObjPtr space, ObjPtr lights, Bool twoSided)
#else
void StartLights(space, lights, twoSided)
ObjPtr space, lights;
Bool twoSided;
#endif
/*Starts the lights*/
{
    ThingListPtr runner;
    int k;

#ifdef GRAPHICS
    if (rgbp)
    {
	lmbind(LMODEL, 1);
    }
#endif

    runner = LISTOF(lights);
    k = 0;
    while (runner)
    {
	ObjPtr light;
	ObjPtr var;
	real brightness;
	real baseColor[3];
	real location[3];
#ifdef GRAPHICS
	float tempLight[50];
	float *lightPtr;
#endif

	light = runner -> thing;

	if (k < TOTALLIGHTS && !GetPredicate(runner -> thing, HIDDEN))
	{
	
	    var = GetVar(light, BRIGHTNESS);
	    if (var && IsReal(var))
	    {
		brightness = GetReal(var);
	    }
	    else
	    {
		brightness = 1.0;
	    }
	    var = GetVar(light, BASECOLOR);
	    if (var)
	    {
		Array2CArray(baseColor, var);
	    }
	    else
	    {
		baseColor[0] = 1.0;
		baseColor[1] = 1.0;
		baseColor[2] = 1.0;
	    }
	    var = GetFixedArrayVar("StartLights", light, LOCATION, 1, 3L);
	    if (!var)
	    {
		return;
	    }
	    Array2CArray(location, var);

#ifdef GRAPHICS
	    lightPtr = tempLight;
	    *lightPtr++ = POSITION;
	    *lightPtr++ = location[0];
	    *lightPtr++ = location[1];
	    *lightPtr++ = location[2];
	    *lightPtr++ = 0.0;
	    *lightPtr++ = LCOLOR;
	    *lightPtr++ = baseColor[0] * brightness;
	    *lightPtr++ = baseColor[1] * brightness;
	    *lightPtr++ = baseColor[2] * brightness;
	    *lightPtr++ = LMNULL; 

	    if (rgbp)
	    {
	        lmdef(DEFLIGHT, k * 2 + 1, 0, tempLight);

		lmbind(LIGHT0 + k, k * 2 + 1);
	    }

	    {
		/*Make the reverse light*/
		lightPtr = tempLight;
		*lightPtr++ = POSITION;
		*lightPtr++ = -location[0];
		*lightPtr++ = -location[1];
		*lightPtr++ = -location[2];
		*lightPtr++ = 0.0;
		*lightPtr++ = LCOLOR;
		*lightPtr++ = baseColor[0] * brightness;
		*lightPtr++ = baseColor[1] * brightness;
		*lightPtr++ = baseColor[2] * brightness;
		*lightPtr++ = LMNULL; 
		if (rgbp)
		{
		    lmdef(DEFLIGHT, k * 2 + 2, 0, tempLight);
		}
	    }
#endif
	    ++k;
	}
	runner = runner -> next;
    }
    nTotalLights = k;
}

void StopLights(space)
ObjPtr space;
/*Stops lights in space*/
{
    int k;

#ifdef GRAPHICS
    for (k = 0; k < TOTALLIGHTS; ++k)
    {
	if (rgbp)
	{
	    lmbind(LIGHT0 + k, 0);
	}
    }

    if (rgbp)
    {
	lmbind(LMODEL, 0);
    }
#endif
}

ObjPtr NewLight(k)
int k;
/*Returns a new light with default definition k*/
{
    ObjPtr light;
    ObjPtr baseColor;
    ObjPtr name;
    ObjPtr loc;
    real *locPtr;

	light = NewObject(lightClass, 0);

	/*Set light position*/
	loc = NewRealArray(1, 3L);
	locPtr = ArrayMeat(loc);
	*locPtr++ = rcos(defLights[k] . theta) * rcos(defLights[k] . phi);
	*locPtr++ = rsin(defLights[k] . theta) * rcos(defLights[k] . phi);
	*locPtr++ = rsin(defLights[k] . phi);
	SetVar(light, LOCATION, loc);

	/*Set light color*/
	baseColor = NewRealArray(1, 3L);
	CArray2Array(baseColor, defLights[k] . baseColor);
	SetVar(light, BASECOLOR, baseColor);

	/*Set light brightness*/
	SetVar(light, BRIGHTNESS, NewReal(defLights[k] . brightness));

	/*Set whether the light is enabled or not*/
	SetVar(light, HIDDEN, NewInt(defLights[k] . enabled));
	
	name = GetVar(lightClass, NAME);
	if (name)
	{
	    sprintf(tempStr, "%s %d", GetString(name), ++lightsNum);
    	}
	else
	{
	    sprintf(tempStr, "Light %d", ++lightsNum);
	}
	name = NewString(tempStr);
	SetVar(light, NAME, name);
	SetVar(light, SPACES, NewList());

    return light;
}

ObjPtr NewLights()
/*Returns a new list of lights*/
{
    ObjPtr lights;
    int k;

    if (oneLights)
    {
	if (commonLights) return commonLights;
    }

    lights = NewList();
    for (k = 0; k < NLIGHTS; ++k)
    {
	ObjPtr light;

	light = NewLight(k);

	/*Set the light's parent*/
	SetVar(light, PARENT, lights);

	PostfixList(lights, light);
    }

    return lights;
}

static ObjPtr CloneLight(light)
ObjPtr light;
/*Clones a light light*/
{
    ObjPtr newLight;

    newLight = NewLight(0);
    CopyVar(newLight, light, LOCATION);
    CopyVar(newLight, light, BASECOLOR);
    CopyVar(newLight, light, BRIGHTNESS);
    CopyVar(newLight, light, HIDDEN);

    return newLight;
}

static ObjPtr BindLightToSpace(light, space)
ObjPtr light, space;
/*Makes space know about a light.*/
{
    ObjPtr lightsList;

    lightsList = GetVar(space, LIGHTS);
    if (!lightsList)
    {
	lightsList = NewList();
	SetVar(space, LIGHTS, lightsList);
    }
	
    PrefixList(lightsList, light);
    SetVar(light, PARENT, space);

    if (ListCount(lightsList) > TOTALLIGHTS)
    {
	char errmes[256];
	sprintf(errmes, "Only %d lights can be shown.\n", TOTALLIGHTS);
	ReportError("BindLightToSpace", errmes); 
    }
    return ObjTrue;
}

static ObjPtr ChangeLightBrightness(object)
ObjPtr object;
/*Changed value for a light's brightness slider*/
{
    real brightness;
    ObjPtr val;
    ObjPtr repObj;

#ifdef INTERACTIVE
    interactiveMoving = false;
#endif

    repObj = GetObjectVar("ChangeLightBrightness", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    val = GetVar(object, VALUE);
    if (val)
    {
	brightness = GetReal(val);
    }
    else
    {
	brightness = 1.0;
    }

    SetVar(repObj, BRIGHTNESS, NewReal(brightness));
    DrawMe(repObj);
    return ObjTrue;
}

static ObjPtr ChangeLightColor(object)
ObjPtr object;
/*Change light color in response to an HS color manipulation*/
{
    ObjPtr valueArray;
    real value[2];
    ObjPtr rgbArray;
    ObjPtr repObj;
    ObjPtr colors;
    ObjPtr sw;
    real rgb[3];

#ifdef INTERACTIVE
    interactiveMoving = false;
#endif
    repObj = GetObjectVar("ChangeLightColor", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    /*Get the current value of the control*/
    valueArray = GetFixedArrayVar("ChangeBaseColor", object, VALUE, 1, 2L);
    if (!valueArray)
    { 
	return ObjFalse;
    }
    Array2CArray(value, valueArray);

    HSV2RGB(&(rgb[0]), &(rgb[1]), &(rgb[2]), value[0], value[1], 1.0);

    rgbArray = NewRealArray(1, 3L);
    CArray2Array(rgbArray, rgb);

    SetVar(repObj, BASECOLOR, rgbArray);

    /*Draw me, because we may be tracking*/
    DrawMe(repObj);
    return ObjTrue;
}

static ObjPtr HideLight(light)
ObjPtr light;
/*Hides a light*/
{
    SetVar(light, HIDDEN, ObjTrue);
    ImInvalid(light);
    return ObjTrue;
}

static ObjPtr ShowLight(light)
ObjPtr light;
/*Shows a light*/
{
    SetVar(light, HIDDEN, ObjFalse);
    ImInvalid(light);
    return ObjTrue;
}


static ObjPtr lightForRGB;

static void LightRGB(visWindow)
WinInfoPtr visWindow;
/*Sends a deferred message to visWindow if it has lightForRGB*/
{
    ObjPtr space;
    ObjPtr lights;

    space = FindSpace(visWindow);
    if (space)
    {
	lights = GetVar(space, LIGHTS);
	if (lights)
	{
	    ThingListPtr runner;

	    runner = LISTOF(lights);
	    while (runner)
	    {
		if (runner -> thing == lightForRGB)
		{
		    DeferMessage((ObjPtr) visWindow, SETRGBMESSAGE);
		    return;
		}
		runner = runner -> next;
	    }
	}
    }
}

static ObjPtr ShowLightControls(object, windowName)
ObjPtr object;
char *windowName;
/*Makes a new control window to control light*/
{
    WinInfoPtr controlWindow;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr contents;
    ObjPtr lights;
    ThingListPtr runner;
    ObjPtr firstIcon = 0;
    WinInfoPtr dialogExists;
    int k;

    dialogExists = DialogExists((WinInfoPtr) object, NewString("Controls"));
    controlWindow = GetDialog((WinInfoPtr) object, NewString("Controls"), windowName, 
	LWINWIDTH, LWINHEIGHT, LWINWIDTH, LWINHEIGHT, WINDBUF + WINFIXEDSIZE);
    SetVar((ObjPtr) controlWindow, HELPSTRING,
	NewString("This window shows controls for a light source at infinity."));

#ifdef GRAPHICS
    if (hasRGB)
    {
	lightForRGB = object;
	ForAllVisWindows(LightRGB);
    }
#endif

    if (!dialogExists)
    {
	ObjPtr control, checkBox, corral, sw, textBox, slider, icon, colorObj;
	ObjPtr var, button;
	int width, left, right, bottom, top, cellHeight, m1, m2;
	real initValue;
	real baseColor[3];
	real hs[2], dummy;

	SetVar((ObjPtr) controlWindow, REPOBJ, object);

	/*Add in a panel*/
	panel = NewPanel(greyPanelClass, 0, LWINWIDTH, 0, LWINHEIGHT);
	if (!panel)
	{
	    return ObjFalse;
	}
	contents = GetVar((ObjPtr) controlWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) controlWindow);
	ContentsExpectWindowSize(controlWindow, LWINWIDTH, LWINHEIGHT);

	contents = NewList();
	SetVar(panel, CONTENTS, contents);

	width = LWINWIDTH - 2 * CORRALBORDER;
	left = MAJORBORDER;

	cellHeight = COLORWHEELWIDTH;

	/*Precalculate the midlines for convenience*/
	m1 = LWINHEIGHT - MAJORBORDER - cellHeight / 2;

	left += COLORWHEELWIDTH + MAJORBORDER;

	/*Create the color control*/
	control = NewColorWheel(left - COLORWHEELWIDTH - MAJORBORDER, left - MAJORBORDER, 
			m1 - COLORWHEELWIDTH / 2,
			m1 + COLORWHEELWIDTH / 2, "Color");
	SetVar(control, HELPSTRING, NewString("This color wheel controls the base color of a light.  \
It will only have an effect if the window containing the light is in full color mode."));
	PrefixList(contents, control);
	SetVar(control, PARENT, panel);
	SetVar(control, REPOBJ, object);
	SetMethod(control, CHANGEDVALUE, ChangeLightColor);

	var = GetVar(object, BASECOLOR);
	if (var)
	{
	    Array2CArray(baseColor, var);
	}
	else
	{
	    baseColor[0] = 1.0;
	    baseColor[1] = 1.0;
	    baseColor[2] = 1.0;
	}
	RGB2HSV(&(hs[0]), &(hs[1]), &dummy, baseColor[0], baseColor[1], baseColor[2]);
	var = NewRealArray(1, 2L);
	CArray2Array(var, hs);
	SetValue(control, var);

	/*Create the text box*/
	textBox = NewTextBox(left - COLORWHEELWIDTH - MAJORBORDER - 30, left - MAJORBORDER + 30, 
			 m1 - cellHeight / 2 - TEXTBOXSEP - TEXTBOXHEIGHT,
			 m1 - cellHeight / 2 - TEXTBOXSEP,
			 0, "Color Text", "Color");
	PrefixList(contents, textBox);
	SetVar(textBox, PARENT, panel);
	SetTextAlign(textBox, CENTERALIGN);

	left += MAJORBORDER;

	/*Create the brightness slider*/
	var = GetVar(object, BRIGHTNESS);
	if (var && IsReal(var))
	{
	    initValue = GetReal(var);
	}
	else
	{
	    initValue = 1.0;
	}
	slider = NewSlider(left, left + SLIDERWIDTH, 
		       m1 - cellHeight / 2, m1 + cellHeight / 2,
		       PLAIN, "Brightness");
	SetVar(slider, HELPSTRING, NewString("This slider controls the brightness \
of the light.  It will only have an effect if the window containing the light is in full color mode."));
	if (!slider)
	{
	    return ObjFalse;
	}
	PrefixList(contents, slider);
	SetVar(slider, PARENT, panel);
	SetSliderRange(slider, 1.0, 0.0, 0.0);
	SetSliderValue(slider, initValue);
	SetVar(slider, REPOBJ, object);
	SetMethod(slider, CHANGEDVALUE, ChangeLightBrightness);

	/*Create the brightness text box*/
	textBox = NewTextBox(left - MAJORBORDER - MINORBORDER, left + SLIDERWIDTH + MINORBORDER + MAJORBORDER, 
			 m1 - cellHeight / 2 - TEXTBOXSEP - TEXTBOXHEIGHT,
			 m1 - cellHeight / 2 - TEXTBOXSEP,
			 0, "Brightness Text", "Brightness");
	PrefixList(contents, textBox);
	SetVar(textBox, PARENT, panel);
	SetTextAlign(textBox, CENTERALIGN);
    }

    return (ObjPtr) controlWindow;
}

#define LIGHTDSPSTEP 0.1  /*Step to display outward for lights*/
static real globalLightRadius;
ObjPtr DrawLight(light)
ObjPtr light;
/*Draws a light in a space*/
{
    float lightrad = 1.0;
	if (IsSelected(light))
	{
	    ObjPtr var;
	    real brightness;
	    real baseColor[3];
	    real location[3];
	    float clr[3];
	    float azimuth;
	    float xyrange;
	    float elevation;
	
	    lightrad += LIGHTDSPSTEP;

	    var = GetVar(light, BRIGHTNESS);
	    if (var && IsReal(var))
	    {
		brightness = GetReal(var);
	    }
	    else
	    {
		brightness = 1.0;
	    }
	    var = GetVar(light, BASECOLOR);
	    if (var)
	    {
		Array2CArray(baseColor, var);
	    }
	    else
	    {
		baseColor[0] = 1.0;
		baseColor[1] = 1.0;
		baseColor[2] = 1.0;
	    }
	    var = GetFixedArrayVar("DrawLights", light, LOCATION, 1, 3L);
	    if (!var)
	    {
		return;
	    }
	    Array2CArray(location, var);

	    /*Calculate the azimuth*/
	    if (location[0] == 0.0 && location[1] == 0.0)
	    {
		azimuth = 0.0;
	    }
	    else
	    {
		azimuth = atan2(location[1], location[0]);
	    }

	    /*Calculate the xyrange and elevation*/
	    xyrange = (float) sqrt(location[0] * location[0] + location[1] * location[1]);
	    elevation = atan2(location[2], xyrange);
	    if (elevation >= M_PI) elevation -= 2 * M_PI;

	    SetLineWidth(2);
	    SetUIColor(UIGRAY50);

	    /*Draw the horizon circle*/
	    {
		NudgeFarther();
		NudgeFarther();
#ifdef GRAPHICS
		circ(0.0, 0.0, globalLightRadius);
#endif
		NudgeCloser();
		NudgeCloser();
	    }

	    /*Draw the X space line*/
	    SetUIColor(UIWHITE);
	    DrawSpaceLine(0.0, 0.0, 0.0, lightrad * globalLightRadius, 0.0, 0.0);

	    /*Choose a color for subsequent stuff*/
#ifdef GRAPHICS
	    clr[0] = baseColor[0] * (brightness * 0.8 + 0.2);
	    clr[1] = baseColor[1] * (brightness * 0.8 + 0.2);
	    clr[2] = baseColor[2] * (brightness * 0.8 + 0.2);
	    if (rgbp)
	    {
		c3f(clr);
	    }
	    else
	    {
		color(uiColorIndex[ClosestUIColor(clr)]);
	    }
#endif

	    /*Draw the azimuth measurement*/
	    sprintf(tempStr, "%4.0f deg.", (float) azimuth * 180.0 / M_PI);
	    SetupFont("Helvetica-Bold", 14);
	    DrawSpaceString((lightrad + 0.05) * globalLightRadius * cos(azimuth * 0.5),
			    (lightrad + 0.05) * globalLightRadius * sin(azimuth * 0.5),
			    0.0,
			    tempStr);

#ifdef GRAPHICS
	    /*Draw the azimuth circle*/
	    if (azimuth < 0.0)
	    {
		arc(0.0, 0.0, lightrad * globalLightRadius, azimuth * 1800.0 / M_PI, 0);
	    }
	    else
	    {
		arc(0.0, 0.0, lightrad * globalLightRadius, 0, azimuth * 1800.0 / M_PI);
	    }
#endif

	    /*Draw the azimuth line*/
	    DrawSpaceLine(0.0, 0.0, 0.0, lightrad * globalLightRadius * cos(azimuth), lightrad * globalLightRadius * sin(azimuth), 0.0);

#ifdef GRAPHICS
	    /*Draw the elevation arc*/
	    pushmatrix();
	    rotate(900, 'x');
	    rot(azimuth * 180.0 / M_PI, 'y');
	    if (elevation < 0.0)
	    {
		arc(0.0, 0.0, lightrad * globalLightRadius, elevation * 1800.0 / M_PI, 0);
	    }
	    else
	    {
		arc(0.0, 0.0, lightrad * globalLightRadius, 0, elevation * 1800.0 / M_PI);
	    }
#endif

	    /*Draw the elevation measurement*/
	    sprintf(tempStr, "%4.0f deg.", (float) elevation * 180.0 / M_PI);
	    SetupFont("Helvetica-Bold", 14);
	    DrawSpaceString((lightrad + 0.05) * globalLightRadius * cos(elevation * 0.5),
			    (lightrad + 0.05) * globalLightRadius * sin(elevation * 0.5),
			    0.0,
			    tempStr);

#ifdef GRAPHICS
	    popmatrix();
#endif

	    SetLineWidth(3);

	    DrawSpaceLine(0.0, 0.0, 0.0, 30.0 * location[0],
		 30.0 * location[1],
		 30.0 * location[2]);
	    SetLineWidth(1);
	}

}	

#ifdef PROTO
void DrawLights(ObjPtr space, ObjPtr lights, int action, real radius)
#else
void DrawLights(space, lights, action, radius)
ObjPtr space, lights;
int action;
real radius;
#endif
/*Draw lights in space if they want to be displayed
  Action is DRAWSPACE, PICKSPACE, etc.
  Radius is radius of sphere in center area*/
{
#ifdef GRAPHICS
    ThingListPtr runner;
 
    zbuffer(FALSE);
    globalLightRadius = radius;
    runner = LISTOF(lights);
    while (runner)
    {
	ObjPtr light;

	DrawObject(runner -> thing);
	runner = runner -> next;
    }

    zbuffer(TRUE);
#endif
}

static ObjPtr ChangeLightIcon(icon)
ObjPtr icon;
/*Changes a light icon in response to its selection*/
{
    ObjPtr space;
    space = GetObjectVar("ChangeLightIcon", icon, SPACE);
    if (space)
    {
	ImInvalid(space);
    }
    return ObjTrue;
}

static ObjPtr DeleteLightIcon(icon)
ObjPtr icon;
/*Deletes a light object associated with an icon*/
{
    ObjPtr space, contents;
    ObjPtr repObj;

    repObj = GetObjectVar("DeleteLightIcon", icon, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    space = GetVar(icon, SPACE);
    if (!space)
    {
	return ObjFalse;
    }

    SetVar(repObj, SPACE, NULLOBJ);
    contents = GetListVar("DeleteVisObjectIcon", space, LIGHTS);
    if (!contents)
    {
	return ObjFalse;
    }
    DeleteFromList(contents, repObj);
    ImInvalid(space);
    return ObjTrue;
}

static ObjPtr MakeLightAppearance(light)
ObjPtr light;
/*Dummy make for light appearance*/
{
    SetVar(light, APPEARANCE, ObjTrue);
    ImInvalid(light);
    return ObjTrue;
}

void InitLights()
{
    ObjPtr icon;
    ObjPtr list;

#ifdef GRAPHICS
    lmdef(DEFLMODEL, 1, 0, lightModel);
#endif

    /*Create class of lights*/
    lightClass = NewObject(controllerClass, 0);
    AddToReferenceList(lightClass);
    SetMethod(lightClass, BINDTOSPACE, BindLightToSpace);
    SetMethod(lightClass, NEWCTLWINDOW, ShowLightControls);
    SetVar(lightClass, DOUBLECLICK, NewString(OF_SHOW_CONTROLS));
    SetMethod(lightClass, HIDE, HideLight);
    SetMethod(lightClass, SHOW, ShowLight);
    list = NewList();
    PrefixList(list, NewSymbol(HIDDEN));
    SetVar(lightClass, SNAPVARS, list);
    SetMethod(lightClass, IMINVALID, (FuncTyp) 0);
    SetMethod(lightClass, CLONE, CloneLight);
    SetMethod(lightClass, DRAW, DrawLight);
    SetVar(lightClass, CLASSID, NewInt(CLASS_LIGHT));
    DeclareDependency(lightClass, APPEARANCE, SELECTED);
    SetMethod(lightClass, APPEARANCE, MakeLightAppearance);

    icon = NewIcon(0, 0, ICONLIGHTS, "Light");
    SetVar(icon, HELPSTRING,
	NewString("This icon represents a light.  When this icon is selected, \
a ray showing the direction and color of the light will appear in the space.  \
You can rotate the light by pressing and dragging in the space with the center \
mouse button.  You can show the controls for the light, such as the brightness and \
color, by selecting it and choosing the Show Controls item in the Object menu.\n\
\nYou can drag the light icon to the corral of another visualization \
window to have it illuminate that space as well.  A space can hold up to eight lights.  \
The easiest way to add additional lights is to select a light and choose Duplicate from \
the Object menu.")); 
    SetMethod(icon, CHANGEDVALUE, ChangeLightIcon);
    SetMethod(icon, DELETEICON, DeleteLightIcon);
    SetMethod(lightClass, DUPLICATE, CloneObject);
    SetMethod(lightClass, DELETE, DeleteObject);
    SetVar(lightClass, DEFAULTICON, icon);
    SetVar(lightClass, NAME, NewString("Light"));
}

void KillLights()
{
    DeleteThing(lightClass);
}
