/*
 * cd3main.c --
 *
 *     This file contains the implementation of the Tcl interface of the
 *     canvas3d widget. It invokes the public interfaces of other files,
 *     hence "main".
 *
 *-------------------------------------------------------------------------
 */

#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include "c3d.h"

/* The events mask for canvas3d X-windows. */
#define ALL_EVENTS_MASK         \
   (KeyPressMask |              \
    KeyReleaseMask |            \
    ButtonPressMask |           \
    ButtonReleaseMask |         \
    EnterWindowMask |           \
    LeaveWindowMask |           \
    PointerMotionMask |         \
    ExposureMask |              \
    VisibilityChangeMask |      \
    FocusChangeMask |           \
    PropertyChangeMask |        \
    ColormapChangeMask)

/*
 * The GEOMETRY_REQUEST bit is set in the type-mask of the Tk option if
 * changing the option may modify the size of the widget viewport (i.e.
 * -width or -height).
 *
 * The SAVEUNDER_CHANGE bit is set for the -saveunder option only.
 */
#define GEOMETRY_REQUEST 0x0001
#define SAVEUNDER_CHANGE 0x0002

/*
** Declare the functions used by the driver portion of the code
** As we jump to native support files, it's hard to control the
** order in which they are defined
*/
static void C3d_Canvas_EventProc(ClientData clientData, XEvent *eventPtr);
static void C3d_Canvas_Delete(ClientData clientData);
#ifdef C3dFree
static void C3dFreeFunc(ClientData clientData);
#endif

static void C3d_Canvas_DrawWhenIdle(ClientData clientData);
static void C3d_Canvas_WorldChanged(ClientData clientData);

static void C3d_Native_EventProc(ClientData clientData, XEvent *eventPtr);
static void C3d_Native_Select3dContext(C3dWidget *pCanvas);
static void C3d_Native_ResizeContext(C3dWidget *pCanvas,int w,int h);
static void C3d_Native_DeleteContext(C3dWidget *pCanvas);
static void C3d_Native_FreePixMaps(C3dWidget *pCanvas);
static Window C3d_Native_CreateCanvas3d(Tk_Window tkwin, Window parent, ClientData clientData);
static void C3d_Native_Select3dContext(C3dWidget *pCanvas);
static void C3d_Native_Release3dContext(C3dWidget *pCanvas);
static void C3d_Native_Pixmap3dToPixmap(C3dWidget *pCanvas);
static void C3d_Native_PixmapToPixmap3d(C3dWidget *pCanvas);
static void C3d_Native_PixmapToWindow(C3dWidget *pCanvas);
static int C3d_Native_Init(Tcl_Interp *interp);
/*
 *---------------------------------------------------------------------------
 *
 * vertexOptionSet --
 *
 *     The functions vertexOptionSet(), vertexOptionGet(),
 *     vertexOptionFree() and vertexOptionRestore() implement a Tk option
 *     type that may be set to a list of three floating point numbers. i.e.
 *     {0 1 2} or {0.5 0.2 -10.8}.
 *
 *     The internal type of the option is (C3dVertex*). This is set to
 *     point at a dynamically allocated C3dVertex struct containing the
 *     option value.
 *
 *     See the manpage for Tk_SetOptions for more information.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
vertexOptionSet (
    ClientData clientData,         /* Not used */
    Tcl_Interp *interp,            /* Report errors here */
    Tk_Window tkwin,               /* Not used */
    Tcl_Obj **valuePtr,            /* Pointer to new vertex */
    char *recordPtr,               /* Pointer to C3dWidgetOptions */
    int intOffset,                 /* Offset to C3dVertex* member */
    char *saveIntPtr,              /* Space to save the old vertex */
    int flags                     /* Not used */
) {
    Tcl_Size nCoord;
    Tcl_Obj **aCoord;
    Tcl_Obj *pList = *valuePtr;
    double x, y, z;
    C3dVertex *pVertex = *(C3dVertex **)(recordPtr + intOffset);

    if (Tcl_ListObjGetElements(interp, pList, &nCoord, &aCoord)) {
        return TCL_ERROR;
    }
    if (nCoord != 3) {
        Tcl_ResetResult(interp);
        Tcl_AppendResult(interp, "Vertex requires 3 coordinates", 0);
        return TCL_ERROR;
    }
    if (Tcl_GetDoubleFromObj(interp, aCoord[0], &x) ||
        Tcl_GetDoubleFromObj(interp, aCoord[1], &y) ||
        Tcl_GetDoubleFromObj(interp, aCoord[2], &z)
    ) {
        return TCL_ERROR;
    }

    if (saveIntPtr) {
        *(C3dVertex **)saveIntPtr = pVertex;
        pVertex = 0;
    }

    if (!pVertex) {
        pVertex = (C3dVertex *)C3dAlloc(sizeof(C3dVertex));
    }
    pVertex->x = x;
    pVertex->y = y;
    pVertex->z = z;

    *(C3dVertex **)(recordPtr + intOffset) = pVertex;
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * vertexOptionGet --
 *
 *     See comments for vertexOptionSet().
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static Tcl_Obj *
vertexOptionGet(
    ClientData clientData,              /* Not used */
    Tk_Window tkwin,                    /* Not used */
    char *recordPtr,                    /* Pointer to C3dWidgetOptions */
    int intOffset                      /* Offset to C3dVertex* member */
) {
    C3dVertex *pVertex = *(C3dVertex **)(recordPtr + intOffset);
    Tcl_Obj *a[3];
    a[0] = Tcl_NewDoubleObj(pVertex->x);
    a[1] = Tcl_NewDoubleObj(pVertex->y);
    a[2] = Tcl_NewDoubleObj(pVertex->z);
    return Tcl_NewListObj(3, a);
}

/*
 *---------------------------------------------------------------------------
 *
 * vertexOptionRestore --
 *
 *     See comments for vertexOptionSet().
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
vertexOptionRestore(
    ClientData clientData,
    Tk_Window tkwin,
    char *internalPtr,
    char *saveInternalPtr
) {
    assert(internalPtr);
    assert(saveInternalPtr);

    assert(!*(C3dVertex **)internalPtr);
    *(C3dVertex **)internalPtr = *(C3dVertex **)saveInternalPtr;
    *(C3dVertex **)saveInternalPtr = 0;
}

/*
 *---------------------------------------------------------------------------
 *
 * vertexOptionFree --
 *
 *     See comments for vertexOptionSet().
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
vertexOptionFree(
    ClientData clientData,
    Tk_Window tkwin,
    char *internalPtr
) {
    C3dVertex *pVertex = *(C3dVertex **)internalPtr;
    if (pVertex) {
        C3dFree((char *)pVertex);
        *(C3dVertex **)internalPtr = 0;
    }
}

/*
 * The static Tk_ObjCustomOption instance for the "vertex" option type
 * implemented by vertexOptionSet(), vertexOptionGet(),
 * vertexOptionRestore() and vertexOptionFree(). See comments above
 * vertexOptionSet() for more information.
 */
static Tk_ObjCustomOption VertexOption = {
    "vertex",
    vertexOptionSet,
    vertexOptionGet,
    vertexOptionRestore,
    vertexOptionFree,
    0
};

/*
 * Possible values for the C3dWidgetOptions.saveunder variable:
 */
#define SAVEUNDER_NONE 0
#define SAVEUNDER_3D   1
#define SAVEUNDER_ALL  2
static const char * SaveunderStrings[] = {"none", "3d", "all", 0};

/*
 * The option_spec array defines the widget options accepted by the
 * [$win configure], [$win cget] and [canvas3d] commands. To neaten things
 * up, Tk_OptionSpec instances are populated using the WIDGET_OPTION macro.
 */
#define WIDGET_OPTION6(t, n, N, d, m, c) {                    \
    TK_OPTION_ ## t, "-" #n, #n, N, d, -1,                    \
    Tk_Offset(C3dWidgetOptions, n), TK_OPTION_NULL_OK, c, m   \
}
#define WIDGET_OPTION(t, n, N, d, m) WIDGET_OPTION6(t, n, N, d, m, 0)
#define WIDGET_VERTEX_OPTION(n,N,d) WIDGET_OPTION6(CUSTOM,n,N,d,0,&VertexOption)

#define WIDGET_OPTION_ALIAS(a, n) {                        \
    TK_OPTION_SYNONYM, "-" #a, (char*)NULL, (char*)NULL,   \
	(char*)NULL, 0, -1, 0, (ClientData) "-" #n, 0      \
}

static Tk_OptionSpec option_spec[] = {

    WIDGET_OPTION(DOUBLE,  closeenough,    "Closeenough", "1.0", 0),
    WIDGET_OPTION(COLOR,   background,     "Background", "black", 0),
    WIDGET_OPTION_ALIAS(   bg,             background),
    WIDGET_VERTEX_OPTION(  cameracenter,   "Cameracenter", "0.0 0.0 0.0"),
    WIDGET_VERTEX_OPTION(  cameralocation, "Cameralocation", "0.0 0.0 1.0"),
    WIDGET_VERTEX_OPTION(  cameraup,       "Cameraup", "0.0 1.0 0.0"),
    WIDGET_OPTION(BOOLEAN, enablealpha,    "Enablealpha", "false", 0),
    WIDGET_OPTION(CURSOR,  cursor,         "Cursor", "", 0),
    WIDGET_OPTION(PIXELS,  height,         "Height", "300", GEOMETRY_REQUEST),
    WIDGET_OPTION(DOUBLE,  visibleangle,   "Visibleangle", "60.0", 0),
    WIDGET_OPTION(PIXELS,  width,          "Width", "400", GEOMETRY_REQUEST),

    WIDGET_OPTION6(STRING_TABLE,
        saveunder, "Saveunder", "none", SAVEUNDER_CHANGE, SaveunderStrings
    ),

    {TK_OPTION_END, 0, 0, 0, 0, 0, 0, 0, 0}
};

/*
** Each platform defines:
** Tk_ClassProcs C3dCanvasProcs -> Array of functions exposed directly to TK
** C3d_Native_Select3dContext -> Function to select a 3d window for GL rendering
** C3d_Native_ResizeContext -> Function to syncronize the size of the GL windows with the Tk Window
** C3d_Native_DeleteContext -> Function to delete a GL context after TK destroyed
** C3d_Native_CreateCanvas3d -> Function to produce an openGL context and canvas
** C3d_Native_Pixmap3dToPixmap -> Function to Copy from C3dPixmap.pixmap to C3dPixmap.pixmap3d
** C3d_Native_PixmapToPixmap3d -> Function to Copy from C3dPixmap.pixmap3d to C3dPixmap.pixmap
** C3d_Native_PixmapToWindow -> Copy pixmap to main window
** C3d_Native_EventProc -> Function to handle <Expose> events
**
*/

#if defined(CAN3D_WGL)
# include "c3dWinMain.c"
#elif defined(CAN3D_NSOPENGL)
# include "c3dCocoaMain.c"
#elif defined(CAN3D_AGL)
# include "c3dCarbonMain.c"
#elif defined(CAN3D_SDL)
# include "c3dSDLMain.c"
#else
# include "c3dX11Main.c"
#endif

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Canvas_DrawNow --
 *
 *     Redraw the entire widget. This function also resizes the X-window if
 *     the size of the Tk window has changed. This function is usually
 *     invoked as a Tcl idle callback.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     Draws the current scene to the widget window.
 *
 *---------------------------------------------------------------------------
 */
void
C3d_Canvas_DrawNow(C3dWidget *pCanvas)
{
    Tcl_HashSearch search;
    Tcl_HashEntry *pEntry;
    Tk_Window win = pCanvas->tkwin;

    Tk_MakeWindowExist(win);
    if (!Tk_IsMapped(win)) return;

    if (!pCanvas->eSelectMode) {
        glEnable(GL_DEPTH_TEST);
        glShadeModel(GL_SMOOTH);

        /* Clear the window. */
        glClearColor(
            pCanvas->options.background.aChannel[0],
            pCanvas->options.background.aChannel[1],
            pCanvas->options.background.aChannel[2],
            pCanvas->options.background.aChannel[3]
        );
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

        /* Enable blending if the -enablealpha option is set */
        if (pCanvas->options.enablealpha) {
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        } else {
            glDisable(GL_BLEND);
        }
    }
    /* Position the camera and reset the modelview matrix. */
    C3dDrawCamera(pCanvas);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    /* Configure the light sources, if any. */
    C3dDrawLights(pCanvas);

    /* Draw all the other (non-light) 3d Open-GL primitives. */
    if( pCanvas->validNotHiddenList ){
      C3dItem *pItem;
      for(pItem = pCanvas->pNotHidden; pItem; pItem=pItem->pNextNotHidden){
        C3dItemDraw(pCanvas, pItem);
      }
    }else{
      C3dItem **ppTail;
      ppTail = &pCanvas->pNotHidden;
      for(pEntry=Tcl_FirstHashEntry(&pCanvas->aItemsById, &search);
          pEntry;
          pEntry=Tcl_NextHashEntry(&search)
      ){
          C3dItem *pItem = Tcl_GetHashValue(pEntry);
          if( pItem->hidden ) continue;
          *ppTail = pItem;
          ppTail = &pItem->pNextNotHidden;
          C3dItemDraw(pCanvas, pItem);
      }
      *ppTail = 0;
      pCanvas->validNotHiddenList = 1;
    }

    C3dItemDrawDoCallbacks(pCanvas);

    /* On X11, if the "-saveunder" mode is "3d", then we copy the scene as
     * currently rendered to pixmap C3dWidget.pixmap3d.  On windows, if the
     * "-saveunder" mode is anything but "none", then we copy from the DIB to
     * C3dWidget.pixmap.
     */
    if (pCanvas->options.saveunder) {
        glFlush();
    }
}

static void
C3d_Canvas_ApplyOverlay(C3dWidget *pCanvas)
{
#ifdef CAN3D_WGL
    if (pCanvas->options.saveunder != SAVEUNDER_NONE) {
        C3d_Native_Pixmap3dToPixmap(pCanvas);
    }
#else
    if (pCanvas->options.saveunder == SAVEUNDER_3D) {
        C3d_Native_PixmapToPixmap3d(pCanvas);
    }
#endif
    /* Draw overlay items and flush the OpenGL rendering pipeline. */
    C3dItemDrawOverlay(pCanvas);
    if (!pCanvas->options.saveunder) {
        glFlush();
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Canvas_DrawWhenIdle --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Canvas_DrawWhenIdle(ClientData clientData)
{
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    Tk_Window win = pCanvas->tkwin;
    int w, h;

    Tk_MakeWindowExist(win);
    w = Tk_Width(win);
    h = Tk_Height(win);

    /* Check if the window size has changed since any pixmaps were allocated.
     * If so, delete the existing pixmaps and set the operation to
     * STATE_REDRAW_ALL. C3d_Native_FreePixMaps() is a no-op if no pixmaps have been
     * allocated.
     */
    if (
        pCanvas->pixmapwidth != w ||
        pCanvas->pixmapheight != h
    ) {
        C3d_Native_FreePixMaps(pCanvas);
        pCanvas->eState = STATE_REDRAW_ALL;
    }

    /* At this point the widget state may be anything except STATE_NONE.
     * Some states can only apply in certain -saveunder modes. Adjust the
     * pCanvas->eState variable accordingly.
     */
    assert(pCanvas->eState != STATE_NONE);
    switch (pCanvas->options.saveunder) {
        case SAVEUNDER_NONE:
            /* Widgets configured with "-saveunder none" have no cache of
             * the scene. Everything is always redrawn from scratch, even
             * for an expose event.
             */
            pCanvas->eState = STATE_REDRAW_ALL;
            break;
        case SAVEUNDER_ALL:
            /* Widgets configured with "-saveunder all" cannot redraw the
             * overlay layer without also redrawing the 3d bit.
             */
            if (pCanvas->eState == STATE_REDRAW_OVERLAY) {
                pCanvas->eState = STATE_REDRAW_ALL;
            }
            break;
        case SAVEUNDER_3D:
            /* Widgets configured with "-saveunder 3d" can handle all
             * kinds of redraw requests.
             */
            break;
        default:
            assert(!"Can't happen");
    }

    switch (pCanvas->eState) {
        case STATE_REDRAW_WINDOW: {
            /* The window is dirty, but C3dWidget.pixmap contains a valid
             * render of the entire scene. All that is required is to copy
             * from C3dWidget.pixmap to the window.
             */
            C3d_Native_PixmapToWindow(pCanvas);
            break;
        }
        case STATE_REDRAW_ALL:
            C3d_Native_Select3dContext(pCanvas);
            C3d_Canvas_DrawNow(pCanvas);
            C3d_Canvas_ApplyOverlay(pCanvas);
            C3d_Native_PixmapToWindow(pCanvas);
            break;
        case STATE_REDRAW_OVERLAY: {
            C3d_Canvas_ApplyOverlay(pCanvas);
            C3d_Native_PixmapToWindow(pCanvas);
            break;
        }
    }

    pCanvas->eState = STATE_NONE;
}

/*
 *---------------------------------------------------------------------------
 *
 * C3dDrawWhenIdle --
 *
 *     Set up an idle callback to redraw the widget. If such a callback is
 *     already pending, this function is a no-op.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     May set up an idle callback.
 *
 *---------------------------------------------------------------------------
 */
void
C3dDrawWhenIdle(C3dWidget *pCanvas, int state)
{

  if( !pCanvas->eState && Tk_WindowId(pCanvas->tkwin) ){
    Tcl_DoWhenIdle(C3d_Canvas_DrawWhenIdle, (ClientData)pCanvas);
  }
  pCanvas->eState |= state;
}


/*
 *---------------------------------------------------------------------------
 *
 * C3d_Canvas_EventProc --
 *
 *     Widget callback for <Expose> events. In this case we need to redraw
 *     the scene. We don't do the drawing immediately, instead
 *     C3dDrawWhenIdle() is invoked.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Canvas_EventProc(ClientData clientData, XEvent *eventPtr)
{
  C3dWidget *pCanvas = (C3dWidget *)clientData;
  switch (eventPtr->type) {
    case Expose:
        /* Ignore expose events on an unmapped window */
        if(!Tk_IsMapped(pCanvas->tkwin)) break;
        if (eventPtr->xexpose.count == 0) {
            if (eventPtr->xexpose.window == Tk_WindowId(pCanvas->tkwin)) {
                C3dDrawWhenIdle(pCanvas, STATE_REDRAW_WINDOW);
            }
        }
        break;
    case ConfigureNotify:
        C3dDrawWhenIdle(pCanvas, STATE_REDRAW_ALL);
        break;
    case DestroyNotify:
        C3d_Canvas_Delete(pCanvas);
        return;  /* no native event processing at this point */
  }
  /* Give the native implementations a chance to respond */
  C3d_Native_EventProc(clientData,eventPtr);
  return;
}


/*
 * C3d_Canvas_WorldChanged
 */
static void
C3d_Canvas_WorldChanged(ClientData clientData)
{
  C3dWidget *pCanvas = (C3dWidget *)clientData;

  Tk_GeometryRequest(pCanvas->tkwin, Tk_Width(pCanvas->tkwin),
          Tk_Height(pCanvas->tkwin));
  Tk_SetInternalBorder(pCanvas->tkwin, 0);

  if (Tk_IsMapped(pCanvas->tkwin)) {
    C3dDrawWhenIdle(pCanvas, STATE_REDRAW_ALL);
  }
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Canvas_Delete --
 *
 *     This function is registered with Tcl to be called when a widget
 *     instance is destroyed. Parameter clientData is a pointer to the
 *     C3dWidget structure that needs to be cleaned up.
 *     This function is invoked when a widget command is deleted. If the
 *     widget isn't already in the process of being destroyed, this command
 *     destroys it.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     Deletes the C3dWidget structure and other resources allocated for
 *     the deleted widget.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Canvas_Delete(ClientData clientData)
{
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    Tk_Window tkwin = pCanvas->tkwin;
    Tcl_HashEntry *pEntry;
    Tcl_HashSearch search;

    if (!pCanvas) {
      return;
    }

    if (tkwin != NULL) {
      Tcl_DeleteCommandFromToken(pCanvas->interp, pCanvas->widgetCmd);
    }

    Tcl_CancelIdleCall(C3d_Canvas_DrawWhenIdle, (ClientData) pCanvas);
    pCanvas->eState=STATE_NONE;

    for (pEntry = Tcl_FirstHashEntry(&pCanvas->aItemsById, &search);
         pEntry;
         pEntry = Tcl_NextHashEntry(&search)
    ){
        C3dItem *pItem = Tcl_GetHashValue(pEntry);
        C3dRemoveTagFromItem(pCanvas, 0, pItem);
        C3dItemDelete(pItem);
    }
    Tcl_DeleteHashTable(&pCanvas->aItemsById);
    Tcl_DeleteHashTable(&pCanvas->aItemsByTag);

    if (pCanvas->aDrawCallback) {
      C3dFree((char *)pCanvas->aDrawCallback);
      pCanvas->aDrawCallback=NULL;
    }

    if (pCanvas->context) {
      C3d_Native_DeleteContext(pCanvas);
    }

    C3d_Native_FreePixMaps(pCanvas);

    if (tkwin != NULL) {
      char *pOptions = (char *)&pCanvas->options;
      pCanvas->tkwin = NULL;
      Tk_DeleteEventHandler(
        tkwin, ExposureMask|StructureNotifyMask, C3d_Canvas_EventProc, pCanvas);
      Tk_FreeConfigOptions(pOptions, pCanvas->optionTable, tkwin);
    }

#ifdef C3dFree
    Tcl_EventuallyFree(pCanvas, (Tcl_FreeProc *) C3dFreeFunc);
#else
    Tcl_EventuallyFree(pCanvas, (Tcl_FreeProc *) C3dFree);
#endif
}

#ifdef C3dFree
static void
C3dFreeFunc(ClientData clientData)
{
    C3dFree(clientData);
}
#endif

/*
 *---------------------------------------------------------------------------
 *
 * C3d_CanvasCmdDeletedProc --
 *
 *     This function is invoked when a widget command is deleted. If the
 *     widget isn't already in the process of being destroyed, this command
 *     destroys it.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     Deletes the C3dWidget structure and other resources allocated for
 *     the deleted widget.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_CanvasCmdDeletedProc(ClientData clientData)
{
  C3dWidget *pCanvas = (C3dWidget *)clientData;
  Tk_Window tkwin = pCanvas->tkwin;
  if (tkwin != NULL) {
    Tk_DestroyWindow(tkwin);
  }
}
/*
** This section of code defines the methods and helper functions
** for instances of the Tk widget
*/


/*
 *---------------------------------------------------------------------------
 *
 * itemDamaged --
 *
 *     The item pItem has been modified in some way. This function
 *     calls C3dDrawWhenIdle() to schedule a drawing callback to update the
 *     display.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
itemDamaged(C3dItem *pItem)
{
    int s = pItem->pType->xOverlay ? STATE_REDRAW_OVERLAY : STATE_REDRAW_ALL;
    C3dDrawWhenIdle(pItem->pCanvas, s);
}

/*
 *---------------------------------------------------------------------------
 *
 * addtagSearchCb --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
addtagSearchCb(ClientData clientData, C3dItem *pItem)
{
    int i;
    struct AddTagContext *pContext = (struct AddTagContext *)clientData;
    assert(pContext && pContext->nTag > 0);
    for (i = 0; i < pContext->nTag; i++) {
        int rc = C3dAddTagToItem(pContext->pCanvas, pContext->aTag[i], pItem);
        if (rc != TCL_OK) {
            return rc;
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandAddtag --
 *
 *     $win addtag search tag ?tag ...?
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandAddtag(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    struct AddTagContext sContext;
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    int flags = SEARCHFLAG_MODIFIES_TAGS;

    if (objc < 4) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH TAG ?TAG ...?");
        return TCL_ERROR;
    }

    sContext.pCanvas = pCanvas;
    sContext.nTag = objc - 3;
    sContext.aTag = &objv[3];

    return C3dSearch(pCanvas, objv[2], flags, addtagSearchCb, &sContext);
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandBoundingsphere --
 *
 *     $win boundingsphere search
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandBoundingsphere(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    int valid;
    float x, y, z, r;

    Tcl_Obj *pRet;
    Tcl_Obj *aRet[4];

    if (objc < 3) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH");
        return TCL_ERROR;
    }

    if (TCL_OK != C3dBoundingSphere(pCanvas, objv[2], &valid, &r, &x, &y, &z)) {
        return TCL_ERROR;
    }

    if (valid) {
        aRet[0] = Tcl_NewDoubleObj(r);
        aRet[1] = Tcl_NewDoubleObj(x);
        aRet[2] = Tcl_NewDoubleObj(y);
        aRet[3] = Tcl_NewDoubleObj(z);
        pRet = Tcl_NewListObj(4, aRet);
        Tcl_SetObjResult(interp, pRet);
    } else {
        Tcl_ResetResult(interp);
    }
    return TCL_OK;
}

static int
bboxSearchCb(ClientData clientData, C3dItem *pItem)
{
    struct BboxContext *pContext = (struct BboxContext *)clientData;
    int i;

    if (pItem->pType->xDraw && !pItem->pType->xOverlay) {
        for (i = 0; i < pItem->nVertex; i++) {
            C3dVertex sVertex;
            int winx, winy;

            memcpy(&sVertex, &pItem->aVertex[i], sizeof(C3dVertex));
            C3dTransformApply(pContext->pProjection, &sVertex);
            winx = ((double)(pContext->width) * (sVertex.x + 1.0) / 2.0);
            winy = ((double)(pContext->height) * (sVertex.y + 1.0) / 2.0);

            if (pContext->valid) {
                pContext->xmin = MIN((int)winx, pContext->xmin);
                pContext->ymin = MIN((int)winy, pContext->ymin);
                pContext->xmax = MAX((int)winx, pContext->xmax);
                pContext->ymax = MAX((int)winy, pContext->ymax);
            } else {
                pContext->xmax = pContext->xmin = winx;
                pContext->ymax = pContext->ymin = winy;
                pContext->valid = 1;
            }
        }
    }

    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandBbox --
 *
 *     $win bbox SEARCH
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandBbox(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    struct BboxContext sContext;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH");
        return TCL_ERROR;
    }

    /* Position the camera */
    C3dDrawCamera(pCanvas);

    memset(&sContext, 0, sizeof(struct BboxContext));
    sContext.pCanvas = pCanvas;
    sContext.pProjection = C3dTransformProjection(pCanvas);
    assert(sContext.pProjection);
    sContext.width = Tk_Width(pCanvas->tkwin);
    sContext.height = Tk_Height(pCanvas->tkwin);
    if (C3dSearch(pCanvas, objv[2], 0, bboxSearchCb, &sContext)) {
        return TCL_ERROR;
    }
    C3dTransformDelete(sContext.pProjection);

    if (sContext.valid) {
        Tcl_Obj *apElem[4];
        apElem[0] = Tcl_NewIntObj(sContext.xmin);
        apElem[1] = Tcl_NewIntObj(sContext.ymin);
        apElem[2] = Tcl_NewIntObj(sContext.xmax);
        apElem[3] = Tcl_NewIntObj(sContext.ymax);
        Tcl_SetObjResult(interp, Tcl_NewListObj(4, apElem));
    } else {
        Tcl_ResetResult(interp);
    }

    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * findSearchCb --
 *
 *     Callback for a [$win find search] search. The clientData pointer
 *     points to a Tcl list object. This callback appends the item id of
 *     pItem to that list.
 *
 *     This callback is used if there is no -sort option passed to the
 *     find command. If there is a -sort option, then findSortSearchCb() is
 *     used instead.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
findSearchCb(ClientData clientData, C3dItem *pItem)
{
    Tcl_Obj *pList = (Tcl_Obj *)clientData;
    return Tcl_ListObjAppendElement(0, pList, Tcl_NewIntObj(pItem->id));
}

/*
 *---------------------------------------------------------------------------
 *
 * findSortSearchCompare --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
findSortSearchCompare(const void *left, const void *right)
{
    struct _findsearchitem *pLeft = (struct _findsearchitem *)left;
    struct _findsearchitem *pRight = (struct _findsearchitem *)right;

    if (pLeft->z < pRight->z) return -1;
    if (pLeft->z > pRight->z) return 1;
    return 0;
}

/*
 *---------------------------------------------------------------------------
 *
 * findSortSearchCb --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
findSortSearchCb(ClientData clientData, C3dItem *pItem)
{
    float z;
    struct FindSearchContext *pContext = (struct FindSearchContext *)clientData;
    if (pContext->nItems == pContext->nAlloc) {
        int sz = sizeof(*pContext->aItems);
        int n = pContext->nItems * 2 + 10;
        char *a = (char *)pContext->aItems;
        pContext->aItems = (struct _findsearchitem *)C3dRealloc(a, n * sz);
        pContext->nAlloc = n;
    }

    z = C3dItemDepth(pContext->pTransform, pItem);
    pContext->aItems[pContext->nItems].z = z;
    pContext->aItems[pContext->nItems].id = pItem->id;
    pContext->nItems++;

    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandFind --
 *
 *     $win find ?-sortbydepth? SEARCH
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandFind(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    Tcl_Obj *pRes;
    int rc;
    int i;
    int sortbydepth = 0;
    C3dWidget *pCanvas = (C3dWidget *)clientData;

    if (objc < 3) {
        Tcl_WrongNumArgs(interp, 2, objv, "?OPTIONS? SEARCH");
        return TCL_ERROR;
    }

    /* TODO: Check options properly */
    for (i = 2; i < objc-1; i++) {
       const char *zOption = Tcl_GetString(objv[i]);
       if (!strcmp("-sortbydepth", zOption)) {
           sortbydepth = 1;
       } else {
           Tcl_ResetResult(interp);
           Tcl_AppendResult(interp, "Bad option \"", zOption, "\": must be", 0);
           Tcl_AppendResult(interp, " -sortbydepth", 0);
           return TCL_ERROR;
       }
    }
    pRes = Tcl_NewObj();
    C3dIncrRefCount(pRes);

    if (!sortbydepth) {
        rc = C3dSearch(pCanvas, objv[2], 0, findSearchCb, pRes);
    } else {
        struct FindSearchContext sContext;
        memset(&sContext, 0, sizeof(struct FindSearchContext));
        sContext.pTransform = C3dTransformProjection(pCanvas);

        rc = C3dSearch(pCanvas, objv[3], 0, findSortSearchCb, &sContext);
        if (rc == TCL_OK) {
            int sz = sizeof(*sContext.aItems);
            qsort(sContext.aItems, sContext.nItems, sz, findSortSearchCompare);
            for (i = 0; i < sContext.nItems; i++) {
                Tcl_Obj *pItem = Tcl_NewIntObj(sContext.aItems[i].id);
                Tcl_ListObjAppendElement(0, pRes, pItem);
            }
        }

        C3dFree((char *)sContext.aItems);
    }

    if (rc == TCL_OK) {
        Tcl_SetObjResult(interp, pRes);
    }
    C3dDecrRefCount(pRes);
    return rc;
}

/*
 *---------------------------------------------------------------------------
 *
 * itemcgetSearchCb --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
itemcgetSearchCb(ClientData clientData, C3dItem *pItem)
{
    struct ItemCgetContext *p = (struct ItemCgetContext *)clientData;
    return C3dItemCget(p->pCanvas, pItem, p->pObj);
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandItemcget --
 *
 *     pathName itemcget SEARCH option
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandItemcget(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    struct ItemCgetContext sContext;

    if (objc != 4) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH -option");
        return TCL_ERROR;
    }
    Tcl_ResetResult(interp);

    sContext.pCanvas = pCanvas;
    sContext.pObj = objv[3];

    return C3dSearch(pCanvas, objv[2], 1, itemcgetSearchCb, &sContext);
}

/*
 *---------------------------------------------------------------------------
 *
 * itemconfigureSearchCb --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
itemconfigureSearchCb(ClientData clientData, C3dItem *pItem)
{
    int rc;
    struct ItemConfigureContext *p = (struct ItemConfigureContext *)clientData;

    switch (p->objc) {
        case 0:
            rc = C3dItemConfigureInfo(p->pCanvas, pItem, 0);
            break;
        case 1:
            rc = C3dItemConfigureInfo(p->pCanvas, pItem, p->objv[0]);
            break;
        default: {
            rc = C3dItemConfigure(p->pCanvas,pItem, p->objc, p->objv);
            if( p->nItem==p->nItemAlloc ){
                p->nItemAlloc = 2 * (p->nItemAlloc + 32);
                p->apItem = (C3dItem **)C3dRealloc(
                        (char *)p->apItem, p->nItemAlloc * sizeof(C3dItem *)
                );
            }
            p->apItem[p->nItem] = pItem;
            p->nItem++;
            break;
        }
    }

    return rc;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandItemconfigure --
 *
 *     $win itemconfigure SEARCH
 *     $win itemconfigure SEARCH -option
 *     $win itemconfigure SEARCH -option value ?-option value...?
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandItemconfigure(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    int rc;                                 /* Return code */
    struct ItemConfigureContext sContext;   /* Passed to search callback */
    int flags = 0;                          /* Search flags */

    if (objc < 3) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH ?-option ?value? ...?");
        return TCL_ERROR;
    }
    Tcl_ResetResult(interp);

    memset(&sContext, 0, sizeof(struct ItemConfigureContext));
    sContext.pCanvas = pCanvas;
    sContext.objc = objc - 3;
    sContext.objv = &objv[3];

    if (sContext.objc < 2) {
        /* If this command is querying the current options (as opposed to
        ** setting new values for options), then we can quit searching after
        ** finding the first item. Set the SEARCHFLAG_ONEONLY flag to
        ** get this behaviour out of C3dSearch().
        */
        flags |= SEARCHFLAG_ONEONLY;
    } else {
        /* TODO: This flag only really needs to be set if the -tags option
         * is present. Other operations would be more efficient if we could
         * leave this flag clear.
         */
        flags |= SEARCHFLAG_MODIFIES_TAGS;
    }

    rc = C3dSearch(pCanvas, objv[2], flags, itemconfigureSearchCb, &sContext);

    assert(sContext.objc>1 || !sContext.apItem);
    if (sContext.objc > 1) {
        /* If sContext.objc is greater than 1, then this command is setting
        ** new values for options, not merely querying the current values
        ** of options. So either C3dItemConfigureCommit() or Rollback()
        ** needs to be called on each item written.
        */
        int ii;
        for(ii=0; ii<sContext.nItem; ii++){
            if (rc == TCL_OK) {
                C3dItemConfigureCommit(sContext.apItem[ii]);
            } else {
                C3dItemConfigureRollback(sContext.apItem[ii]);
            }
        }
        C3dFree((char *)sContext.apItem);
#if 0
        Tcl_HashEntry *pEntry;
        Tcl_HashSearch search;
        for(pEntry = Tcl_FirstHashEntry(&pCanvas->aItemsById, &search);
            pEntry;
            pEntry = Tcl_NextHashEntry(&search)
        ) {
            C3dItem *pItem = Tcl_GetHashValue(pEntry);
            if (pItem->flags & ITEMFLAG_SAVEDOPTIONS_VALID) {
               if (rc == TCL_OK) {
                    C3dItemConfigureCommit(pItem);
               } else {
                    C3dItemConfigureRollback(pItem);
               }
            }
        }
#endif
    }
    pCanvas->validNotHiddenList = 0;
    C3dDrawWhenIdle(pCanvas, STATE_REDRAW_ALL);

    return rc;
}

/*
 *---------------------------------------------------------------------------
 *
 * gettagsSearchCb --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
gettagsSearchCb(ClientData clientData, C3dItem *pItem)
{
    Tcl_Obj *pList = (Tcl_Obj *)clientData;

#ifndef NDEBUG
    Tcl_Size len;
    int rc = Tcl_ListObjLength(0, pList, &len);
    assert(rc == TCL_OK);
    assert(len == 0);
#endif

    return C3dGetTags(pItem, pList);
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandGettags --
 *
 *     $win gettags SEARCH
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandGettags(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    int rc;
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    Tcl_Obj *pRet;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH");
        return TCL_ERROR;
    }

    pRet = Tcl_NewObj();
    C3dIncrRefCount(pRet);

    rc = C3dSearch(pCanvas, objv[2], 1, gettagsSearchCb, pRet);
    if (rc == TCL_OK) {
        Tcl_SetObjResult(interp, pRet);
    }

    C3dDecrRefCount(pRet);
    return rc;
}

/*
 *---------------------------------------------------------------------------
 *
 * statisticsSearchCb --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
statisticsSearchCb(ClientData clientData, C3dItem *pItem)
{
    C3dStatistics *pStatistics = (C3dStatistics *)clientData;
    C3dItemStatistics(pItem, pStatistics);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandStatistics --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandStatistics(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    C3dStatistics sStatistics;
    Tcl_Obj *pRet;
    C3dWidget *pCanvas = (C3dWidget *)clientData;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH");
        return TCL_ERROR;
    }

    memset(&sStatistics, 0, sizeof(C3dStatistics));
    if (C3dSearch(pCanvas, objv[2], 0, statisticsSearchCb, &sStatistics)) {
        return TCL_ERROR;
    }

    pRet = Tcl_NewObj();
    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj("nVertex",-1));
    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(sStatistics.nVertex));
    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj("nFace",-1));
    Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(sStatistics.nFace));
    Tcl_SetObjResult(interp, pRet);

    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * transformSearchCb --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
transformSearchCb(ClientData clientData, C3dItem *pItem)
{
    int i;
    C3dTransform *pTransform = (C3dTransform *)clientData;
    for (i = 0; i < pItem->nVertex; i++) {
        C3dTransformApply(pTransform, &pItem->aVertex[i]);
    }
    pItem->flags &= ~(ITEMFLAG_NORMALS_VALID);
    itemDamaged(pItem);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandTransform --
 *
 *     $win transform ?-camera? SEARCH TRANSFORM
 *
 * Results:
 *     Tcl result.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandTransform(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    C3dTransform *pTransform;
    int rc;
    C3dWidget *pCanvas = (C3dWidget *)clientData;

    if (
        (objc != 4) &&
        (objc != 5 || strcmp(Tcl_GetString(objv[2]), "-camera"))
    ) {
        Tcl_WrongNumArgs(interp, 2, objv, "?-camera? SEARCH TRANSFORM");
        return TCL_ERROR;
    }

    pTransform = C3dTransformCompile(interp, pCanvas, objv[objc-1]);
    if (!pTransform) {
        return TCL_ERROR;
    }

    if (objc == 5) {
        C3dCameraTransform(pCanvas, pTransform);
        C3dDrawWhenIdle(pCanvas, STATE_REDRAW_ALL);
    }

    rc = C3dSearch(pCanvas, objv[objc-2], 0, transformSearchCb, pTransform);
    C3dTransformDelete(pTransform);
    return rc;
}

/*
 *---------------------------------------------------------------------------
 *
 * typeSearchCb --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
typeSearchCb(ClientData clientData, C3dItem *pItem)
{
    Tcl_Obj *pRet = (Tcl_Obj *)clientData;
    C3dItemGetType(pItem, pRet);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandType --
 *
 *     $win type SEARCH
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandType(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    int rc;
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    Tcl_Obj *pRet;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH");
        return TCL_ERROR;
    }

    pRet = Tcl_NewObj();
    C3dIncrRefCount(pRet);

    rc = C3dSearch(pCanvas, objv[2], 1, typeSearchCb, pRet);
    if (rc == TCL_OK) {
        Tcl_SetObjResult(interp, pRet);
    }

    C3dDecrRefCount(pRet);
    return rc;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandCget --
 *
 *     $win cget OPTION
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandCget(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    Tcl_Obj *pRet;            /* Return value if successful. */
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    Tk_Window win = pCanvas->tkwin;
    C3dWidgetOptions *pOptions = &pCanvas->options;
    Tk_OptionTable table = pCanvas->optionTable;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 2, objv, "OPTION");
        return TCL_ERROR;
    }

    pRet = Tk_GetOptionValue(interp, (char *)pOptions, table, objv[2], win);
    if (!pRet) {
        return TCL_ERROR;
    }

    Tcl_SetObjResult(interp, pRet);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandConfigure --
 *
 *     The Tcl callback to handle [$win configure] commands.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandConfigure(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    int geometry_request = 0;     /* True if the widget size is modified */
    int rc = TCL_OK;              /* Return code. */
    int mask = 0;                 /* Bitmask written by Tk_SetOptions */
    Tcl_Obj *pObj = 0;

    C3dWidget *pCanvas = (C3dWidget *)clientData;
    Tk_Window win = pCanvas->tkwin;
    char *pOptions = (char *)&pCanvas->options;
    Tk_OptionTable tbl;

    Tcl_Preserve(pCanvas);
    if (!pCanvas->optionTable) {
        pCanvas->optionTable = C3dCreateOptionTable(interp, option_spec);
        assert(pCanvas->optionTable);
        assert(win);
        Tk_InitOptions(interp, pOptions, pCanvas->optionTable, win);
        geometry_request = 1;
    }

    tbl = pCanvas->optionTable;

    switch (objc - 2) {
        case 1:
            pObj = objv[2];
        case 0:
            pObj = Tk_GetOptionInfo(interp, pOptions, tbl, pObj, win);
            if (pObj) {
                Tcl_SetObjResult(interp, pObj);
            } else {
                rc = TCL_ERROR;
            }
            break;
        default:
            C3dDrawWhenIdle(pCanvas, STATE_REDRAW_ALL);
            rc = Tk_SetOptions(interp,pOptions,tbl,objc-2,&objv[2],win,0,&mask);
            if (rc == TCL_OK) {
                if (mask & GEOMETRY_REQUEST) {
                    geometry_request = 1;
                }
                if (mask & SAVEUNDER_CHANGE) {
#ifdef CAN3D_AGL
		    pCanvas->options.saveunder = SAVEUNDER_NONE;
#else
                    C3d_Native_FreePixMaps(pCanvas);
#endif
                }
            }
            break;
    }
    if (geometry_request) {
        int w = pCanvas->options.width;
        int h = pCanvas->options.height;
        Tk_GeometryRequest(win, w, h);
    }

    /* Call Tk_MakeWindowExist() now in case the -cursor option was set. If
     * this is the case, Tk will magically set the cursor type for us. But
     * this only works if the window already exists.
     */

    Tk_MakeWindowExist(win);
    if (pCanvas->eState) {
	Tcl_CancelIdleCall(C3d_Canvas_DrawWhenIdle, (ClientData)pCanvas);
	Tcl_DoWhenIdle(C3d_Canvas_DrawWhenIdle, (ClientData)pCanvas);
    }

    Tcl_Release(pCanvas);
    return rc;
}

/*
 *---------------------------------------------------------------------------
 *
 * coordsSearchCb --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
coordsSearchCb(ClientData clientData, C3dItem *pItem)
{
    *((C3dItem **)clientData) = pItem;
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandCoords --
 *
 *     .win coords SEARCH ?coordList...?
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandCoords(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    int rc;
    C3dItem *p = 0;
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    int i;
    if (objc<3) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH COORD-LIST ?COORD-LIST...?");
        return TCL_ERROR;
    }
    for(i=0;i<objc;i++) {
      printf("%s ",Tcl_GetString(objv[i]));
    }
    printf("\n");
    rc = C3dSearch(pCanvas, objv[2], SEARCHFLAG_ONEONLY, coordsSearchCb, &p);

    if (p && rc == TCL_OK) {
        if (objc > 3) {
            Tcl_Obj **apCoord = (Tcl_Obj **)&objv[3];
            C3dItem *pNew = C3dItemCreate(pCanvas, objc - 3, apCoord, p);
            if (pNew) {
                Tcl_HashEntry *pEntry;
                long id = p->id;
                C3dRemoveTagFromItem(pCanvas, 0, p);
                C3dItemDelete(p);
                pEntry = Tcl_FindHashEntry(&pCanvas->aItemsById, (char *)id);
                Tcl_SetHashValue(pEntry, pNew);
                p = pNew;
                p->id = id;
                itemDamaged(pNew);
                pCanvas->validNotHiddenList = 0;
            } else {
		Tcl_AppendResult(interp, "bad coordinate list", 0);
                rc = TCL_ERROR;
            }
        }
    }

    if (p && rc == TCL_OK) {
        Tcl_Obj *pRet = Tcl_NewObj();
        C3dItemCoords(p, pRet);
        Tcl_SetObjResult(interp, pRet);
    }
    return rc;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandCreate --
 *
 *     $win create TYPE COORD-LIST ?OPTION VALUE...?
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandCreate(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    C3dItem *pItem = 0;
    int newentry;
    long id;
    Tcl_HashEntry *pEntry;

    if (objc<4) {
        Tcl_WrongNumArgs(interp, 2, objv, "TYPE COORD-LIST ?OPTION VALUE...?");
        return TCL_ERROR;
    }

    pItem = C3dItemCreate(pCanvas, objc - 2, (Tcl_Obj **)&objv[2], 0);
    if (!pItem) {
        return TCL_ERROR;
    }

    id = pCanvas->iNextItem++;
    pItem->id = id;
    pEntry = Tcl_CreateHashEntry(&pCanvas->aItemsById, (char *)id, &newentry);
    assert(newentry);
    Tcl_SetHashValue(pEntry, pItem);
    itemDamaged(pItem);
    pCanvas->validNotHiddenList = 0;

    Tcl_SetObjResult(interp, Tcl_NewIntObj(id));
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * deleteSearchCb --
 *
 *     This function is the search-callback for a [$win delete search]
 *     command. clientData is a pointer to the C3dWidget structure.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
deleteSearchCb(ClientData clientData, C3dItem *pItem)
{
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    long id = pItem->id;
    Tcl_HashEntry *pEntry;

    itemDamaged(pItem);
    /* C3dDrawWhenIdle(pItem->pCanvas, STATE_REDRAW_OVERLAY); */

    /* Remove all tag entries for the item */
    C3dRemoveTagFromItem(pCanvas, 0, pItem);

    /* Remove the item from the aItemsById array */
    pEntry = Tcl_FindHashEntry(&pCanvas->aItemsById, (char *)id);
    assert(pEntry);
    assert(pItem==Tcl_GetHashValue(pEntry));
    Tcl_DeleteHashEntry(pEntry);

    /* Free the item. */
    C3dItemDelete(pItem);

    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandDelete --
 *
 *     $win delete search
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandDelete(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    int rc;
    int flags = SEARCHFLAG_MODIFIES_TAGS;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH");
        return TCL_ERROR;
    }

    rc = C3dSearch(pCanvas, objv[2], flags, deleteSearchCb, pCanvas);
    if (rc == TCL_OK) {
        Tcl_SetResult(interp, "", TCL_STATIC);
    }
    pCanvas->validNotHiddenList = 0;
    return rc;
}

/*
 *---------------------------------------------------------------------------
 *
 * dtagSearchCb --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
dtagSearchCb(ClientData clientData, C3dItem *pItem)
{
    int i;
    struct AddTagContext *pContext = (struct AddTagContext *)clientData;
    assert(pContext && pContext->nTag > 0);
    for (i = 0; i < pContext->nTag; i++) {
        C3dRemoveTagFromItem(pContext->pCanvas, pContext->aTag[i], pItem);
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandDtag --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandDtag(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    struct AddTagContext sContext;
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    int flags = SEARCHFLAG_MODIFIES_TAGS;

    if (objc < 4) {
        Tcl_WrongNumArgs(interp, 2, objv, "SEARCH TAG ?TAG ...?");
        return TCL_ERROR;
    }

    sContext.pCanvas = pCanvas;
    sContext.nTag = objc - 3;
    sContext.aTag = &objv[3];

    return C3dSearch(pCanvas, objv[2], flags, dtagSearchCb, &sContext);
}


/*
 *---------------------------------------------------------------------------
 *
 * Compute the solution to a linear system of three equations and
 * three unknowns.  Call the unknowns x, y, z, w.  Then the problem is
 * encoded like this:
 *
 *       m[0]*x +  m[1]*y +  m[2]*z +  m[3]*w =  m[4]
 *       m[5]*x +  m[6]*y +  m[7]*z +  m[8]*w =  m[9]
 *      m[10]*x + m[11]*y + m[12]*z + m[12]*w = m[13]
 *      m[14]*x + m[15]*y + m[16]*z + m[17]*w = m[18]
 *
 * If this system has a solution, then write the result in m[4], m[9],
 * m[13], and m[18] and return 0.  If no solution exists, return 1.
 *
 * The m[] matrix is overwritten by this routine regardless of whether
 * or not a solution is found.
 *
 *---------------------------------------------------------------------------
 */
static int
solve4by4(double *m)
{
    int i, j, k;
    double x, y, scale;
    /* Process four rows from top to bottom */
    for(i=0; i<4; i++){
        /* Find the pivot for the i-th row */
        x = fabs(m[i*6]);
        k = i;
        for(j=i+1; j<4; j++){
            y = fabs(m[j*5+i]);
            if( y>x ){
                x = y;
                k = j;
            }
        }

        /* Swap in the pivot */
        if( k>i ){
            double t;
            int n, p, q;
            p = i*6;
            q = k*5+i;
            for(n=i; n<5; n++, p++, q++){
               t = m[p];
               m[p] = m[q];
               m[q] = t;
            }
        }

        /* No solution if the pivot is 0.0 */
        if( m[i*6]==0.0 ) return 1;

        /* Divide the i-th row by the pivot */
        scale = m[i*6];
        for(j=i; j<5; j++){
          m[i*5+j] /= scale;
        }

        /* Add the i-th row to other rows to eliminate the i-th term. */
        for(j=i+1; j<4; j++){
           double scale = -m[j*5+i];
           for(k=i; k<5; k++){
             m[j*5+k] += m[i*5+k]*scale;
           }
        }
    }

    /* Now we have an upper triangular matrix with a diagonal of 1.
    ** Finishing the solution is easy */
    for(i=2; i>=0; i--){
        for(j=3; j>i; j--){
           m[i*5+4] -= m[j*5+4]*m[i*5+j];
        }
    }

    return 0;
}


/*
 * Adapted from Togl_TakePhoto
 *
 *   Take a photo image of the current OpenGL window.  May have problems
 *   if window is partially obscured, either by other windows or by the
 *   edges of the display.
 */
static int
widgetCommandPhoto(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    Tk_PhotoHandle photo;
    GLubyte *buffer;
    Tk_PhotoImageBlock photoBlock;
    int     y, midy;
    unsigned char *cp;
    int width  = Tk_Width(pCanvas->tkwin);
    int height = Tk_Height(pCanvas->tkwin);

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 2, objv, "photo");
        return TCL_ERROR;
    }
    photo = Tk_FindPhoto(interp, Tcl_GetString(objv[2]));
    if (photo == NULL) {
        Tcl_SetResult(interp, "image doesn't exist or is not a photo image",
		      TCL_STATIC);
	return TCL_ERROR;
    }

    buffer = (GLubyte *) ckalloc(width * height * 4);
    photoBlock.pixelPtr = buffer;
    photoBlock.width = width;
    photoBlock.height = height;
    photoBlock.pitch = width * 4;
    photoBlock.pixelSize = 4;
    photoBlock.offset[0] = 0;
    photoBlock.offset[1] = 1;
    photoBlock.offset[2] = 2;
    photoBlock.offset[3] = 3;

    glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT);
    glPixelStorei(GL_PACK_ALIGNMENT, 4);        /* guarantee performance */
#ifndef ANDROID
    glPixelStorei(GL_PACK_SWAP_BYTES, GL_FALSE);
    glPixelStorei(GL_PACK_SKIP_PIXELS, 0);

    glPixelStorei(GL_PACK_ROW_LENGTH, 0);
    glPixelStorei(GL_PACK_SKIP_ROWS, 0);
#endif
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    /* Some OpenGL drivers are buggy and return zero for Alpha instead of one
     * for RGB pixel formats.  If that is happening to you, upgrade your
     * graphics driver. */

    /* OpenGL's origin is bottom-left, Tk Photo image's is top-left, so mirror
     * the rows around the middle row. */
    midy = height / 2;
    cp = buffer;
    for (y = 0; y < midy; ++y) {
        int     m_y = height - 1 - y;   /* mirror y */
        unsigned char *m_cp = buffer + m_y * photoBlock.pitch;
        int     x;

        for (x = 0; x < photoBlock.pitch; ++x) {
            unsigned char c = *cp;

            *cp++ = *m_cp;
            *m_cp++ = c;
        }
    }
    /*
     * TIP #116 altered Tk_PhotoPutBlock API to add interp arg that 8.4
     * doesn't have.
     * We need to remove that for compiling with 8.4.
     */
    #if (TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION < 5)
      Tk_PhotoPutBlock(photo, &photoBlock, 0, 0, width, height, TK_PHOTO_COMPOSITE_SET);
    #else
      Tk_PhotoPutBlock(pCanvas->interp, photo, &photoBlock, 0, 0, width, height, TK_PHOTO_COMPOSITE_SET);
    #endif

    glPopClientAttrib();        /* restore PACK_ALIGNMENT */
    ckfree((char *) buffer);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandProjection --
 *
 *     If called with no argument, then return the current projection
 *     matrix as an array of 16 floating-point values.
 *
 *     If called with to canvas coordinates (that is to say, with
 *     pixel coordinates where 0,0 is the upper left corner of the
 *     screen), then return a 3D coordinate that is beneath that
 *     pixel.
 *
 *     If called when 3d coordinates, then return the pixel into which
 *     that coordinate projects.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandProjection(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    struct C3dTransform *pTransform;
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    int i;
    Tcl_Obj *pResult;
    float matrix[16];       /* The projection matrix */
    double m2[20];          /* A linear system to be solved */

    if (objc != 2 && objc != 4 && objc != 5) {
        Tcl_WrongNumArgs(interp, 2, objv, "?X Y ?Z??");
        return TCL_ERROR;
    }

    pTransform = C3dTransformProjection(pCanvas);
    memcpy(matrix, pTransform, sizeof(matrix));
    C3dTransformDelete(pTransform);
    pResult = Tcl_NewObj();
    if( objc==2 ){
        /* No argument.  Return the projection matrix */
        for(i=0; i<16; i++){
            Tcl_ListObjAppendElement(0, pResult, Tcl_NewDoubleObj(matrix[i]));
        }
    }else if( objc==5 ){
        /* Three arguments.  Project the given 3D point onto the screen. */
        double x, y, z, cx, cy, w;
        if( Tcl_GetDoubleFromObj(interp, objv[2], &x) ) return TCL_ERROR;
        if( Tcl_GetDoubleFromObj(interp, objv[3], &y) ) return TCL_ERROR;
        if( Tcl_GetDoubleFromObj(interp, objv[4], &z) ) return TCL_ERROR;
        w = matrix[12]*x + matrix[13]*y + matrix[14]*z + matrix[15];
        cx = (matrix[0]*x + matrix[1]*y + matrix[2]*z + matrix[3])/w;
        cy = (matrix[4]*x + matrix[5]*y + matrix[6]*z + matrix[7])/w;
        Tcl_ListObjAppendElement(0, pResult,
               Tcl_NewDoubleObj((0.5*cx + 0.5)*Tk_Width(pCanvas->tkwin)));
        Tcl_ListObjAppendElement(0, pResult,
               Tcl_NewDoubleObj((0.5*cy + 0.5)*Tk_Height(pCanvas->tkwin)));
    }else{
        double cx, cy, cw, ch;
        if( Tcl_GetDoubleFromObj(interp, objv[2], &cx) ) return TCL_ERROR;
        if( Tcl_GetDoubleFromObj(interp, objv[3], &cy) ) return TCL_ERROR;
        cw = Tk_Width(pCanvas->tkwin);
        ch = Tk_Height(pCanvas->tkwin);
        if( cw<=0.0 || ch<=0.0 ){
            Tcl_AppendResult(interp, "zero size canvas", 0);
            return TCL_ERROR;
        }
        m2[0] = matrix[0];
        m2[1] = matrix[1];
        m2[2] = matrix[2];
        m2[3] = matrix[3];
        m2[4] = cx/(0.5*cw) - 1.0;
        m2[5] = matrix[4];
        m2[6] = matrix[5];
        m2[7] = matrix[6];
        m2[8] = matrix[7];
        m2[9] = cy/(0.5*ch) - 1.0;
        m2[10] = matrix[8];
        m2[11] = matrix[9];
        m2[12] = matrix[10];
        m2[13] = matrix[11];
        m2[14] = 0.0;
        m2[15] = matrix[12];
        m2[16] = matrix[13];
        m2[17] = matrix[14];
        m2[18] = matrix[15];
        m2[19] = 1.0;
        if( solve4by4(m2) || m2[19]==0.0 ){
            Tcl_AppendResult(interp, "no solution", 0);
            return TCL_ERROR;
        }
        for(i=4; i<=14; i+=5){
            Tcl_ListObjAppendElement(0,pResult,Tcl_NewDoubleObj(m2[i]/m2[19]));
        }
    }
    Tcl_SetObjResult(interp, pResult);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommandSegment --
 *
 *     3 arguments:  An item ID followed by X and Y canvas coordinates.
 *
 * Results:
 *     Return a floating point value which provides some information about
 *     where in the object the X,Y coordinate refers.  For a line, the
 *     integer part of the number returned is the segment number (starting
 *     with 0.0) and the fractional part is fraction of the segment that
 *     has been traversed.  For polygons, the value returned is the
 *     index of the first face that is beneath the point.  0.0 is returned
 *     for other objects.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommandSegment(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    long lid;
    int id, x, y;
    C3dWidget *pCanvas;
    C3dItem *pItem;
    Tcl_HashEntry *pEntry;
    double seg;

    if (objc != 5) {
        Tcl_WrongNumArgs(interp, 2, objv, "ID X Y");
        return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[2], &id)) return TCL_ERROR;
    if (Tcl_GetIntFromObj(interp, objv[3], &x)) return TCL_ERROR;
    if (Tcl_GetIntFromObj(interp, objv[4], &y)) return TCL_ERROR;
    pCanvas = (C3dWidget*)clientData;
    lid = id;
    pEntry = Tcl_FindHashEntry(&pCanvas->aItemsById, (char*)lid);
    if (pEntry == 0) {
        Tcl_AppendResult(interp, "no such item", 0);
        return TCL_ERROR;
    }
    pItem = Tcl_GetHashValue(pEntry);
    if (pItem->pType->xSegment == 0) {
        seg = 0.0;
    } else {
        seg = pItem->pType->xSegment(pItem, x, y);
    }
    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(seg));
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * widgetCommand --
 *
 *     This function implements the widget command for a canvas3d widget.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
widgetCommand(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    static const char *SubCommands[] = {
        "addtag",            "boundingsphere",    "bbox",
        "cget",              "configure",         "coords",
        "create",            "delete",            "dtag",
        "find",              "gettags",           "itemcget",
        "itemconfigure",     "photo",             "projection",
        "segment",
	"statistics",        "transform",         "type",
	0
    };
    Tcl_ObjCmdProc *aFuncs[] = {
        widgetCommandAddtag,                   /* addtag */
        widgetCommandBoundingsphere,           /* boundingsphere */
        widgetCommandBbox,                     /* bbox */

        widgetCommandCget,                     /* cget */
        widgetCommandConfigure,                /* configure */
        widgetCommandCoords,                   /* coords */

        widgetCommandCreate,                   /* create */
        widgetCommandDelete,                   /* delete */
        widgetCommandDtag,                     /* dtag */

        widgetCommandFind,                     /* find */
        widgetCommandGettags,                  /* gettags */
        widgetCommandItemcget,                 /* itemcget */

        widgetCommandItemconfigure,            /* itemconfigure */
        widgetCommandPhoto,                    /* photo */
        widgetCommandProjection,               /* projection */
        widgetCommandSegment,                  /* segment */

        widgetCommandStatistics,               /* statistics */
        widgetCommandTransform,                /* transform */
        widgetCommandType                      /* type */
    };
    int c, ret;

    assert(
            (sizeof(aFuncs)/sizeof(Tcl_ObjCmdProc *) + 1) ==
            (sizeof(SubCommands)/sizeof(const char *))
    );
    if (objc < 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
        return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObj(interp, objv[1], SubCommands, "option", 0, &c)) {
        return TCL_ERROR;
    }

    assert(aFuncs[c]);
    C3d_Native_Select3dContext((C3dWidget *)clientData);
    ret = aFuncs[c](clientData, interp, objc, objv);
    C3d_Native_Release3dContext((C3dWidget *)clientData);
    return ret;
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_CanvasObjCmd --
 *
 *     canvas3d pathName ?option value...?
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
C3d_CanvasObjCmd(
    ClientData clientData, /* Main window associated with interpreter. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    Tk_Window mainwin = clientData;
    C3dWidget *pCanvas = 0;          /* Widget data structure */
    Tk_Window newWin = 0;               /* Token for the new window */
    int rc;

    /* Check we have at least one argument (the window name). */
    if (objc < 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName ?option value...?");
        return TCL_ERROR;
    }

    newWin = Tk_CreateWindowFromPath(interp, mainwin, Tcl_GetString(objv[1]), NULL);
    if (!newWin) {
      return TCL_ERROR;
    }

    /* Create the data structure to hold state information. */
    pCanvas = (C3dWidget *)C3dAlloc(sizeof(C3dWidget));
    memset(pCanvas, 0, sizeof(C3dWidget));
    pCanvas->tkwin = newWin;
    pCanvas->display = Tk_Display(newWin);
    pCanvas->interp = interp;
    pCanvas->widgetCmd=Tcl_CreateObjCommand(interp,
            Tk_PathName(pCanvas->tkwin), widgetCommand, pCanvas, C3d_CanvasCmdDeletedProc);

    Tk_SetClass(pCanvas->tkwin, "Canvas3d");
    Tk_SetClassProcs(pCanvas->tkwin, &C3dCanvasProcs, (ClientData)pCanvas);
    Tk_CreateEventHandler(
      pCanvas->tkwin, ExposureMask|StructureNotifyMask,
      C3d_Canvas_EventProc, pCanvas);

    /* Return the name of the window we just created. */
    rc = widgetCommandConfigure(pCanvas, interp, objc, objv);
    if (rc != TCL_OK) {
	return rc;
    }

    /* The two hash tables items live in. One indexed by unique item-id,
     * the other by tag-name.
     */
    Tcl_InitHashTable(&pCanvas->aItemsById, TCL_ONE_WORD_KEYS);
    Tcl_InitHashTable(&pCanvas->aItemsByTag, TCL_STRING_KEYS);
    Tcl_SetObjResult(interp, Tcl_DuplicateObj(objv[1]));

#if defined(CAN3D_AGL)
    if (pCanvas->context == NULL) {
      aglSetCurrentContext(pCanvas->context);
    }
#endif

    return rc;

    /* Todo: Cleanup */
    return TCL_ERROR;
}

/*
 *---------------------------------------------------------------------------
 *
 * transformCommand --
 *
 *     ::canvas3d::transform coordList ?coordList...? transform
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
transformCommand(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj * const objv[]
) {
    Tcl_Obj **apCoord;
    Tcl_Obj *pRet = 0;
    C3dTransform *pTransform = 0;
    int nCoord;
    int n;
    int i;
    Tcl_Obj *pBadList = 0;

    n = C3dGetCoords(objc - 2, (Tcl_Obj **)&objv[1], &nCoord, &apCoord);
    if (n != (objc - 2)) {
        pBadList = objv[n];
        goto badlist_out;
    }

    pTransform = C3dTransformCompile(interp, 0, objv[objc - 1]);
    if (!pTransform) {
        return TCL_ERROR;
    }

    pRet = Tcl_NewObj();

    for (i = 0; i < nCoord; i++) {
        Tcl_Size j;
        Tcl_Obj **apNumber;
        Tcl_Size nNumber;
        Tcl_Obj *pRetCoord = Tcl_NewObj();
        C3dIncrRefCount(pRetCoord);

        pBadList = apCoord[i];
        if (Tcl_ListObjGetElements(interp, apCoord[i], &nNumber, &apNumber)) {
            C3dDecrRefCount(pRetCoord);
            return TCL_ERROR;
        }
        if (nNumber % 3) {
            C3dDecrRefCount(pRetCoord);
            goto badlist_out;
        }
        for (j = 0; j < nNumber; j += 3) {
             double dx, dy, dz;
             C3dVertex v;
             if (
                 Tcl_GetDoubleFromObj(interp, apNumber[j+0], &dx) ||
                 Tcl_GetDoubleFromObj(interp, apNumber[j+1], &dy) ||
                 Tcl_GetDoubleFromObj(interp, apNumber[j+2], &dz)
             ) {
                 C3dDecrRefCount(pRetCoord);
                 goto badlist_out;
             }
             v.x = dx;
             v.y = dy;
             v.z = dz;
             C3dTransformApply(pTransform, &v);
             Tcl_ListObjAppendElement(interp, pRetCoord, Tcl_NewDoubleObj(v.x));
             Tcl_ListObjAppendElement(interp, pRetCoord, Tcl_NewDoubleObj(v.y));
             Tcl_ListObjAppendElement(interp, pRetCoord, Tcl_NewDoubleObj(v.z));
        }

        Tcl_ListObjAppendElement(interp, pRet, pRetCoord);
        C3dDecrRefCount(pRetCoord);
    }

    Tcl_SetObjResult(interp, pRet);
    C3dTransformDelete(pTransform);
    return TCL_OK;

badlist_out:
    Tcl_ResetResult(interp);
    Tcl_AppendResult(interp, "Malformed coordList: ", 0);
    Tcl_AppendResult(interp, Tcl_GetString(pBadList), 0);

    if (pRet) {
        C3dIncrRefCount(pRet);
        C3dDecrRefCount(pRet);
    }
    C3dTransformDelete(pTransform);
    return TCL_ERROR;
}

/*
** Provide a dummy Tcl_InitStubs if we are using this as a static
** library.
*/
#ifndef USE_TCL_STUBS
# undef  Tcl_InitStubs
# define Tcl_InitStubs(a,b,c) TCL_VERSION
#endif

/*
 *---------------------------------------------------------------------------
 *
 * Canvas3d_Init --
 *
 *     The initialisation function for the 3d-canvas package. As well as
 *     Canvas3d_Init(), Canvas_Init() is supplied as an alias. This is
 *     so the Tcl command [load libcanvas3d1.0] works despite the '3' in
 *     the package name.
 *
 * Results:
 *     Tcl error code.
 *
 * Side effects:
 *     Provides the package "Canvas3d" and registers the [canvas3d] command
 *     with the interpreter.
 *
 *---------------------------------------------------------------------------
 */
int DLLEXPORT
Canvas3d_Init(Tcl_Interp *interp)
{
    Tk_Window mainwin;
    /* Initialise the stubs tables. */
#ifdef USE_TCL_STUBS
    if (
        !Tcl_InitStubs(interp, "8.3", 0) ||
        !Tk_InitStubs(interp, "8.3", 0)
    ) {
        return TCL_ERROR;
    }
#endif
    mainwin = Tk_MainWindow(interp);
    assert(mainwin);

    /* Register the package. */
    if (Tcl_PkgProvide(interp, "Canvas3d", CANVAS3D_VERSION)) {
        return TCL_ERROR;
    }
    if(C3d_Native_Init(interp)) {
      return TCL_ERROR;
    }
    /* Create the widget command. */
    Tcl_CreateObjCommand(interp, "canvas3d", C3d_CanvasObjCmd, mainwin, 0);
    Tcl_CreateObjCommand(interp, "::canvas3d::transform", transformCommand,0,0);
#ifndef NDEBUG
    Tcl_CreateObjCommand(interp, "canvas3d_alloc", C3dAllocCommand, 0, 0);
#endif
    return TCL_OK;
}

int DLLEXPORT
Canvas3d_SafeInit(Tcl_Interp *interp)
{
    return Canvas3d_Init(interp);
}

int DLLEXPORT
Canvas_Init(Tcl_Interp *interp)
{
    return Canvas3d_Init(interp);
}
