/*
 * c3dpolygon.c --
 *
 *     This file implements the C3dItemType functions for the polygon item.
 */

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

/*
 * The following two constants and string table are passed to
 * Tk_CreateOptionTable() to create the Tk_OptionTable for polygon items.
 * It is important that the constants work as array indexes. i.e.:
 *
 *     PolygonStyles[POLYGON_SOLID] == "solid"
 *     PolygonStyles[POLYGON_OUTLINE] == "outline"
 */
#define POLYGON_SOLID 0
#define POLYGON_OUTLINE 1
static const char * PolygonStyles[] = {"solid", "outline", 0};

/*
 * This structure contains data for a polygon item that is private to this
 * file. There is one instance per item.
 *
 * The 'common' field, type C3dItem, must be first. This allows a
 * PolygonItem* to be cast to a C3dItem* and manipulated by common code.
 */
typedef struct PolygonItem PolygonItem;
struct PolygonItem {
    C3dItem common;      /* Variables common to all items. Must be first! */

    int style;           /* One of POLYGON_OUTLINE and POLYGON_SOLID. */
    C3dColor color;      /* Color to draw polygon if lighting is disabled. */
    C3dColor ambient;    /* Reflection coefficients for ambient light */
    C3dColor diffuse;    /* Reflection coefficients for diffuse light */
    C3dColor specular;   /* Reflection coefficients for specular light */
    double shininess;    /* Used to calculate specular reflection itensity */
    int smooth;          /* Value of -smooth boolean option */

    int nFace;                /* Number of faces in this item */
    C3dVertex *aFaceNormal;   /* Array of vectors normal to faces */
    int *aFinalVertex;        /* Array of final vertices for faces */

    C3dVertex *aVertexNormal; /* Array of normal vectors for vertices */
    int nIdx;                 /* Number of entries in aVertexIdx */
    int *aVertexIdx;          /* Array of indexes into common.aVertex */

    C3dTexture *teximage;
    C3dTextureCoords *texcoords;
};

/*
 *---------------------------------------------------------------------------
 *
 * polygonIsTransparent --
 *
 *     This function is used to determine if a polygon item requires the
 *     special handling required for items rendered with an alpha channel
 *     of other than 1.0.
 *
 * Results:
 *     If the special handling is required non-zero is returned. Otherwise
 *     zero.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static int
polygonIsTransparent(C3dWidget *pCanvas, PolygonItem *pPolygon)
{
    if (pCanvas->options.enablealpha) {
        int lights = (pPolygon->style == POLYGON_SOLID && pCanvas->nLight > 0);
        if (lights) {
            return (
                pPolygon->ambient.aChannel[3] != 1.0 ||
                pPolygon->diffuse.aChannel[3] != 1.0 ||
                pPolygon->specular.aChannel[3] != 1.0
            );
        }
        return (pPolygon->color.aChannel[3] != 1.0);
    }
    return 0;
}

/*
 *---------------------------------------------------------------------------
 *
 * calculateNormals --
 *
 *     This procedure calculates a normal vector for each polygon face and
 *     stores it in PolygonItem.aFace[N].normal.  This function is a no-op
 *     if the ITEMFLAG_NORMALS_VALID flag in PolygonItem.common.flags is
 *     set.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     Modifies the normal vectors in the PolygonItem.aFace array.
 *
 *---------------------------------------------------------------------------
 */
static void
calculateNormals(PolygonItem *pItem)
{
    int i;
    int v = 0;

    if (pItem->common.flags & ITEMFLAG_NORMALS_VALID) return;

    /* Calculate the normal vector for each face of the item. Store these
     * vectors in the PolygonItem.aFaceNormal array.
     */
    for (i = 0; i < pItem->nFace; i++) {
	/* Compute the cross product of the edge vectors that meet at
	 * vertex 1 of the face. Then normalize the result and use it as
	 * the normal vector for the vertex.
         */
        C3dVertex *p1 = &pItem->common.aVertex[pItem->aVertexIdx[v+0]];
        C3dVertex *p2 = &pItem->common.aVertex[pItem->aVertexIdx[v+1]];
        float mag;          /* Magnitude of normal vector */

        assert(pItem->aFinalVertex[i] - v >= 3);
        do {
            C3dVertex *p3 = &pItem->common.aVertex[pItem->aVertexIdx[v+2]];
            C3dVertex *p = &pItem->aFaceNormal[i];

            float x1, y1, z1;
            float x2, y2, z2;

            x1 = p2->x - p1->x;
            y1 = p2->y - p1->y;
            z1 = p2->z - p1->z;
            x2 = p3->x - p2->x;
            y2 = p3->y - p2->y;
            z2 = p3->z - p2->z;

            C3dCross(x1, y1, z1, x2, y2, z2, &p->x, &p->y, &p->z);
            C3dNormalize(&p->x, &p->y, &p->z, &mag);
            v++;
        } while (v+2 < pItem->aFinalVertex[i] && mag == 0.0);

        v = pItem->aFinalVertex[i];
        assert(v+2 < pItem->nIdx || i+1 == pItem->nFace);
    }

    /* If the -smooth option is set, calculate normals for each vertex. */
    if (pItem->smooth) {
        int f = -1;
        float x = 0.0;
        float y = 0.0;
        float z = 0.0;
        int nVertex = pItem->common.nVertex;
        memset(pItem->aVertexNormal, 0, sizeof(C3dVertex) * nVertex);

        for (i = 0; i < pItem->nIdx; i++) {
            float *pX, *pY, *pZ;
            if (!i || i == pItem->aFinalVertex[f]) {
                f++;
                x = pItem->aFaceNormal[f].x;
                y = pItem->aFaceNormal[f].y;
                z = pItem->aFaceNormal[f].z;
            }
            pX = &pItem->aVertexNormal[pItem->aVertexIdx[i]].x;
            pY = &pItem->aVertexNormal[pItem->aVertexIdx[i]].y;
            pZ = &pItem->aVertexNormal[pItem->aVertexIdx[i]].z;
            if (C3dDot(*pX, *pY, *pZ, x, y, z) < 0.0) {
                *pX -= x;
                *pY -= y;
                *pZ -= z;
            } else {
                *pX += x;
                *pY += y;
                *pZ += z;
            }
        }

        for (i = 0; i < nVertex; i++) {
            C3dVertex *p = &pItem->aVertexNormal[i];
            C3dNormalize(&p->x, &p->y, &p->z, 0);
        }
    }

    pItem->common.flags |= ITEMFLAG_NORMALS_VALID;
}

/*
 *---------------------------------------------------------------------------
 *
 * polygonTable --
 *
 *     Return the Tk_OptionTable used to implement [itemconfigure] and
 *     [itemcget] for the polygon item.
 *
 * Results:
 *     Tk_OptionTable populated with options for polygon items.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static Tk_OptionTable
polygonTable(Tcl_Interp *interp)
{
    static Tk_OptionSpec polygon_option_array[] = {
        TAGS_OPTION,
        HIDDEN_OPTION,
        C3DOPTION(PolygonItem, STRING_TABLE, style, "solid", PolygonStyles),
        C3DOPTION(PolygonItem, DOUBLE, shininess, "0.0", 0),
        C3DOPTION(PolygonItem, COLOR, ambient, "0.2 0.2 0.2 1.0", 0),
        C3DOPTION(PolygonItem, COLOR, diffuse, "0.8 0.8 0.8 1.0", 0),
        C3DOPTION(PolygonItem, COLOR, specular, "0.0 0.0 0.0 1.0", 0),
        C3DOPTION(PolygonItem, COLOR, color, "white", 0),
        C3DOPTION(PolygonItem, BOOLEAN, smooth, "0", 0),
        C3DOPTION(PolygonItem, CUSTOM, teximage, "", 0),
        C3DOPTION(PolygonItem, CUSTOM, texcoords, "", 0),
        {TK_OPTION_END, 0, 0, 0, 0, 0, 0, 0, 0}
    };
    Tk_OptionTable polygon_options;

    polygon_options = C3dCreateOptionTable(interp, polygon_option_array);
    assert(polygon_options);

    return polygon_options;
}

/*
 *---------------------------------------------------------------------------
 *
 * polygonMultiCreate --
 *
 *     Allocate and return a pointer to a new C3dItem struct containing a
 *     polygon item. This function is responsible for allocating and
 *     zeroing the memory used to store the polygon, and for populating
 *     the common C3dItem.aVertex and C3dItem.nVertex, and any private data
 *     that will not be initialised by a call to Tk_InitOptions(). All
 *     other set up for the item is done by the caller.
 *
 *     This function allocates all space space required by the item using a
 *     single call to C3dAlloc(): the PolygonItem struct, the face array,
 *     and the array of vertices.
 *
 * Results:
 *     Pointer to C3dItem struct containing a polygon item.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static C3dItem *
polygonMultiCreate(
    C3dWidget *pCanvas,           /* Pointer to widget structure */
    int nFace,                    /* Number of faces for new polygon */
    Tcl_Obj **apCoords            /* Coordinates for each face */
) {
    PolygonItem *pPolygon;        /* New polygon item structure */
    int nBytes;                   /* Number of bytes to allocate */
    int nCoords = 0;              /* Total number of vertices, all faces */
    Tcl_Interp *interp = pCanvas->interp;

    int i;
    int j;
    int v;
    Tcl_HashTable vertexHash;
    Tcl_HashSearch search;
    Tcl_HashEntry *pEntry;
    long nUnique = 0;

    /* Initialise the vertexHash hash table. This hash will map from vertex
     * (three floating point values) to the index in C3dItem.aVertex where
     * the vertex will be stored.
     */
    assert(0 == sizeof(C3dVertex)%sizeof(int));
    Tcl_InitHashTable(&vertexHash, sizeof(C3dVertex) / sizeof(int));

    /* This loop populates the vertexHash hash table, sets nCoords to the
     * number of vertices on all faces, and nUnique to the number of unique
     * vertices on all faces.
     */
    for (i = 0; i < nFace; i++) {
        double x, y, z;
        Tcl_Obj **apObj;
        int nObj = 0;
        if (C3dCheckCoords(interp, apCoords[i], 3, -1, 0)) {
            return 0;
        }
        Tcl_ListObjGetElements(interp, apCoords[i], &nObj, &apObj);
        assert(nObj > 0 && 0 == (nObj % 3));
        for (j = 0; j < nObj; j += 3) {
            C3dVertex vertex;
            int newentry;
            if (
                TCL_OK != Tcl_GetDoubleFromObj(interp, apObj[j+0], &x) ||
                TCL_OK != Tcl_GetDoubleFromObj(interp, apObj[j+1], &y) ||
                TCL_OK != Tcl_GetDoubleFromObj(interp, apObj[j+2], &z)
            ) {
                return 0;
            }
            vertex.x = x;
            vertex.y = y;
            vertex.z = z;
            pEntry = Tcl_CreateHashEntry(&vertexHash,(char *)&vertex,&newentry);
            if (newentry) {
                Tcl_SetHashValue(pEntry, nUnique);
                nUnique++;
            }
        }
        nCoords += (nObj / 3);
    }

    /* Allocate a chunk of memory for the PolygonItem and the various
     * arrays it contains. The sizes of all such arrays are fixed now, at
     * item creation time, which means we can allocate space for them all
     * with one big allocation.
     */
    nBytes = sizeof(PolygonItem) +            /* PolygonItem */
             sizeof(C3dVertex) * nUnique +    /* C3dItem.aVertex */
             sizeof(C3dVertex) * nUnique +    /* PolygonItem.aVertexNormal */
             sizeof(C3dVertex) * nFace +      /* PolygonItem.aFaceNormal */
             sizeof(int)       * nFace +      /* PolygonItem.aFinalVertex */
             sizeof(int)       * nCoords;     /* PolygonItem.aVertexIdx */
    pPolygon = (PolygonItem *)C3dAlloc(nBytes);
    assert((char *)pPolygon == (char *)&pPolygon->common);
    memset(pPolygon, 0, nBytes);
    pPolygon->common.aVertex = (C3dVertex *)&pPolygon[1];
    pPolygon->aVertexNormal  = &pPolygon->common.aVertex[nUnique];
    pPolygon->aFaceNormal    = &pPolygon->aVertexNormal[nUnique];
    pPolygon->aFinalVertex   = (int *)&pPolygon->aFaceNormal[nFace];
    pPolygon->aVertexIdx     = (int *)&pPolygon->aFinalVertex[nFace];

    /* Populate the C3dItem.aVertex array. */
    pPolygon->common.nVertex = nUnique;
    for (
        pEntry = Tcl_FirstHashEntry(&vertexHash, &search);
        pEntry;
        pEntry = Tcl_NextHashEntry(&search)
    ) {
        C3dVertex *pVertex = (C3dVertex *)Tcl_GetHashKey(&vertexHash, pEntry);
        long idx = (long)Tcl_GetHashValue(pEntry);
        pPolygon->common.aVertex[idx].x = pVertex->x;
        pPolygon->common.aVertex[idx].y = pVertex->y;
        pPolygon->common.aVertex[idx].z = pVertex->z;
    }

    /* Popluate PolygonItem.aVertexIdx and PolygonItem.aFinalVertex */
    v = 0;
    pPolygon->nIdx = nCoords;
    pPolygon->nFace = nFace;
    for (i = 0; i < nFace; i++) {
        double x, y, z;
        Tcl_Obj **apObj;
        int nObj = 0;
        Tcl_ListObjGetElements(interp, apCoords[i], &nObj, &apObj);
        assert(nObj > 0 && 0 == (nObj % 3));
        for (j = 0; j < nObj; j += 3) {
            C3dVertex vertex;
            long idx;
            if (
                TCL_OK != Tcl_GetDoubleFromObj(interp, apObj[j+0], &x) ||
                TCL_OK != Tcl_GetDoubleFromObj(interp, apObj[j+1], &y) ||
                TCL_OK != Tcl_GetDoubleFromObj(interp, apObj[j+2], &z)
            ) {
                return 0;
            }
            vertex.x = x;
            vertex.y = y;
            vertex.z = z;
            pEntry = Tcl_FindHashEntry(&vertexHash, (char *)&vertex);
            assert(pEntry);
            idx = (long)Tcl_GetHashValue(pEntry);
            pPolygon->aVertexIdx[v++] = idx;
        }
        pPolygon->aFinalVertex[i] = v;
    }

    Tcl_DeleteHashTable(&vertexHash);

    return (C3dItem *)pPolygon;
}

/*
 *---------------------------------------------------------------------------
 *
 * polygonDrawFace --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
polygonDrawFace(C3dItem *pItem, ClientData clientData)
{
    long iFace = (long)clientData;
    C3dWidget *pCanvas = pItem->pCanvas;
    int i;
    PolygonItem *pOptions = (PolygonItem *)pItem;
    int useLighting;      /* Set properties used for lighting */
    int toggleLighting;   /* Turn off OpenGL lighting while drawing */
    int primitive;        /* Either GL_POLYGON or GL_LINE_LOOP */
    int transparent;      /* True if transparent polygon */
    C3dTexture *pTex;     /* Texture to use, if any */
    GLuint texture;

    assert(pOptions->style==POLYGON_SOLID || pOptions->style==POLYGON_OUTLINE);

    primitive = (pOptions->style == POLYGON_SOLID ? GL_POLYGON : GL_LINE_LOOP);
    useLighting = (pOptions->style == POLYGON_SOLID && pCanvas->nLight > 0);
    toggleLighting = !useLighting && (pCanvas->nLight > 0);
    transparent = polygonIsTransparent(pCanvas, pOptions);
    pTex = pOptions->teximage;

    /* If lighting is to be used, set the material properties of the face.
     * Otherwise set the raster color to the value of the -color option.
     */
    if (useLighting) {
        calculateNormals(pOptions);
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, pOptions->ambient.aChannel);
        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, pOptions->diffuse.aChannel);
        glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,pOptions->specular.aChannel);
        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, pOptions->shininess);
    } else {
        glColor4fv(pOptions->color.aChannel);
    }

    /* Toggle lighting off if required */
    if (toggleLighting) {
        glDisable(GL_LIGHTING);
    }

    /* If the polygon is transparent, set the depth-buffer to read-only */
    if (transparent) {
        glDepthMask(GL_FALSE);
    }

    if (pTex) {
        glGenTextures(1, &texture);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(GL_TEXTURE_2D, 0, 4, pTex->iWidth, pTex->iHeight, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, pTex->aTexels);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    }

    /* Draw an OpenGL polygon for the face */
    glBegin(primitive);
    i = (iFace > 0 ? pOptions->aFinalVertex[iFace -1] : 0);
    for (; i < pOptions->aFinalVertex[iFace]; i++) {
        C3dVertex *pVertex = &pItem->aVertex[pOptions->aVertexIdx[i]];
        C3dVertex *pNormal;
        pNormal = &pOptions->aFaceNormal[iFace];
        if (!pOptions->smooth) {
            glNormal3f(pNormal->x, pNormal->y, pNormal->z);
        } else {
            float nx, ny, nz;
            C3dVertex *pN = &pOptions->aVertexNormal[pOptions->aVertexIdx[i]];
            nx = pN->x;
            ny = pN->y;
            nz = pN->z;

            if (C3dDot(pNormal->x, pNormal->y, pNormal->z, nx, ny, nz) < 0.0) {
                nx = nx * -1.0;
                ny = ny * -1.0;
                nz = nz * -1.0;
            }

            glNormal3f(nx, ny, nz);
        }
        if (pTex) {
            static float aCoords[] = {0, 0, 1, 0, 1, 1, 0, 1};
            static const C3dTextureCoords defCoords = {0, 4, aCoords};
            C3dTextureCoords const *pCoords = pOptions->texcoords;
            int n;

            if (!pCoords) {
                pCoords = &defCoords;
            }

            n = (i % pCoords->nCoords) * 2;
            glTexCoord2f(pCoords->aCoords[n], pCoords->aCoords[n+1]);
        }
        glVertex3f(pVertex->x, pVertex->y, pVertex->z);
    }
    glEnd();

    /* Toggle lighting back on if required. If this was a transparent
     * polygon, reenable writing to the depth buffer.
     */
    if (toggleLighting) {
        assert(pCanvas->nLight > 0);
        glEnable(GL_LIGHTING);
    }
    if (transparent) {
        glDepthMask(GL_TRUE);
    }
    if (pTex) {
        glDisable(GL_TEXTURE_2D);
        glDeleteTextures(1, &texture);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * polygonDraw --
 *
 *     Draw all faces of the polygon item using OpenGL commands. When this
 *     function is called the following are true of the OpenGL state:
 *
 *         * The matrix mode is MODELVIEW.
 *         * The projection matrix has been set for a 3d projection.
 *         * If a light source item is defined, lighting is enabled.
 *
 *     These properties are also true when this function returns.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     May modify the current OpenGL raster color and material properties.
 *
 *---------------------------------------------------------------------------
 */
static void
polygonDraw(C3dWidget *pCanvas, C3dItem *pItem)
{
    long i;
    PolygonItem *pPolygon = (PolygonItem *)pItem;
    int transparent = polygonIsTransparent(pCanvas, pPolygon);
    for (i = 0; i < pPolygon->nFace; i++) {
        if (transparent) {
            ClientData c = (ClientData)i;
            float fOrder;
            float lx, ly, lz;
            float cx, cy, cz;
            float fx = 0.0, fy = 0.0, fz = 0.0;
            int v = (i == 0 ? 0 : pPolygon->aFinalVertex[i - 1]);
            int nVertex;      /* Number of vertices comprising this face */

            /* Because this polygon features alpha transparency we draw it
             * after all the opaque objects have been drawn.
             */
            lx = pCanvas->options.cameralocation->x;
            ly = pCanvas->options.cameralocation->y;
            lz = pCanvas->options.cameralocation->z;
            cx = pCanvas->options.cameracenter->x;
            cy = pCanvas->options.cameracenter->y;
            cz = pCanvas->options.cameracenter->z;

            nVertex = pPolygon->aFinalVertex[i] - v;
            for ( ; v < pPolygon->aFinalVertex[i]; v++) {
                int idx = pPolygon->aVertexIdx[v];
                fx += pItem->aVertex[idx].x;
                fy += pItem->aVertex[idx].y;
                fz += pItem->aVertex[idx].z;
            }
            fx = fx / (float)nVertex;
            fy = fy / (float)nVertex;
            fz = fz / (float)nVertex;

            fOrder = C3dDot(cx-lx, cy-ly, cz-lz, fx-lx, fy-ly, fz-lz);
            if (fOrder > 0.0) {
                fOrder = fOrder * -1.0;
                C3dItemDrawCallback(pCanvas, polygonDrawFace, pItem, c, fOrder);
            }
        } else {
            polygonDrawFace(pItem, (ClientData) i);
        }
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * polygonStatistics --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
polygonStatistics(C3dItem *pItem, C3dStatistics *pStatistics)
{
    PolygonItem *pPolygon = (PolygonItem *)pItem;

    pStatistics->nVertex += (pPolygon->nIdx - pPolygon->common.nVertex);
    pStatistics->nFace += pPolygon->nFace;
}

/*
 *---------------------------------------------------------------------------
 *
 * polygonCoords --
 *
 *     Write the coordinates for polygon pItem in Tcl form to the Tcl
 *     object pRet.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     Modifies the contents of pRet.
 *
 *---------------------------------------------------------------------------
 */
static void
polygonCoords(C3dItem *pItem, Tcl_Obj *pRet)
{
    PolygonItem *pPolygon = (PolygonItem *)pItem;
    int nBytes;
    Tcl_Obj **apFace;
    Tcl_Obj **apVertex;
    int v = 0;
    int i;

    nBytes = sizeof(Tcl_Obj *) * pPolygon->nIdx * 3 +
             sizeof(Tcl_Obj *) * pPolygon->nFace;
    apFace = (Tcl_Obj **)C3dAlloc(nBytes);
    apVertex = &apFace[pPolygon->nFace];

    for (i = 0; i < pPolygon->nIdx; i++) {
        int v = pPolygon->aVertexIdx[i];
        apVertex[i*3 + 0] = Tcl_NewDoubleObj(pItem->aVertex[v].x);
        apVertex[i*3 + 1] = Tcl_NewDoubleObj(pItem->aVertex[v].y);
        apVertex[i*3 + 2] = Tcl_NewDoubleObj(pItem->aVertex[v].z);
    }

    for (i = 0; i < pPolygon->nFace; i++) {
        int nv = 3 * (pPolygon->aFinalVertex[i] - v);
        apFace[i] = Tcl_NewListObj(nv, &apVertex[v * 3]);
        v = pPolygon->aFinalVertex[i];
    }

    Tcl_ListObjReplace(0, pRet, 0, 0, pPolygon->nFace, apFace);
    C3dFree((char *)apFace);
}

/*
 *---------------------------------------------------------------------------
 *
 * C3dPolygon --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
C3dItemType *
C3dPolygon(void)
{
    static C3dItemType polygon = {
        "polygon",          /* zType    */
        0,                  /* isOverlay */
        0,                  /* xCreate  */
        polygonMultiCreate, /* xMultiCreate  */
        0,                  /* xDelete  */
        polygonDraw,        /* xDraw    */
        polygonTable,       /* xTable   */
        polygonStatistics,  /* xStatistics */
        polygonCoords       /* xCoords */
    };

    return &polygon;
}
