os/graphics/m3g/m3gcore11/src/m3g_sprite.c
changeset 0 bde4ae8d615e
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/os/graphics/m3g/m3gcore11/src/m3g_sprite.c	Fri Jun 15 03:10:57 2012 +0200
     1.3 @@ -0,0 +1,1044 @@
     1.4 +/*
     1.5 +* Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies).
     1.6 +* All rights reserved.
     1.7 +* This component and the accompanying materials are made available
     1.8 +* under the terms of the License "Eclipse Public License v1.0"
     1.9 +* which accompanies this distribution, and is available
    1.10 +* at the URL "http://www.eclipse.org/legal/epl-v10.html".
    1.11 +*
    1.12 +* Initial Contributors:
    1.13 +* Nokia Corporation - initial contribution.
    1.14 +*
    1.15 +* Contributors:
    1.16 +*
    1.17 +* Description: Sprite implementation
    1.18 +*
    1.19 +*/
    1.20 +
    1.21 +
    1.22 +/*!
    1.23 + * \internal
    1.24 + * \file
    1.25 + * \brief Sprite implementation
    1.26 + */
    1.27 +
    1.28 +#ifndef M3G_CORE_INCLUDE
    1.29 +#   error included by m3g_core.c; do not compile separately.
    1.30 +#endif
    1.31 +
    1.32 +/*#include <stdio.h>*/
    1.33 +
    1.34 +#include "m3g_sprite.h"
    1.35 +#include "m3g_appearance.h"
    1.36 +#include "m3g_camera.h"
    1.37 +#include "m3g_rendercontext.h"
    1.38 +#include "m3g_renderqueue.h"
    1.39 +
    1.40 +#define FLIPX   1
    1.41 +#define FLIPY   2
    1.42 +
    1.43 +
    1.44 +/*----------------------------------------------------------------------
    1.45 + * Internal functions
    1.46 + *--------------------------------------------------------------------*/
    1.47 +
    1.48 +/*!
    1.49 + * \internal
    1.50 + * \brief Destroys this Sprite object.
    1.51 + *
    1.52 + * \param obj Sprite object
    1.53 + */
    1.54 +static void m3gDestroySprite(Object *obj)
    1.55 +{
    1.56 +    Sprite *sprite = (Sprite *) obj;
    1.57 +    M3G_VALIDATE_OBJECT(sprite);
    1.58 +
    1.59 +    M3G_ASSIGN_REF(sprite->image, NULL);
    1.60 +    M3G_ASSIGN_REF(sprite->appearance, NULL);
    1.61 +
    1.62 +    m3gIncStat(M3G_INTERFACE(obj), M3G_STAT_RENDERABLES, -1);
    1.63 +    
    1.64 +    m3gDestroyNode(obj);
    1.65 +}
    1.66 +
    1.67 +/*!
    1.68 + * \internal
    1.69 + * \brief Overloaded Object3D method.
    1.70 + *
    1.71 + * \param property      animation property
    1.72 + * \retval M3G_TRUE     property supported
    1.73 + * \retval M3G_FALSE    property not supported
    1.74 + */
    1.75 +static M3Gbool m3gSpriteIsCompatible(M3Gint property)
    1.76 +{
    1.77 +    switch (property) {
    1.78 +    case M3G_ANIM_CROP:
    1.79 +        return M3G_TRUE;
    1.80 +    default:
    1.81 +        return m3gNodeIsCompatible(property);
    1.82 +    }
    1.83 +}
    1.84 +
    1.85 +/*!
    1.86 + * \internal
    1.87 + * \brief Overloaded Node method
    1.88 + */
    1.89 +static M3Gint m3gSpriteGetBBox(Node *self, AABB *bbox)
    1.90 +{
    1.91 +    Sprite *sprite = (Sprite*) self;
    1.92 +
    1.93 +    /* Only scaled sprites can have a bounding box; non-scaled ones
    1.94 +     * are marked as non-cullable in the "SetParent" function in
    1.95 +     * m3g_node.c */
    1.96 +    
    1.97 +    if (sprite->scaled) {
    1.98 +        const AABB spriteBBox = { { -.5f, -.5f,  0.f },
    1.99 +                                  {  .5f,  .5f,  0.f } };
   1.100 +        *bbox = spriteBBox;
   1.101 +        return (4 * VFC_VERTEX_COST +
   1.102 +                2 * VFC_TRIANGLE_COST +
   1.103 +                VFC_NODE_OVERHEAD);
   1.104 +    }
   1.105 +    else {
   1.106 +        return 0; /* no bounding box for non-scaled sprites */
   1.107 +    }
   1.108 +}
   1.109 +
   1.110 +/*!
   1.111 + * \internal
   1.112 + * \brief Overloaded Object3D method.
   1.113 + *
   1.114 + * \param self          Sprite object
   1.115 + * \param property      animation property
   1.116 + * \param valueSize     size of value array
   1.117 + * \param value         value array
   1.118 + */
   1.119 +static void m3gSpriteUpdateProperty(Object *self,
   1.120 +                                    M3Gint property,
   1.121 +                                    M3Gint valueSize,
   1.122 +                                    const M3Gfloat *value)
   1.123 +{
   1.124 +    Sprite *sprite = (Sprite *) self;
   1.125 +    M3G_VALIDATE_OBJECT(sprite);
   1.126 +    M3G_ASSERT_PTR(value);
   1.127 +
   1.128 +    switch (property) {
   1.129 +    case M3G_ANIM_CROP:
   1.130 +        /* Assert that the value vector is large enough */
   1.131 +        if (valueSize > 2) {
   1.132 +            M3G_ASSERT(valueSize >= 4);
   1.133 +            m3gSetCrop(sprite,  m3gRoundToInt(value[0]),
   1.134 +                       m3gRoundToInt(value[1]),
   1.135 +                       m3gClampInt(m3gRoundToInt(value[2]),
   1.136 +                                   -M3G_MAX_TEXTURE_DIMENSION,
   1.137 +                                   M3G_MAX_TEXTURE_DIMENSION),
   1.138 +                       m3gClampInt(m3gRoundToInt(value[3]),
   1.139 +                                   -M3G_MAX_TEXTURE_DIMENSION,
   1.140 +                                   M3G_MAX_TEXTURE_DIMENSION) );
   1.141 +        }
   1.142 +        else {
   1.143 +            M3G_ASSERT(valueSize >= 2);
   1.144 +            m3gSetCrop(sprite,  m3gRoundToInt(value[0]),
   1.145 +                       m3gRoundToInt(value[1]),
   1.146 +                       sprite->crop.width,
   1.147 +                       sprite->crop.height );
   1.148 +        }
   1.149 +        break;
   1.150 +    default:
   1.151 +        m3gNodeUpdateProperty(self, property, valueSize, value);
   1.152 +    }
   1.153 +}
   1.154 +
   1.155 +/*!
   1.156 + * \internal
   1.157 + * \brief Overloaded Node method.
   1.158 + *
   1.159 + * \param self Sprite object
   1.160 + * \param toCamera transform to camera
   1.161 + * \param alphaFactor total alpha factor
   1.162 + * \param caller caller node
   1.163 + * \param renderQueue RenderQueue
   1.164 + *
   1.165 + * \retval M3G_TRUE continue render setup
   1.166 + * \retval M3G_FALSE abort render setup
   1.167 + */
   1.168 +static M3Gbool m3gSpriteSetupRender(Node *self,
   1.169 +                                    const Node *caller,
   1.170 +                                    SetupRenderState *s,
   1.171 +                                    RenderQueue *renderQueue)
   1.172 +{
   1.173 +    Sprite *sprite = (Sprite *)self;
   1.174 +    Interface *m3g = M3G_INTERFACE(sprite);
   1.175 +    M3G_UNREF(caller);
   1.176 +    m3gIncStat(M3G_INTERFACE(self), M3G_STAT_RENDER_NODES, 1);
   1.177 +
   1.178 +    if ((self->enableBits & NODE_RENDER_BIT) != 0 &&
   1.179 +        (self->scope & renderQueue->scope) != 0) {
   1.180 +        
   1.181 +        if (sprite->appearance != NULL && sprite->image != NULL &&
   1.182 +            sprite->crop.width != 0 && sprite->crop.height != 0) {
   1.183 +
   1.184 +            /* Fetch the cumulative alpha factor for this node */
   1.185 +            sprite->totalAlphaFactor =
   1.186 +                (M3Gushort) m3gGetTotalAlphaFactor((Node*) sprite, renderQueue->root);
   1.187 +
   1.188 +            /* Touch the POT image to make sure it's allocated prior
   1.189 +             * to rendering */
   1.190 +            
   1.191 +            if (!m3gGetPowerOfTwoImage(sprite->image) ||
   1.192 +                !m3gInsertDrawable(m3g,
   1.193 +                                   renderQueue,
   1.194 +                                   self,
   1.195 +                                   &s->toCamera,
   1.196 +                                   0,
   1.197 +                                   m3gGetAppearanceSortKey(sprite->appearance)))
   1.198 +                return M3G_FALSE;
   1.199 +        }
   1.200 +    }
   1.201 +
   1.202 +    return M3G_TRUE;
   1.203 +}
   1.204 +
   1.205 +/*!
   1.206 + * \internal
   1.207 + * \brief Calculates sprite vertex positions and texture coordinates.
   1.208 + *
   1.209 + * \param sprite        Sprite object
   1.210 + * \param ctx           RenderContext object (Graphics3D)
   1.211 + * \param cam           Camera object
   1.212 + * \param vert          vertex position to fill in
   1.213 + * \param texvert       texture coordinates to fill in
   1.214 + * \param eyeSpace      coordinates after modelview
   1.215 + * \param adjust        adjust for texture coorinates, render and
   1.216 + *                      pick need different adjustment
   1.217 + * \retval M3G_TRUE     crop and image intersect
   1.218 + * \retval M3G_FALSE    crop and image do not intersect
   1.219 + */
   1.220 +static M3Gbool m3gGetSpriteCoordinates(Sprite *sprite,
   1.221 +                                       RenderContext *ctx,
   1.222 +                                       const Camera *cam,
   1.223 +                                       const Matrix *toCamera,
   1.224 +                                       M3Gint *vert,
   1.225 +                                       M3Gshort *texvert,
   1.226 +                                       Vec4 *eyeSpace,
   1.227 +                                       M3Gshort adjust)
   1.228 +{
   1.229 +    Vec4 o = {0, 0, 0, 1};      /* Origin */
   1.230 +    Vec4 x = {0.5f, 0, 0, 1};   /* Half of x unit */
   1.231 +    Vec4 y = {0, 0.5f, 0, 1};   /* Half of y unit */
   1.232 +    Vec4 ot;
   1.233 +    Rect rIsect, rImage;
   1.234 +
   1.235 +    rImage.x = 0;
   1.236 +    rImage.y = 0;
   1.237 +    rImage.width = sprite->width;
   1.238 +    rImage.height = sprite->height;
   1.239 +
   1.240 +    /* Intersection of image and crop*/
   1.241 +    if (!m3gIntersectRectangle(&rIsect, &rImage, &sprite->crop)) {
   1.242 +        /* No intersection -> nothing to render / pick */
   1.243 +        return M3G_FALSE;
   1.244 +    }
   1.245 +
   1.246 +    /* Calculate origin and vectors after modelview */
   1.247 +    m3gTransformVec4(toCamera, &o);
   1.248 +    m3gTransformVec4(toCamera, &x);
   1.249 +    m3gTransformVec4(toCamera, &y);
   1.250 +
   1.251 +    ot = o;
   1.252 +
   1.253 +    m3gScaleVec4(&o, m3gRcp(o.w));
   1.254 +    m3gScaleVec4(&x, m3gRcp(x.w));
   1.255 +    m3gScaleVec4(&y, m3gRcp(y.w));
   1.256 +
   1.257 +    /* Store eyespace coordinates */
   1.258 +    if (eyeSpace != NULL) {
   1.259 +        eyeSpace->x = o.x;
   1.260 +        eyeSpace->y = o.y;
   1.261 +        eyeSpace->z = o.z;
   1.262 +    }
   1.263 +
   1.264 +    m3gSubVec4(&x, &o);
   1.265 +    m3gSubVec4(&y, &o);
   1.266 +
   1.267 +    x.x = m3gAdd(ot.x, m3gLengthVec3((const Vec3*) &x));
   1.268 +    x.y = ot.y;
   1.269 +    x.z = ot.z;
   1.270 +    x.w = ot.w;
   1.271 +
   1.272 +    y.y = m3gAdd(ot.y, m3gLengthVec3((const Vec3*) &y));
   1.273 +    y.x = ot.x;
   1.274 +    y.z = ot.z;
   1.275 +    y.w = ot.w;
   1.276 +
   1.277 +    /* Calculate origin and vectors after projection */
   1.278 +    {
   1.279 +        const Matrix *projMatrix = m3gProjectionMatrix(cam);
   1.280 +        m3gTransformVec4(projMatrix, &ot);
   1.281 +        m3gTransformVec4(projMatrix, &x);
   1.282 +        m3gTransformVec4(projMatrix, &y);
   1.283 +    }
   1.284 +#ifndef M3G_USE_NGL_API
   1.285 +    /* Store w after projection */
   1.286 +    if (eyeSpace != NULL) {
   1.287 +        eyeSpace->w = ot.w;
   1.288 +    }
   1.289 +#endif
   1.290 +    m3gScaleVec4(&ot, m3gRcp(ot.w));
   1.291 +    m3gScaleVec4(&x, m3gRcp(x.w));
   1.292 +    m3gScaleVec4(&y, m3gRcp(y.w));
   1.293 +
   1.294 +    m3gSubVec4(&x, &ot);
   1.295 +    m3gSubVec4(&y, &ot);
   1.296 +
   1.297 +    x.x = m3gLengthVec3((const Vec3*) &x);
   1.298 +    y.y = m3gLengthVec3((const Vec3*) &y);
   1.299 +
   1.300 +    /* Non-scaled sprites take width from crop rectangle*/
   1.301 +    if (!sprite->scaled) {
   1.302 +        M3Gint viewport[4];
   1.303 +        if (ctx != NULL) {
   1.304 +            m3gGetViewport(ctx, viewport, viewport + 1, viewport + 2, viewport + 3);
   1.305 +        }
   1.306 +        else {
   1.307 +            /* Use a dummy viewport, this is only when picking and
   1.308 +               not rendering to anything. Values must represent a valid viewport */
   1.309 +            viewport[0] = 0;
   1.310 +            viewport[1] = 0;
   1.311 +            viewport[2] = 256;
   1.312 +            viewport[3] = 256;
   1.313 +        }
   1.314 +
   1.315 +        x.x = m3gDivif (rIsect.width, viewport[2]);
   1.316 +        y.y = m3gDivif (rIsect.height, viewport[3]);
   1.317 +
   1.318 +        ot.x = m3gSub(ot.x,
   1.319 +                      m3gDivif (2 * sprite->crop.x + sprite->crop.width - 2 * rIsect.x - rIsect.width,
   1.320 +                                viewport[2]));
   1.321 +
   1.322 +        ot.y = m3gAdd(ot.y,
   1.323 +                      m3gDivif (2 * sprite->crop.y + sprite->crop.height - 2 * rIsect.y - rIsect.height,
   1.324 +                                viewport[3]));
   1.325 +    }
   1.326 +    else {
   1.327 +        /* Adjust width and height according to cropping rectangle */
   1.328 +        x.x = m3gDiv(x.x, (M3Gfloat) sprite->crop.width);
   1.329 +        y.y = m3gDiv(y.y, (M3Gfloat) sprite->crop.height);
   1.330 +
   1.331 +        ot.x = m3gSub(ot.x,
   1.332 +                      m3gMul((M3Gfloat)(2 * sprite->crop.x + sprite->crop.width - 2 * rIsect.x - rIsect.width),
   1.333 +                             x.x));
   1.334 +
   1.335 +        ot.y = m3gAdd(ot.y,
   1.336 +                      m3gMul((M3Gfloat)(2 * sprite->crop.y + sprite->crop.height - 2 * rIsect.y - rIsect.height),
   1.337 +                             y.y));
   1.338 +
   1.339 +        x.x = m3gMul(x.x, (M3Gfloat) rIsect.width);
   1.340 +        y.y = m3gMul(y.y, (M3Gfloat) rIsect.height);
   1.341 +    }
   1.342 +#ifdef M3G_USE_NGL_API
   1.343 +    /* Store final Z */
   1.344 +    if (eyeSpace != NULL) {
   1.345 +        eyeSpace->w = ot.z;
   1.346 +    }
   1.347 +#endif
   1.348 +    /* Set up positions */
   1.349 +    vert[0 * 3 + 0] = (M3Gint) m3gMul(65536, m3gSub(ot.x, x.x));
   1.350 +    vert[0 * 3 + 1] = m3gRoundToInt(m3gAdd(m3gMul(65536, m3gAdd(ot.y, y.y)), 0.5f));
   1.351 +    vert[0 * 3 + 2] = m3gRoundToInt(m3gMul(65536, ot.z));
   1.352 +
   1.353 +    vert[1 * 3 + 0] = vert[0 * 3 + 0];
   1.354 +    vert[1 * 3 + 1] = (M3Gint) m3gMul(65536, m3gSub(ot.y, y.y));
   1.355 +    vert[1 * 3 + 2] = vert[0 * 3 + 2];
   1.356 +
   1.357 +    vert[2 * 3 + 0] = m3gRoundToInt(m3gAdd(m3gMul(65536, m3gAdd(ot.x, x.x)), 0.5f));
   1.358 +    vert[2 * 3 + 1] = vert[0 * 3 + 1];
   1.359 +    vert[2 * 3 + 2] = vert[0 * 3 + 2];
   1.360 +
   1.361 +    vert[3 * 3 + 0] = vert[2 * 3 + 0];
   1.362 +    vert[3 * 3 + 1] = vert[1 * 3 + 1];
   1.363 +    vert[3 * 3 + 2] = vert[0 * 3 + 2];
   1.364 +
   1.365 +    /* Set up texture coordinates */
   1.366 +    if (!(sprite->flip & FLIPX)) {
   1.367 +        texvert[0 * 2 + 0] = (M3Gshort) rIsect.x;
   1.368 +        texvert[1 * 2 + 0] = (M3Gshort) rIsect.x;
   1.369 +        texvert[2 * 2 + 0] = (M3Gshort) (rIsect.x + rIsect.width - adjust);
   1.370 +        texvert[3 * 2 + 0] = (M3Gshort) (rIsect.x + rIsect.width - adjust);
   1.371 +    }
   1.372 +    else {
   1.373 +        texvert[0 * 2 + 0] = (M3Gshort) (rIsect.x + rIsect.width - adjust);
   1.374 +        texvert[1 * 2 + 0] = (M3Gshort) (rIsect.x + rIsect.width - adjust);
   1.375 +        texvert[2 * 2 + 0] = (M3Gshort) rIsect.x;
   1.376 +        texvert[3 * 2 + 0] = (M3Gshort) rIsect.x;
   1.377 +    }
   1.378 +
   1.379 +    if (!(sprite->flip & FLIPY)) {
   1.380 +        texvert[0 * 2 + 1] = (M3Gshort) rIsect.y;
   1.381 +        texvert[1 * 2 + 1] = (M3Gshort) (rIsect.y + rIsect.height - adjust);
   1.382 +        texvert[2 * 2 + 1] = (M3Gshort) rIsect.y;
   1.383 +        texvert[3 * 2 + 1] = (M3Gshort) (rIsect.y + rIsect.height - adjust);
   1.384 +    }
   1.385 +    else {
   1.386 +        texvert[0 * 2 + 1] = (M3Gshort) (rIsect.y + rIsect.height - adjust);
   1.387 +        texvert[1 * 2 + 1] = (M3Gshort) rIsect.y;
   1.388 +        texvert[2 * 2 + 1] = (M3Gshort) (rIsect.y + rIsect.height - adjust);
   1.389 +        texvert[3 * 2 + 1] = (M3Gshort) rIsect.y;
   1.390 +    }
   1.391 +
   1.392 +    return M3G_TRUE;
   1.393 +}
   1.394 +
   1.395 +/*!
   1.396 + * \internal
   1.397 + * \brief Overloaded Node method.
   1.398 + *
   1.399 + * Renders the sprite as a textured quad.
   1.400 + *
   1.401 + * \param self Mesh object
   1.402 + * \param ctx current render context
   1.403 + * \param patchIndex submesh index
   1.404 + */
   1.405 +static void m3gSpriteDoRender(Node *self,
   1.406 +                              RenderContext *ctx,
   1.407 +                              const Matrix *toCamera,
   1.408 +                              M3Gint patchIndex)
   1.409 +{
   1.410 +    Sprite *sprite = (Sprite *)self;
   1.411 +    M3Gshort texvert[4 * 2];
   1.412 +    M3Gint vert[4 * 3];
   1.413 +    Vec4 eyeSpace;
   1.414 +    Image *imagePow2;
   1.415 +    M3G_UNREF(patchIndex);
   1.416 +
   1.417 +    M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS);
   1.418 +    if (!m3gGetSpriteCoordinates(sprite,
   1.419 +                                 ctx,
   1.420 +                                 m3gGetCurrentCamera(ctx),
   1.421 +                                 toCamera,
   1.422 +                                 vert,
   1.423 +                                 texvert,
   1.424 +                                 &eyeSpace,
   1.425 +                                 0)) {
   1.426 +        return;
   1.427 +    }
   1.428 +    M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS);
   1.429 +
   1.430 +    /* Get power of two image */
   1.431 +    imagePow2 = m3gGetPowerOfTwoImage(sprite->image);
   1.432 +    /* If NULL -> out of memory */
   1.433 +    if (imagePow2 == NULL) {
   1.434 +        return;
   1.435 +    }
   1.436 +
   1.437 +    if (m3gGetColorMaskWorkaround(M3G_INTERFACE(ctx))) {
   1.438 +        m3gUpdateColorMaskStatus(ctx,
   1.439 +                                 m3gColorMask(sprite->appearance),
   1.440 +                                 m3gAlphaMask(sprite->appearance));
   1.441 +    }
   1.442 +
   1.443 +    /* Disable unwanted state. Note that we do this BEFORE setting the
   1.444 +     * sprite color to avoid any problems with glColorMaterial  */
   1.445 +    m3gApplyDefaultMaterial();
   1.446 +    m3gApplyDefaultPolygonMode();
   1.447 +
   1.448 +    /* Disable color array, normals and textures*/
   1.449 +    glDisableClientState(GL_COLOR_ARRAY);
   1.450 +    glDisableClientState(GL_NORMAL_ARRAY);
   1.451 +    m3gDisableTextures();
   1.452 +
   1.453 +    /* Sprite image to texture unit 0 */
   1.454 +    glClientActiveTexture(GL_TEXTURE0);
   1.455 +    glActiveTexture(GL_TEXTURE0);
   1.456 +    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
   1.457 +    glTexCoordPointer(2, GL_SHORT, 0, texvert);
   1.458 +    glEnable(GL_TEXTURE_2D);
   1.459 +    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, (GLfixed) GL_MODULATE);
   1.460 +    m3gBindTextureImage(imagePow2,
   1.461 +                        M3G_FILTER_BASE_LEVEL,
   1.462 +                        m3gIsAccelerated(ctx) ? M3G_FILTER_LINEAR : M3G_FILTER_NEAREST);
   1.463 +
   1.464 +    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   1.465 +    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
   1.466 +    
   1.467 +    glMatrixMode(GL_TEXTURE);
   1.468 +    glLoadIdentity();
   1.469 +    glScalef(m3gRcp((M3Gfloat) m3gGetWidth(sprite->image)),
   1.470 +             m3gRcp((M3Gfloat) m3gGetHeight(sprite->image)),
   1.471 +             1.f);
   1.472 +    glMatrixMode(GL_MODELVIEW);
   1.473 +
   1.474 +    /* Apply fog and compositing mode */
   1.475 +#ifdef M3G_USE_NGL_API
   1.476 +    m3gApplySpriteFog(sprite->appearance->fog, eyeSpace.z, eyeSpace.w);
   1.477 +#else
   1.478 +    m3gApplyFog(sprite->appearance->fog);
   1.479 +#endif
   1.480 +    m3gApplyCompositingMode(sprite->appearance->compositingMode, ctx);
   1.481 +
   1.482 +    {
   1.483 +        GLfixed a = (GLfixed) (0xff * sprite->totalAlphaFactor);
   1.484 +        a = (a >> (NODE_ALPHA_FACTOR_BITS - 8))
   1.485 +            + (a >> NODE_ALPHA_FACTOR_BITS)
   1.486 +            + (a >> (NODE_ALPHA_FACTOR_BITS + 7));
   1.487 +        glColor4x((GLfixed) 1 << 16, (GLfixed) 1 << 16, (GLfixed) 1 << 16, a);
   1.488 +    }
   1.489 +
   1.490 +    /* Load vertices */
   1.491 +    glEnableClientState(GL_VERTEX_ARRAY);
   1.492 +    glVertexPointer(3, GL_FIXED, 0, vert);
   1.493 +
   1.494 +    /* Store current matrices, then set up an identity modelview and
   1.495 +     * projection */
   1.496 +
   1.497 +    m3gPushScreenSpace(ctx, M3G_FALSE);
   1.498 +
   1.499 +#ifndef M3G_USE_NGL_API
   1.500 +    /* Transform the sprite vertices (in NDC) back to eye coordinates, so that 
   1.501 +       the fog distance will be calculated correctly in the OpenGL pipeline. */
   1.502 +    {
   1.503 +        GLfloat transform[16];
   1.504 +        GLfloat scaleW[16] = { 0.f, 0.f, 0.f, 0.f,
   1.505 +                               0.f, 0.f, 0.f, 0.f,
   1.506 +                               0.f, 0.f, 0.f, 0.f,
   1.507 +                               0.f, 0.f, 0.f, 0.f };
   1.508 +        Matrix invProjMatrix;
   1.509 +        const Matrix *projMatrix = m3gProjectionMatrix(m3gGetCurrentCamera(ctx));
   1.510 +
   1.511 +        m3gMatrixInverse(&invProjMatrix, projMatrix);
   1.512 +		m3gGetMatrixColumns(&invProjMatrix, transform);
   1.513 +        
   1.514 +        glMatrixMode(GL_MODELVIEW);
   1.515 +        glMultMatrixf(transform);
   1.516 +        scaleW[0] = scaleW[5] = scaleW[10] = scaleW[15] = eyeSpace.w;
   1.517 +        glMultMatrixf(scaleW);
   1.518 +
   1.519 +        glMatrixMode(GL_PROJECTION);
   1.520 +        m3gGetMatrixColumns(projMatrix, transform);
   1.521 +        glLoadMatrixf(transform);
   1.522 +    }
   1.523 +#endif
   1.524 +
   1.525 +    /* Load indices -> draws the sprite */
   1.526 +    M3G_BEGIN_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_NGL_DRAW);
   1.527 +    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
   1.528 +    M3G_END_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_NGL_DRAW);
   1.529 +
   1.530 +    m3gReleaseTextureImage(imagePow2);
   1.531 +    
   1.532 +    /* Restore the previous modelview and projection */
   1.533 +
   1.534 +    m3gPopSpace(ctx);
   1.535 +}
   1.536 +
   1.537 +/*!
   1.538 + * \internal
   1.539 + * \brief Overloaded Node method.
   1.540 + *
   1.541 + * Picks a scaled sprite as 2D from viewport.
   1.542 + *
   1.543 + * \param self      Mesh object
   1.544 + * \param mask      pick scope mask
   1.545 + * \param ray       pick ray
   1.546 + * \param ri        RayIntersection object
   1.547 + * \param toGroup   transform to originating group
   1.548 + * \retval          M3G_TRUE    continue pick
   1.549 + * \retval          M3G_FALSE   abort pick
   1.550 + */
   1.551 +static M3Gbool m3gSpriteRayIntersect(Node *self,
   1.552 +                                     M3Gint mask,
   1.553 +                                     M3Gfloat *ray,
   1.554 +                                     RayIntersection *ri,
   1.555 +                                     Matrix *toGroup)
   1.556 +{
   1.557 +    Sprite *sprite = (Sprite *)self;
   1.558 +    M3Gshort texvert[4 * 2];
   1.559 +    M3Gint vert[4 * 3];
   1.560 +    M3Gint x, y;
   1.561 +    Vec4 eyeSpace;
   1.562 +    M3Gfloat distance;
   1.563 +    M3G_UNREF(toGroup);
   1.564 +
   1.565 +    /* Check that picking is possible */
   1.566 +    
   1.567 +    if (sprite->image == NULL ||
   1.568 +        sprite->appearance == NULL ||
   1.569 +        ri->camera == NULL ||
   1.570 +        !sprite->scaled ||
   1.571 +        sprite->crop.width == 0 ||
   1.572 +        sprite->crop.height == 0 ||
   1.573 +        (self->scope & mask) == 0) {
   1.574 +        return M3G_TRUE;
   1.575 +    }
   1.576 +
   1.577 +    /* Calculate modelview transform, picking is possible without rendering */
   1.578 +    
   1.579 +    {
   1.580 +        Matrix toCamera;
   1.581 +        
   1.582 +        if (!m3gGetTransformTo(self, (Node *)ri->camera,
   1.583 +                               &toCamera)) {
   1.584 +            return M3G_FALSE;
   1.585 +        }
   1.586 +        if (!m3gGetSpriteCoordinates(sprite, NULL,
   1.587 +                                     (const Camera *)ri->camera, &toCamera,
   1.588 +                                     vert, texvert, &eyeSpace, 1)) {
   1.589 +            return M3G_TRUE;
   1.590 +        }
   1.591 +    }
   1.592 +
   1.593 +    /* Do the pick in 2D, formula is from the spec and values are
   1.594 +       set to 16.16 fixed point format */
   1.595 +    
   1.596 +    x = m3gRoundToInt(m3gMul(2 * 65536, ri->x)) - 65536;
   1.597 +    y = 65536 - m3gRoundToInt(m3gMul(2 * 65536, ri->y));
   1.598 +
   1.599 +    if (x >= vert[0 * 3 + 0] && x <= vert[2 * 3 + 0] &&
   1.600 +        y <= vert[0 * 3 + 1] && y >= vert[1 * 3 + 1] ) {
   1.601 +
   1.602 +        distance = m3gDiv(m3gSub(eyeSpace.z, ray[6]), m3gSub(ray[7], ray[6]));
   1.603 +
   1.604 +        if (distance <= 0 ||
   1.605 +            distance >= ri->tMin) return M3G_TRUE;
   1.606 +
   1.607 +        ri->tMin = distance;
   1.608 +        ri->distance = ri->tMin;
   1.609 +        ri->submeshIndex = 0;
   1.610 +
   1.611 +        x -= vert[0 * 3 + 0];
   1.612 +        y  = vert[0 * 3 + 1] - y;
   1.613 +
   1.614 +        if (!(sprite->flip & FLIPX)) {
   1.615 +            ri->textureS[0] = m3gAdd(texvert[0 * 2 + 0],
   1.616 +                                     m3gDivif ((texvert[2 * 2 + 0] - texvert[0 * 2 + 0] + 1) * x,
   1.617 +                                               vert[2 * 3 + 0] - vert[0 * 3 + 0]));
   1.618 +        }
   1.619 +        else {
   1.620 +            ri->textureS[0] = m3gSub((M3Gfloat)(texvert[0 * 2 + 0] + 1),
   1.621 +                                     m3gDivif ((texvert[0 * 2 + 0] - texvert[2 * 2 + 0] + 1) * x,
   1.622 +                                               vert[2 * 3 + 0] - vert[0 * 3 + 0]));
   1.623 +        }
   1.624 +
   1.625 +        if (!(sprite->flip & FLIPY)) {
   1.626 +            ri->textureT[0] = m3gAdd(texvert[0 * 2 + 1],
   1.627 +                                     m3gDivif ((texvert[1 * 2 + 1] - texvert[0 * 2 + 1] + 1) * y,
   1.628 +                                               vert[0 * 3 + 1] - vert[1 * 3 + 1]));
   1.629 +        }
   1.630 +        else {
   1.631 +            ri->textureT[0] = m3gSub((M3Gfloat)(texvert[0 * 2 + 1] + 1),
   1.632 +                                     m3gDivif ((texvert[0 * 2 + 1] - texvert[1 * 2 + 1] + 1) * y,
   1.633 +                                               vert[0 * 3 + 1] - vert[1 * 3 + 1]));
   1.634 +        }
   1.635 +
   1.636 +        {
   1.637 +            /* Finally check against alpha */
   1.638 +            M3Gint threshold = 0, alpha;
   1.639 +
   1.640 +            if (sprite->appearance->compositingMode) {
   1.641 +                threshold = (M3Gint)m3gMul(m3gGetAlphaThreshold(sprite->appearance->compositingMode), 256);
   1.642 +            }
   1.643 +
   1.644 +            alpha = m3gGetAlpha(sprite->image, (M3Gint)ri->textureS[0], (M3Gint)ri->textureT[0]);
   1.645 +
   1.646 +            if (alpha >= threshold) {
   1.647 +                /* Normalize texture coordinates */
   1.648 +                ri->textureS[0] = m3gDiv(ri->textureS[0], (M3Gfloat) sprite->width);
   1.649 +                ri->textureT[0] = m3gDiv(ri->textureT[0], (M3Gfloat) sprite->height);
   1.650 +
   1.651 +                ri->textureS[1] = 0.f;
   1.652 +                ri->textureT[1] = 0.f;
   1.653 +
   1.654 +                ri->normal[0] = 0.f;
   1.655 +                ri->normal[1] = 0.f;
   1.656 +                ri->normal[2] = 1.f;
   1.657 +
   1.658 +                ri->intersected = self;
   1.659 +            }
   1.660 +        }
   1.661 +    }
   1.662 +
   1.663 +    return M3G_TRUE;
   1.664 +}
   1.665 +
   1.666 +/*!
   1.667 + * \internal
   1.668 + * \brief Overloaded Object3D method.
   1.669 + *
   1.670 + * \param self Sprite object
   1.671 + * \param references array of reference objects
   1.672 + * \return number of references
   1.673 + */
   1.674 +static M3Gint m3gSpriteDoGetReferences(Object *self, Object **references)
   1.675 +{
   1.676 +    Sprite *sprite = (Sprite *)self;
   1.677 +    int num = m3gObjectDoGetReferences(self, references);
   1.678 +    if (sprite->image != NULL) {
   1.679 +        if (references != NULL)
   1.680 +            references[num] = (Object *)sprite->image;
   1.681 +        num++;
   1.682 +    }
   1.683 +    if (sprite->appearance != NULL) {
   1.684 +        if (references != NULL)
   1.685 +            references[num] = (Object *)sprite->appearance;
   1.686 +        num++;
   1.687 +    }
   1.688 +    return num;
   1.689 +}
   1.690 +
   1.691 +/*!
   1.692 + * \internal
   1.693 + * \brief Overloaded Object3D method.
   1.694 + */
   1.695 +static Object *m3gSpriteFindID(Object *self, M3Gint userID)
   1.696 +{
   1.697 +    Sprite *sprite = (Sprite *)self;
   1.698 +    Object *found = m3gObjectFindID(self, userID);
   1.699 +        
   1.700 +    if (!found && sprite->image != NULL) {
   1.701 +        found = m3gFindID((Object*) sprite->image, userID);
   1.702 +    }
   1.703 +    if (!found && sprite->appearance != NULL) {
   1.704 +        found = m3gFindID((Object*) sprite->appearance, userID);
   1.705 +    }
   1.706 +    return found;
   1.707 +}
   1.708 +
   1.709 +/*!
   1.710 + * \internal
   1.711 + * \brief Overloaded Object3D method.
   1.712 + *
   1.713 + * \param originalObj original Sprite object
   1.714 + * \param cloneObj pointer to cloned Sprite object
   1.715 + * \param pairs array for all object-duplicate pairs
   1.716 + * \param numPairs number of pairs
   1.717 + */
   1.718 +static M3Gbool m3gSpriteDuplicate(const Object *originalObj,
   1.719 +                                  Object **cloneObj,
   1.720 +                                  Object **pairs,
   1.721 +                                  M3Gint *numPairs)
   1.722 +{
   1.723 +    Sprite *original = (Sprite *)originalObj;
   1.724 +    Sprite *clone;
   1.725 +    M3G_ASSERT(*cloneObj == NULL); /* no derived classes */
   1.726 +
   1.727 +    /* Create the clone object */
   1.728 +    
   1.729 +    clone = (Sprite *)m3gCreateSprite(originalObj->interface,
   1.730 +                                      original->scaled,
   1.731 +                                      original->image,
   1.732 +                                      original->appearance);
   1.733 +    if (!clone) {
   1.734 +        return M3G_FALSE;
   1.735 +    }
   1.736 +    *cloneObj = (Object *)clone;
   1.737 +
   1.738 +    /* Duplicate our own fields */
   1.739 +    
   1.740 +    clone->crop = original->crop;
   1.741 +    clone->flip = original->flip;
   1.742 +    
   1.743 +    /* Duplicate base class data */
   1.744 +    
   1.745 +    return m3gNodeDuplicate(originalObj, cloneObj, pairs, numPairs);
   1.746 +}
   1.747 +
   1.748 +/*!
   1.749 + * \internal
   1.750 + * \brief Overloaded Object3D method.
   1.751 + *
   1.752 + * \param self Sprite object
   1.753 + * \param time current world time
   1.754 + * \return minimum validity
   1.755 + */
   1.756 +static M3Gint m3gSpriteApplyAnimation(Object *self, M3Gint time)
   1.757 +{
   1.758 +    M3Gint validity, minValidity;
   1.759 +    Sprite *sprite = (Sprite *)self;
   1.760 +    Object *app;
   1.761 +    M3G_VALIDATE_OBJECT(sprite);
   1.762 +
   1.763 +    minValidity = m3gObjectApplyAnimation(self, time);
   1.764 +
   1.765 +    if (minValidity > 0) {
   1.766 +        app = (Object *) sprite->appearance;
   1.767 +        
   1.768 +        if (app != NULL) {
   1.769 +            validity = M3G_VFUNC(Object, app, applyAnimation)(app, time);
   1.770 +            minValidity = M3G_MIN(validity, minValidity);
   1.771 +        }
   1.772 +    }
   1.773 +    return minValidity;
   1.774 +}
   1.775 +
   1.776 +/*!
   1.777 + * \internal
   1.778 + * \brief Initializes a Sprite object. See specification
   1.779 + * for default values.
   1.780 + *
   1.781 + * \param m3g           M3G interface
   1.782 + * \param sprite        Sprite object
   1.783 + * \param scaled        scaled flag
   1.784 + * \param appearance    Appearance object
   1.785 + * \param image         Image2D object
   1.786 + * \retval M3G_TRUE     Sprite initialized
   1.787 + * \retval M3G_FALSE    initialization failed
   1.788 + */
   1.789 +static M3Gbool m3gInitSprite(Interface *m3g,
   1.790 +                             Sprite *sprite,
   1.791 +                             M3Gbool scaled,
   1.792 +                             Appearance *appearance,
   1.793 +                             Image *image)
   1.794 +{
   1.795 +    /* Sprite is derived from node */
   1.796 +    m3gInitNode(m3g, &sprite->node, M3G_CLASS_SPRITE);
   1.797 +    sprite->node.hasRenderables = M3G_TRUE;
   1.798 +
   1.799 +    m3gIncStat(m3g, M3G_STAT_RENDERABLES, 1);
   1.800 +    
   1.801 +    sprite->scaled = scaled;
   1.802 +    M3G_ASSIGN_REF(sprite->appearance, appearance);
   1.803 +    return m3gSetSpriteImage(sprite, image);
   1.804 +}
   1.805 +
   1.806 +/*----------------------------------------------------------------------
   1.807 + * Virtual function table
   1.808 + *--------------------------------------------------------------------*/
   1.809 +
   1.810 +static const NodeVFTable m3gvf_Sprite = {
   1.811 +    {
   1.812 +        {
   1.813 +            m3gSpriteApplyAnimation,
   1.814 +            m3gSpriteIsCompatible,
   1.815 +            m3gSpriteUpdateProperty,
   1.816 +            m3gSpriteDoGetReferences,
   1.817 +            m3gSpriteFindID,
   1.818 +            m3gSpriteDuplicate,
   1.819 +            m3gDestroySprite
   1.820 +        }
   1.821 +    },
   1.822 +    m3gNodeAlign,
   1.823 +    m3gSpriteDoRender,
   1.824 +    m3gSpriteGetBBox,
   1.825 +    m3gSpriteRayIntersect,
   1.826 +    m3gSpriteSetupRender,
   1.827 +    m3gNodeUpdateDuplicateReferences,
   1.828 +    m3gNodeValidate
   1.829 +};
   1.830 +
   1.831 +
   1.832 +/*----------------------------------------------------------------------
   1.833 + * Public API functions
   1.834 + *--------------------------------------------------------------------*/
   1.835 +
   1.836 +/*!
   1.837 + * \brief Creates a Sprite object.
   1.838 + *
   1.839 + * \param hInterface    M3G interface
   1.840 + * \param scaled        scaled flag
   1.841 + * \param hImage        Image2D object
   1.842 + * \param hAppearance   Appearance object
   1.843 + * \retval Sprite new Sprite object
   1.844 + * \retval NULL Sprite creating failed
   1.845 + */
   1.846 +M3G_API M3GSprite m3gCreateSprite(M3GInterface hInterface,
   1.847 +                                  M3Gbool scaled,
   1.848 +                                  M3GImage hImage,
   1.849 +                                  M3GAppearance hAppearance)
   1.850 +{
   1.851 +    Interface *m3g = (Interface *) hInterface;
   1.852 +    M3G_VALIDATE_INTERFACE(m3g);
   1.853 +
   1.854 +    if (hImage == 0) {
   1.855 +        m3gRaiseError(m3g, M3G_NULL_POINTER);
   1.856 +        return NULL;
   1.857 +    }
   1.858 +
   1.859 +    {
   1.860 +        Sprite *sprite =  m3gAllocZ(m3g, sizeof(Sprite));
   1.861 +
   1.862 +        if (sprite != NULL) {
   1.863 +            if (!m3gInitSprite(m3g,
   1.864 +                               sprite,
   1.865 +                               scaled,
   1.866 +                               (Appearance *)hAppearance,
   1.867 +                               (Image *)hImage)) {
   1.868 +                M3G_ASSIGN_REF(sprite->image, NULL);
   1.869 +                M3G_ASSIGN_REF(sprite->appearance, NULL);
   1.870 +                m3gFree(m3g, sprite);
   1.871 +                return NULL;
   1.872 +            }
   1.873 +        }
   1.874 +
   1.875 +        return (M3GSprite) sprite;
   1.876 +    }
   1.877 +}
   1.878 +
   1.879 +/*!
   1.880 + * \brief Get sprite scaled flag.
   1.881 + *
   1.882 + * \param handle        Sprite object
   1.883 + * \retval M3G_TRUE     sprite is scaled
   1.884 + * \retval M3G_FALSE    sprite is not scaled
   1.885 + */
   1.886 +M3G_API M3Gbool m3gIsScaledSprite(M3GSprite handle)
   1.887 +{
   1.888 +    Sprite *sprite = (Sprite *) handle;
   1.889 +    M3G_VALIDATE_OBJECT(sprite);
   1.890 +
   1.891 +    return sprite->scaled;
   1.892 +}
   1.893 +
   1.894 +/*!
   1.895 + * \brief Set sprite appearance.
   1.896 + *
   1.897 + * \param handle        Sprite object
   1.898 + * \param hAppearance   Appearance object
   1.899 + */
   1.900 +M3G_API void m3gSetSpriteAppearance(M3GSprite handle,
   1.901 +                                    M3GAppearance hAppearance)
   1.902 +{
   1.903 +    Sprite *sprite = (Sprite *) handle;
   1.904 +    M3G_VALIDATE_OBJECT(sprite);
   1.905 +
   1.906 +    M3G_ASSIGN_REF(sprite->appearance, hAppearance);
   1.907 +}
   1.908 +
   1.909 +/*!
   1.910 + * \brief Set sprite image
   1.911 + *
   1.912 + * \param handle        Sprite object
   1.913 + * \param hImage        Image2D object
   1.914 + * \retval              M3G_TRUE image was set
   1.915 + * \retval              M3G_FALSE failed to set image
   1.916 + */
   1.917 +M3G_API M3Gbool m3gSetSpriteImage(M3GSprite handle, M3GImage hImage)
   1.918 +{
   1.919 +    Sprite *sprite = (Sprite *) handle;
   1.920 +    Image *image = (Image *)hImage;
   1.921 +    M3G_VALIDATE_OBJECT(sprite);
   1.922 +
   1.923 +    if (image == NULL) {
   1.924 +        m3gRaiseError(M3G_INTERFACE(sprite), M3G_NULL_POINTER);
   1.925 +        return M3G_FALSE;
   1.926 +    }
   1.927 +
   1.928 +    M3G_ASSIGN_REF(sprite->image, image);
   1.929 +
   1.930 +    sprite->width = m3gGetWidth(image);
   1.931 +    sprite->height = m3gGetHeight(image);
   1.932 +
   1.933 +    sprite->crop.x = 0;
   1.934 +    sprite->crop.y = 0;
   1.935 +    sprite->crop.width  = m3gClampInt(sprite->width,  0, M3G_MAX_TEXTURE_DIMENSION);
   1.936 +    sprite->crop.height = m3gClampInt(sprite->height, 0, M3G_MAX_TEXTURE_DIMENSION);
   1.937 +
   1.938 +    sprite->flip = 0;
   1.939 +
   1.940 +    return M3G_TRUE;
   1.941 +}
   1.942 +
   1.943 +/*!
   1.944 + * \brief Set sprite image crop rectangle.
   1.945 + *
   1.946 + * \param handle        Sprite object
   1.947 + * \param cropX         crop upper left x
   1.948 + * \param cropY         crop upper left y
   1.949 + * \param width         crop width
   1.950 + * \param height        crop height
   1.951 + */
   1.952 +M3G_API void m3gSetCrop(M3GSprite handle,
   1.953 +                        M3Gint cropX, M3Gint cropY,
   1.954 +                        M3Gint width, M3Gint height)
   1.955 +{
   1.956 +    Sprite *sprite = (Sprite *) handle;
   1.957 +    M3G_VALIDATE_OBJECT(sprite);
   1.958 +
   1.959 +    /* Check for illegal crop size */
   1.960 +    if (!m3gInRange(width,  -M3G_MAX_TEXTURE_DIMENSION, M3G_MAX_TEXTURE_DIMENSION) ||
   1.961 +        !m3gInRange(height, -M3G_MAX_TEXTURE_DIMENSION, M3G_MAX_TEXTURE_DIMENSION) ) {
   1.962 +        m3gRaiseError(M3G_INTERFACE(sprite), M3G_INVALID_VALUE);
   1.963 +        return;
   1.964 +    }
   1.965 +
   1.966 +    sprite->crop.x = cropX;
   1.967 +    sprite->crop.y = cropY;
   1.968 +
   1.969 +    if (width < 0) {
   1.970 +        sprite->crop.width = -width;
   1.971 +        sprite->flip |= FLIPX;
   1.972 +    }
   1.973 +    else {
   1.974 +        sprite->crop.width = width;
   1.975 +        sprite->flip &= ~FLIPX;
   1.976 +    }
   1.977 +
   1.978 +    if (height < 0) {
   1.979 +        sprite->crop.height = -height;
   1.980 +        sprite->flip |= FLIPY;
   1.981 +    }
   1.982 +    else {
   1.983 +        sprite->crop.height = height;
   1.984 +        sprite->flip &= ~FLIPY;
   1.985 +    }
   1.986 +}
   1.987 +
   1.988 +/*!
   1.989 + * \brief Get sprite image crop parameter.
   1.990 + *
   1.991 + * \param handle        Sprite object
   1.992 + * \param which         which crop parameter to return
   1.993 + *                      \arg M3G_GET_CROPX
   1.994 + *                      \arg M3G_GET_CROPY
   1.995 + *                      \arg M3G_GET_CROPWIDTH
   1.996 + *                      \arg M3G_GET_CROPHEIGHT
   1.997 + * \return              image crop parameter
   1.998 + */
   1.999 +M3Gint m3gGetCrop(M3GSprite handle, M3Gint which)
  1.1000 +{
  1.1001 +    Sprite *sprite = (Sprite *) handle;
  1.1002 +    M3G_VALIDATE_OBJECT(sprite);
  1.1003 +
  1.1004 +    switch(which) {
  1.1005 +    case M3G_GET_CROPX:
  1.1006 +        return sprite->crop.x;
  1.1007 +    case M3G_GET_CROPY:
  1.1008 +        return sprite->crop.y;
  1.1009 +    case M3G_GET_CROPWIDTH:
  1.1010 +        return (sprite->flip & FLIPX) ? -sprite->crop.width : sprite->crop.width;
  1.1011 +    case M3G_GET_CROPHEIGHT:
  1.1012 +    default:
  1.1013 +        return (sprite->flip & FLIPY) ? -sprite->crop.height : sprite->crop.height;
  1.1014 +    }
  1.1015 +}
  1.1016 +
  1.1017 +/*!
  1.1018 + * \brief Gets sprite appearance.
  1.1019 + *
  1.1020 + * \param handle        Sprite object
  1.1021 + * \return              Appearance object
  1.1022 + */
  1.1023 +M3G_API M3GAppearance m3gGetSpriteAppearance(M3GSprite handle)
  1.1024 +{
  1.1025 +    Sprite *sprite = (Sprite *) handle;
  1.1026 +    M3G_VALIDATE_OBJECT(sprite);
  1.1027 +
  1.1028 +    return sprite->appearance;
  1.1029 +}
  1.1030 +
  1.1031 +/*!
  1.1032 + * \brief Gets sprite image.
  1.1033 + *
  1.1034 + * \param handle        Sprite object
  1.1035 + * \return              Image2D object
  1.1036 + */
  1.1037 +M3G_API M3GImage m3gGetSpriteImage(M3GSprite handle)
  1.1038 +{
  1.1039 +    Sprite *sprite = (Sprite *) handle;
  1.1040 +    M3G_VALIDATE_OBJECT(sprite);
  1.1041 +
  1.1042 +    return sprite->image;
  1.1043 +}
  1.1044 +
  1.1045 +#undef FLIPX
  1.1046 +#undef FLIPY
  1.1047 +