sl@0: /* sl@0: * Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies). sl@0: * All rights reserved. sl@0: * This component and the accompanying materials are made available sl@0: * under the terms of the License "Eclipse Public License v1.0" sl@0: * which accompanies this distribution, and is available sl@0: * at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: * sl@0: * Initial Contributors: sl@0: * Nokia Corporation - initial contribution. sl@0: * sl@0: * Contributors: sl@0: * sl@0: * Description: Sprite implementation sl@0: * sl@0: */ sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \file sl@0: * \brief Sprite implementation sl@0: */ sl@0: sl@0: #ifndef M3G_CORE_INCLUDE sl@0: # error included by m3g_core.c; do not compile separately. sl@0: #endif sl@0: sl@0: /*#include */ sl@0: sl@0: #include "m3g_sprite.h" sl@0: #include "m3g_appearance.h" sl@0: #include "m3g_camera.h" sl@0: #include "m3g_rendercontext.h" sl@0: #include "m3g_renderqueue.h" sl@0: sl@0: #define FLIPX 1 sl@0: #define FLIPY 2 sl@0: sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Internal functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Destroys this Sprite object. sl@0: * sl@0: * \param obj Sprite object sl@0: */ sl@0: static void m3gDestroySprite(Object *obj) sl@0: { sl@0: Sprite *sprite = (Sprite *) obj; sl@0: M3G_VALIDATE_OBJECT(sprite); sl@0: sl@0: M3G_ASSIGN_REF(sprite->image, NULL); sl@0: M3G_ASSIGN_REF(sprite->appearance, NULL); sl@0: sl@0: m3gIncStat(M3G_INTERFACE(obj), M3G_STAT_RENDERABLES, -1); sl@0: sl@0: m3gDestroyNode(obj); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param property animation property sl@0: * \retval M3G_TRUE property supported sl@0: * \retval M3G_FALSE property not supported sl@0: */ sl@0: static M3Gbool m3gSpriteIsCompatible(M3Gint property) sl@0: { sl@0: switch (property) { sl@0: case M3G_ANIM_CROP: sl@0: return M3G_TRUE; sl@0: default: sl@0: return m3gNodeIsCompatible(property); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method sl@0: */ sl@0: static M3Gint m3gSpriteGetBBox(Node *self, AABB *bbox) sl@0: { sl@0: Sprite *sprite = (Sprite*) self; sl@0: sl@0: /* Only scaled sprites can have a bounding box; non-scaled ones sl@0: * are marked as non-cullable in the "SetParent" function in sl@0: * m3g_node.c */ sl@0: sl@0: if (sprite->scaled) { sl@0: const AABB spriteBBox = { { -.5f, -.5f, 0.f }, sl@0: { .5f, .5f, 0.f } }; sl@0: *bbox = spriteBBox; sl@0: return (4 * VFC_VERTEX_COST + sl@0: 2 * VFC_TRIANGLE_COST + sl@0: VFC_NODE_OVERHEAD); sl@0: } sl@0: else { sl@0: return 0; /* no bounding box for non-scaled sprites */ sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param self Sprite object sl@0: * \param property animation property sl@0: * \param valueSize size of value array sl@0: * \param value value array sl@0: */ sl@0: static void m3gSpriteUpdateProperty(Object *self, sl@0: M3Gint property, sl@0: M3Gint valueSize, sl@0: const M3Gfloat *value) sl@0: { sl@0: Sprite *sprite = (Sprite *) self; sl@0: M3G_VALIDATE_OBJECT(sprite); sl@0: M3G_ASSERT_PTR(value); sl@0: sl@0: switch (property) { sl@0: case M3G_ANIM_CROP: sl@0: /* Assert that the value vector is large enough */ sl@0: if (valueSize > 2) { sl@0: M3G_ASSERT(valueSize >= 4); sl@0: m3gSetCrop(sprite, m3gRoundToInt(value[0]), sl@0: m3gRoundToInt(value[1]), sl@0: m3gClampInt(m3gRoundToInt(value[2]), sl@0: -M3G_MAX_TEXTURE_DIMENSION, sl@0: M3G_MAX_TEXTURE_DIMENSION), sl@0: m3gClampInt(m3gRoundToInt(value[3]), sl@0: -M3G_MAX_TEXTURE_DIMENSION, sl@0: M3G_MAX_TEXTURE_DIMENSION) ); sl@0: } sl@0: else { sl@0: M3G_ASSERT(valueSize >= 2); sl@0: m3gSetCrop(sprite, m3gRoundToInt(value[0]), sl@0: m3gRoundToInt(value[1]), sl@0: sprite->crop.width, sl@0: sprite->crop.height ); sl@0: } sl@0: break; sl@0: default: sl@0: m3gNodeUpdateProperty(self, property, valueSize, value); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method. sl@0: * sl@0: * \param self Sprite object sl@0: * \param toCamera transform to camera sl@0: * \param alphaFactor total alpha factor sl@0: * \param caller caller node sl@0: * \param renderQueue RenderQueue sl@0: * sl@0: * \retval M3G_TRUE continue render setup sl@0: * \retval M3G_FALSE abort render setup sl@0: */ sl@0: static M3Gbool m3gSpriteSetupRender(Node *self, sl@0: const Node *caller, sl@0: SetupRenderState *s, sl@0: RenderQueue *renderQueue) sl@0: { sl@0: Sprite *sprite = (Sprite *)self; sl@0: Interface *m3g = M3G_INTERFACE(sprite); sl@0: M3G_UNREF(caller); sl@0: m3gIncStat(M3G_INTERFACE(self), M3G_STAT_RENDER_NODES, 1); sl@0: sl@0: if ((self->enableBits & NODE_RENDER_BIT) != 0 && sl@0: (self->scope & renderQueue->scope) != 0) { sl@0: sl@0: if (sprite->appearance != NULL && sprite->image != NULL && sl@0: sprite->crop.width != 0 && sprite->crop.height != 0) { sl@0: sl@0: /* Fetch the cumulative alpha factor for this node */ sl@0: sprite->totalAlphaFactor = sl@0: (M3Gushort) m3gGetTotalAlphaFactor((Node*) sprite, renderQueue->root); sl@0: sl@0: /* Touch the POT image to make sure it's allocated prior sl@0: * to rendering */ sl@0: sl@0: if (!m3gGetPowerOfTwoImage(sprite->image) || sl@0: !m3gInsertDrawable(m3g, sl@0: renderQueue, sl@0: self, sl@0: &s->toCamera, sl@0: 0, sl@0: m3gGetAppearanceSortKey(sprite->appearance))) sl@0: return M3G_FALSE; sl@0: } sl@0: } sl@0: sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Calculates sprite vertex positions and texture coordinates. sl@0: * sl@0: * \param sprite Sprite object sl@0: * \param ctx RenderContext object (Graphics3D) sl@0: * \param cam Camera object sl@0: * \param vert vertex position to fill in sl@0: * \param texvert texture coordinates to fill in sl@0: * \param eyeSpace coordinates after modelview sl@0: * \param adjust adjust for texture coorinates, render and sl@0: * pick need different adjustment sl@0: * \retval M3G_TRUE crop and image intersect sl@0: * \retval M3G_FALSE crop and image do not intersect sl@0: */ sl@0: static M3Gbool m3gGetSpriteCoordinates(Sprite *sprite, sl@0: RenderContext *ctx, sl@0: const Camera *cam, sl@0: const Matrix *toCamera, sl@0: M3Gint *vert, sl@0: M3Gshort *texvert, sl@0: Vec4 *eyeSpace, sl@0: M3Gshort adjust) sl@0: { sl@0: Vec4 o = {0, 0, 0, 1}; /* Origin */ sl@0: Vec4 x = {0.5f, 0, 0, 1}; /* Half of x unit */ sl@0: Vec4 y = {0, 0.5f, 0, 1}; /* Half of y unit */ sl@0: Vec4 ot; sl@0: Rect rIsect, rImage; sl@0: sl@0: rImage.x = 0; sl@0: rImage.y = 0; sl@0: rImage.width = sprite->width; sl@0: rImage.height = sprite->height; sl@0: sl@0: /* Intersection of image and crop*/ sl@0: if (!m3gIntersectRectangle(&rIsect, &rImage, &sprite->crop)) { sl@0: /* No intersection -> nothing to render / pick */ sl@0: return M3G_FALSE; sl@0: } sl@0: sl@0: /* Calculate origin and vectors after modelview */ sl@0: m3gTransformVec4(toCamera, &o); sl@0: m3gTransformVec4(toCamera, &x); sl@0: m3gTransformVec4(toCamera, &y); sl@0: sl@0: ot = o; sl@0: sl@0: m3gScaleVec4(&o, m3gRcp(o.w)); sl@0: m3gScaleVec4(&x, m3gRcp(x.w)); sl@0: m3gScaleVec4(&y, m3gRcp(y.w)); sl@0: sl@0: /* Store eyespace coordinates */ sl@0: if (eyeSpace != NULL) { sl@0: eyeSpace->x = o.x; sl@0: eyeSpace->y = o.y; sl@0: eyeSpace->z = o.z; sl@0: } sl@0: sl@0: m3gSubVec4(&x, &o); sl@0: m3gSubVec4(&y, &o); sl@0: sl@0: x.x = m3gAdd(ot.x, m3gLengthVec3((const Vec3*) &x)); sl@0: x.y = ot.y; sl@0: x.z = ot.z; sl@0: x.w = ot.w; sl@0: sl@0: y.y = m3gAdd(ot.y, m3gLengthVec3((const Vec3*) &y)); sl@0: y.x = ot.x; sl@0: y.z = ot.z; sl@0: y.w = ot.w; sl@0: sl@0: /* Calculate origin and vectors after projection */ sl@0: { sl@0: const Matrix *projMatrix = m3gProjectionMatrix(cam); sl@0: m3gTransformVec4(projMatrix, &ot); sl@0: m3gTransformVec4(projMatrix, &x); sl@0: m3gTransformVec4(projMatrix, &y); sl@0: } sl@0: #ifndef M3G_USE_NGL_API sl@0: /* Store w after projection */ sl@0: if (eyeSpace != NULL) { sl@0: eyeSpace->w = ot.w; sl@0: } sl@0: #endif sl@0: m3gScaleVec4(&ot, m3gRcp(ot.w)); sl@0: m3gScaleVec4(&x, m3gRcp(x.w)); sl@0: m3gScaleVec4(&y, m3gRcp(y.w)); sl@0: sl@0: m3gSubVec4(&x, &ot); sl@0: m3gSubVec4(&y, &ot); sl@0: sl@0: x.x = m3gLengthVec3((const Vec3*) &x); sl@0: y.y = m3gLengthVec3((const Vec3*) &y); sl@0: sl@0: /* Non-scaled sprites take width from crop rectangle*/ sl@0: if (!sprite->scaled) { sl@0: M3Gint viewport[4]; sl@0: if (ctx != NULL) { sl@0: m3gGetViewport(ctx, viewport, viewport + 1, viewport + 2, viewport + 3); sl@0: } sl@0: else { sl@0: /* Use a dummy viewport, this is only when picking and sl@0: not rendering to anything. Values must represent a valid viewport */ sl@0: viewport[0] = 0; sl@0: viewport[1] = 0; sl@0: viewport[2] = 256; sl@0: viewport[3] = 256; sl@0: } sl@0: sl@0: x.x = m3gDivif (rIsect.width, viewport[2]); sl@0: y.y = m3gDivif (rIsect.height, viewport[3]); sl@0: sl@0: ot.x = m3gSub(ot.x, sl@0: m3gDivif (2 * sprite->crop.x + sprite->crop.width - 2 * rIsect.x - rIsect.width, sl@0: viewport[2])); sl@0: sl@0: ot.y = m3gAdd(ot.y, sl@0: m3gDivif (2 * sprite->crop.y + sprite->crop.height - 2 * rIsect.y - rIsect.height, sl@0: viewport[3])); sl@0: } sl@0: else { sl@0: /* Adjust width and height according to cropping rectangle */ sl@0: x.x = m3gDiv(x.x, (M3Gfloat) sprite->crop.width); sl@0: y.y = m3gDiv(y.y, (M3Gfloat) sprite->crop.height); sl@0: sl@0: ot.x = m3gSub(ot.x, sl@0: m3gMul((M3Gfloat)(2 * sprite->crop.x + sprite->crop.width - 2 * rIsect.x - rIsect.width), sl@0: x.x)); sl@0: sl@0: ot.y = m3gAdd(ot.y, sl@0: m3gMul((M3Gfloat)(2 * sprite->crop.y + sprite->crop.height - 2 * rIsect.y - rIsect.height), sl@0: y.y)); sl@0: sl@0: x.x = m3gMul(x.x, (M3Gfloat) rIsect.width); sl@0: y.y = m3gMul(y.y, (M3Gfloat) rIsect.height); sl@0: } sl@0: #ifdef M3G_USE_NGL_API sl@0: /* Store final Z */ sl@0: if (eyeSpace != NULL) { sl@0: eyeSpace->w = ot.z; sl@0: } sl@0: #endif sl@0: /* Set up positions */ sl@0: vert[0 * 3 + 0] = (M3Gint) m3gMul(65536, m3gSub(ot.x, x.x)); sl@0: vert[0 * 3 + 1] = m3gRoundToInt(m3gAdd(m3gMul(65536, m3gAdd(ot.y, y.y)), 0.5f)); sl@0: vert[0 * 3 + 2] = m3gRoundToInt(m3gMul(65536, ot.z)); sl@0: sl@0: vert[1 * 3 + 0] = vert[0 * 3 + 0]; sl@0: vert[1 * 3 + 1] = (M3Gint) m3gMul(65536, m3gSub(ot.y, y.y)); sl@0: vert[1 * 3 + 2] = vert[0 * 3 + 2]; sl@0: sl@0: vert[2 * 3 + 0] = m3gRoundToInt(m3gAdd(m3gMul(65536, m3gAdd(ot.x, x.x)), 0.5f)); sl@0: vert[2 * 3 + 1] = vert[0 * 3 + 1]; sl@0: vert[2 * 3 + 2] = vert[0 * 3 + 2]; sl@0: sl@0: vert[3 * 3 + 0] = vert[2 * 3 + 0]; sl@0: vert[3 * 3 + 1] = vert[1 * 3 + 1]; sl@0: vert[3 * 3 + 2] = vert[0 * 3 + 2]; sl@0: sl@0: /* Set up texture coordinates */ sl@0: if (!(sprite->flip & FLIPX)) { sl@0: texvert[0 * 2 + 0] = (M3Gshort) rIsect.x; sl@0: texvert[1 * 2 + 0] = (M3Gshort) rIsect.x; sl@0: texvert[2 * 2 + 0] = (M3Gshort) (rIsect.x + rIsect.width - adjust); sl@0: texvert[3 * 2 + 0] = (M3Gshort) (rIsect.x + rIsect.width - adjust); sl@0: } sl@0: else { sl@0: texvert[0 * 2 + 0] = (M3Gshort) (rIsect.x + rIsect.width - adjust); sl@0: texvert[1 * 2 + 0] = (M3Gshort) (rIsect.x + rIsect.width - adjust); sl@0: texvert[2 * 2 + 0] = (M3Gshort) rIsect.x; sl@0: texvert[3 * 2 + 0] = (M3Gshort) rIsect.x; sl@0: } sl@0: sl@0: if (!(sprite->flip & FLIPY)) { sl@0: texvert[0 * 2 + 1] = (M3Gshort) rIsect.y; sl@0: texvert[1 * 2 + 1] = (M3Gshort) (rIsect.y + rIsect.height - adjust); sl@0: texvert[2 * 2 + 1] = (M3Gshort) rIsect.y; sl@0: texvert[3 * 2 + 1] = (M3Gshort) (rIsect.y + rIsect.height - adjust); sl@0: } sl@0: else { sl@0: texvert[0 * 2 + 1] = (M3Gshort) (rIsect.y + rIsect.height - adjust); sl@0: texvert[1 * 2 + 1] = (M3Gshort) rIsect.y; sl@0: texvert[2 * 2 + 1] = (M3Gshort) (rIsect.y + rIsect.height - adjust); sl@0: texvert[3 * 2 + 1] = (M3Gshort) rIsect.y; sl@0: } sl@0: sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method. sl@0: * sl@0: * Renders the sprite as a textured quad. sl@0: * sl@0: * \param self Mesh object sl@0: * \param ctx current render context sl@0: * \param patchIndex submesh index sl@0: */ sl@0: static void m3gSpriteDoRender(Node *self, sl@0: RenderContext *ctx, sl@0: const Matrix *toCamera, sl@0: M3Gint patchIndex) sl@0: { sl@0: Sprite *sprite = (Sprite *)self; sl@0: M3Gshort texvert[4 * 2]; sl@0: M3Gint vert[4 * 3]; sl@0: Vec4 eyeSpace; sl@0: Image *imagePow2; sl@0: M3G_UNREF(patchIndex); sl@0: sl@0: M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS); sl@0: if (!m3gGetSpriteCoordinates(sprite, sl@0: ctx, sl@0: m3gGetCurrentCamera(ctx), sl@0: toCamera, sl@0: vert, sl@0: texvert, sl@0: &eyeSpace, sl@0: 0)) { sl@0: return; sl@0: } sl@0: M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS); sl@0: sl@0: /* Get power of two image */ sl@0: imagePow2 = m3gGetPowerOfTwoImage(sprite->image); sl@0: /* If NULL -> out of memory */ sl@0: if (imagePow2 == NULL) { sl@0: return; sl@0: } sl@0: sl@0: if (m3gGetColorMaskWorkaround(M3G_INTERFACE(ctx))) { sl@0: m3gUpdateColorMaskStatus(ctx, sl@0: m3gColorMask(sprite->appearance), sl@0: m3gAlphaMask(sprite->appearance)); sl@0: } sl@0: sl@0: /* Disable unwanted state. Note that we do this BEFORE setting the sl@0: * sprite color to avoid any problems with glColorMaterial */ sl@0: m3gApplyDefaultMaterial(); sl@0: m3gApplyDefaultPolygonMode(); sl@0: sl@0: /* Disable color array, normals and textures*/ sl@0: glDisableClientState(GL_COLOR_ARRAY); sl@0: glDisableClientState(GL_NORMAL_ARRAY); sl@0: m3gDisableTextures(); sl@0: sl@0: /* Sprite image to texture unit 0 */ sl@0: glClientActiveTexture(GL_TEXTURE0); sl@0: glActiveTexture(GL_TEXTURE0); sl@0: glEnableClientState(GL_TEXTURE_COORD_ARRAY); sl@0: glTexCoordPointer(2, GL_SHORT, 0, texvert); sl@0: glEnable(GL_TEXTURE_2D); sl@0: glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, (GLfixed) GL_MODULATE); sl@0: m3gBindTextureImage(imagePow2, sl@0: M3G_FILTER_BASE_LEVEL, sl@0: m3gIsAccelerated(ctx) ? M3G_FILTER_LINEAR : M3G_FILTER_NEAREST); sl@0: sl@0: glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); sl@0: glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); sl@0: sl@0: glMatrixMode(GL_TEXTURE); sl@0: glLoadIdentity(); sl@0: glScalef(m3gRcp((M3Gfloat) m3gGetWidth(sprite->image)), sl@0: m3gRcp((M3Gfloat) m3gGetHeight(sprite->image)), sl@0: 1.f); sl@0: glMatrixMode(GL_MODELVIEW); sl@0: sl@0: /* Apply fog and compositing mode */ sl@0: #ifdef M3G_USE_NGL_API sl@0: m3gApplySpriteFog(sprite->appearance->fog, eyeSpace.z, eyeSpace.w); sl@0: #else sl@0: m3gApplyFog(sprite->appearance->fog); sl@0: #endif sl@0: m3gApplyCompositingMode(sprite->appearance->compositingMode, ctx); sl@0: sl@0: { sl@0: GLfixed a = (GLfixed) (0xff * sprite->totalAlphaFactor); sl@0: a = (a >> (NODE_ALPHA_FACTOR_BITS - 8)) sl@0: + (a >> NODE_ALPHA_FACTOR_BITS) sl@0: + (a >> (NODE_ALPHA_FACTOR_BITS + 7)); sl@0: glColor4x((GLfixed) 1 << 16, (GLfixed) 1 << 16, (GLfixed) 1 << 16, a); sl@0: } sl@0: sl@0: /* Load vertices */ sl@0: glEnableClientState(GL_VERTEX_ARRAY); sl@0: glVertexPointer(3, GL_FIXED, 0, vert); sl@0: sl@0: /* Store current matrices, then set up an identity modelview and sl@0: * projection */ sl@0: sl@0: m3gPushScreenSpace(ctx, M3G_FALSE); sl@0: sl@0: #ifndef M3G_USE_NGL_API sl@0: /* Transform the sprite vertices (in NDC) back to eye coordinates, so that sl@0: the fog distance will be calculated correctly in the OpenGL pipeline. */ sl@0: { sl@0: GLfloat transform[16]; sl@0: GLfloat scaleW[16] = { 0.f, 0.f, 0.f, 0.f, sl@0: 0.f, 0.f, 0.f, 0.f, sl@0: 0.f, 0.f, 0.f, 0.f, sl@0: 0.f, 0.f, 0.f, 0.f }; sl@0: Matrix invProjMatrix; sl@0: const Matrix *projMatrix = m3gProjectionMatrix(m3gGetCurrentCamera(ctx)); sl@0: sl@0: m3gMatrixInverse(&invProjMatrix, projMatrix); sl@0: m3gGetMatrixColumns(&invProjMatrix, transform); sl@0: sl@0: glMatrixMode(GL_MODELVIEW); sl@0: glMultMatrixf(transform); sl@0: scaleW[0] = scaleW[5] = scaleW[10] = scaleW[15] = eyeSpace.w; sl@0: glMultMatrixf(scaleW); sl@0: sl@0: glMatrixMode(GL_PROJECTION); sl@0: m3gGetMatrixColumns(projMatrix, transform); sl@0: glLoadMatrixf(transform); sl@0: } sl@0: #endif sl@0: sl@0: /* Load indices -> draws the sprite */ sl@0: M3G_BEGIN_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_NGL_DRAW); sl@0: glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); sl@0: M3G_END_PROFILE(M3G_INTERFACE(ctx), M3G_PROFILE_NGL_DRAW); sl@0: sl@0: m3gReleaseTextureImage(imagePow2); sl@0: sl@0: /* Restore the previous modelview and projection */ sl@0: sl@0: m3gPopSpace(ctx); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method. sl@0: * sl@0: * Picks a scaled sprite as 2D from viewport. sl@0: * sl@0: * \param self Mesh object sl@0: * \param mask pick scope mask sl@0: * \param ray pick ray sl@0: * \param ri RayIntersection object sl@0: * \param toGroup transform to originating group sl@0: * \retval M3G_TRUE continue pick sl@0: * \retval M3G_FALSE abort pick sl@0: */ sl@0: static M3Gbool m3gSpriteRayIntersect(Node *self, sl@0: M3Gint mask, sl@0: M3Gfloat *ray, sl@0: RayIntersection *ri, sl@0: Matrix *toGroup) sl@0: { sl@0: Sprite *sprite = (Sprite *)self; sl@0: M3Gshort texvert[4 * 2]; sl@0: M3Gint vert[4 * 3]; sl@0: M3Gint x, y; sl@0: Vec4 eyeSpace; sl@0: M3Gfloat distance; sl@0: M3G_UNREF(toGroup); sl@0: sl@0: /* Check that picking is possible */ sl@0: sl@0: if (sprite->image == NULL || sl@0: sprite->appearance == NULL || sl@0: ri->camera == NULL || sl@0: !sprite->scaled || sl@0: sprite->crop.width == 0 || sl@0: sprite->crop.height == 0 || sl@0: (self->scope & mask) == 0) { sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /* Calculate modelview transform, picking is possible without rendering */ sl@0: sl@0: { sl@0: Matrix toCamera; sl@0: sl@0: if (!m3gGetTransformTo(self, (Node *)ri->camera, sl@0: &toCamera)) { sl@0: return M3G_FALSE; sl@0: } sl@0: if (!m3gGetSpriteCoordinates(sprite, NULL, sl@0: (const Camera *)ri->camera, &toCamera, sl@0: vert, texvert, &eyeSpace, 1)) { sl@0: return M3G_TRUE; sl@0: } sl@0: } sl@0: sl@0: /* Do the pick in 2D, formula is from the spec and values are sl@0: set to 16.16 fixed point format */ sl@0: sl@0: x = m3gRoundToInt(m3gMul(2 * 65536, ri->x)) - 65536; sl@0: y = 65536 - m3gRoundToInt(m3gMul(2 * 65536, ri->y)); sl@0: sl@0: if (x >= vert[0 * 3 + 0] && x <= vert[2 * 3 + 0] && sl@0: y <= vert[0 * 3 + 1] && y >= vert[1 * 3 + 1] ) { sl@0: sl@0: distance = m3gDiv(m3gSub(eyeSpace.z, ray[6]), m3gSub(ray[7], ray[6])); sl@0: sl@0: if (distance <= 0 || sl@0: distance >= ri->tMin) return M3G_TRUE; sl@0: sl@0: ri->tMin = distance; sl@0: ri->distance = ri->tMin; sl@0: ri->submeshIndex = 0; sl@0: sl@0: x -= vert[0 * 3 + 0]; sl@0: y = vert[0 * 3 + 1] - y; sl@0: sl@0: if (!(sprite->flip & FLIPX)) { sl@0: ri->textureS[0] = m3gAdd(texvert[0 * 2 + 0], sl@0: m3gDivif ((texvert[2 * 2 + 0] - texvert[0 * 2 + 0] + 1) * x, sl@0: vert[2 * 3 + 0] - vert[0 * 3 + 0])); sl@0: } sl@0: else { sl@0: ri->textureS[0] = m3gSub((M3Gfloat)(texvert[0 * 2 + 0] + 1), sl@0: m3gDivif ((texvert[0 * 2 + 0] - texvert[2 * 2 + 0] + 1) * x, sl@0: vert[2 * 3 + 0] - vert[0 * 3 + 0])); sl@0: } sl@0: sl@0: if (!(sprite->flip & FLIPY)) { sl@0: ri->textureT[0] = m3gAdd(texvert[0 * 2 + 1], sl@0: m3gDivif ((texvert[1 * 2 + 1] - texvert[0 * 2 + 1] + 1) * y, sl@0: vert[0 * 3 + 1] - vert[1 * 3 + 1])); sl@0: } sl@0: else { sl@0: ri->textureT[0] = m3gSub((M3Gfloat)(texvert[0 * 2 + 1] + 1), sl@0: m3gDivif ((texvert[0 * 2 + 1] - texvert[1 * 2 + 1] + 1) * y, sl@0: vert[0 * 3 + 1] - vert[1 * 3 + 1])); sl@0: } sl@0: sl@0: { sl@0: /* Finally check against alpha */ sl@0: M3Gint threshold = 0, alpha; sl@0: sl@0: if (sprite->appearance->compositingMode) { sl@0: threshold = (M3Gint)m3gMul(m3gGetAlphaThreshold(sprite->appearance->compositingMode), 256); sl@0: } sl@0: sl@0: alpha = m3gGetAlpha(sprite->image, (M3Gint)ri->textureS[0], (M3Gint)ri->textureT[0]); sl@0: sl@0: if (alpha >= threshold) { sl@0: /* Normalize texture coordinates */ sl@0: ri->textureS[0] = m3gDiv(ri->textureS[0], (M3Gfloat) sprite->width); sl@0: ri->textureT[0] = m3gDiv(ri->textureT[0], (M3Gfloat) sprite->height); sl@0: sl@0: ri->textureS[1] = 0.f; sl@0: ri->textureT[1] = 0.f; sl@0: sl@0: ri->normal[0] = 0.f; sl@0: ri->normal[1] = 0.f; sl@0: ri->normal[2] = 1.f; sl@0: sl@0: ri->intersected = self; sl@0: } sl@0: } sl@0: } sl@0: sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param self Sprite object sl@0: * \param references array of reference objects sl@0: * \return number of references sl@0: */ sl@0: static M3Gint m3gSpriteDoGetReferences(Object *self, Object **references) sl@0: { sl@0: Sprite *sprite = (Sprite *)self; sl@0: int num = m3gObjectDoGetReferences(self, references); sl@0: if (sprite->image != NULL) { sl@0: if (references != NULL) sl@0: references[num] = (Object *)sprite->image; sl@0: num++; sl@0: } sl@0: if (sprite->appearance != NULL) { sl@0: if (references != NULL) sl@0: references[num] = (Object *)sprite->appearance; sl@0: num++; sl@0: } sl@0: return num; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: */ sl@0: static Object *m3gSpriteFindID(Object *self, M3Gint userID) sl@0: { sl@0: Sprite *sprite = (Sprite *)self; sl@0: Object *found = m3gObjectFindID(self, userID); sl@0: sl@0: if (!found && sprite->image != NULL) { sl@0: found = m3gFindID((Object*) sprite->image, userID); sl@0: } sl@0: if (!found && sprite->appearance != NULL) { sl@0: found = m3gFindID((Object*) sprite->appearance, userID); sl@0: } sl@0: return found; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param originalObj original Sprite object sl@0: * \param cloneObj pointer to cloned Sprite object sl@0: * \param pairs array for all object-duplicate pairs sl@0: * \param numPairs number of pairs sl@0: */ sl@0: static M3Gbool m3gSpriteDuplicate(const Object *originalObj, sl@0: Object **cloneObj, sl@0: Object **pairs, sl@0: M3Gint *numPairs) sl@0: { sl@0: Sprite *original = (Sprite *)originalObj; sl@0: Sprite *clone; sl@0: M3G_ASSERT(*cloneObj == NULL); /* no derived classes */ sl@0: sl@0: /* Create the clone object */ sl@0: sl@0: clone = (Sprite *)m3gCreateSprite(originalObj->interface, sl@0: original->scaled, sl@0: original->image, sl@0: original->appearance); sl@0: if (!clone) { sl@0: return M3G_FALSE; sl@0: } sl@0: *cloneObj = (Object *)clone; sl@0: sl@0: /* Duplicate our own fields */ sl@0: sl@0: clone->crop = original->crop; sl@0: clone->flip = original->flip; sl@0: sl@0: /* Duplicate base class data */ sl@0: sl@0: return m3gNodeDuplicate(originalObj, cloneObj, pairs, numPairs); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param self Sprite object sl@0: * \param time current world time sl@0: * \return minimum validity sl@0: */ sl@0: static M3Gint m3gSpriteApplyAnimation(Object *self, M3Gint time) sl@0: { sl@0: M3Gint validity, minValidity; sl@0: Sprite *sprite = (Sprite *)self; sl@0: Object *app; sl@0: M3G_VALIDATE_OBJECT(sprite); sl@0: sl@0: minValidity = m3gObjectApplyAnimation(self, time); sl@0: sl@0: if (minValidity > 0) { sl@0: app = (Object *) sprite->appearance; sl@0: sl@0: if (app != NULL) { sl@0: validity = M3G_VFUNC(Object, app, applyAnimation)(app, time); sl@0: minValidity = M3G_MIN(validity, minValidity); sl@0: } sl@0: } sl@0: return minValidity; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Initializes a Sprite object. See specification sl@0: * for default values. sl@0: * sl@0: * \param m3g M3G interface sl@0: * \param sprite Sprite object sl@0: * \param scaled scaled flag sl@0: * \param appearance Appearance object sl@0: * \param image Image2D object sl@0: * \retval M3G_TRUE Sprite initialized sl@0: * \retval M3G_FALSE initialization failed sl@0: */ sl@0: static M3Gbool m3gInitSprite(Interface *m3g, sl@0: Sprite *sprite, sl@0: M3Gbool scaled, sl@0: Appearance *appearance, sl@0: Image *image) sl@0: { sl@0: /* Sprite is derived from node */ sl@0: m3gInitNode(m3g, &sprite->node, M3G_CLASS_SPRITE); sl@0: sprite->node.hasRenderables = M3G_TRUE; sl@0: sl@0: m3gIncStat(m3g, M3G_STAT_RENDERABLES, 1); sl@0: sl@0: sprite->scaled = scaled; sl@0: M3G_ASSIGN_REF(sprite->appearance, appearance); sl@0: return m3gSetSpriteImage(sprite, image); sl@0: } sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Virtual function table sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: static const NodeVFTable m3gvf_Sprite = { sl@0: { sl@0: { sl@0: m3gSpriteApplyAnimation, sl@0: m3gSpriteIsCompatible, sl@0: m3gSpriteUpdateProperty, sl@0: m3gSpriteDoGetReferences, sl@0: m3gSpriteFindID, sl@0: m3gSpriteDuplicate, sl@0: m3gDestroySprite sl@0: } sl@0: }, sl@0: m3gNodeAlign, sl@0: m3gSpriteDoRender, sl@0: m3gSpriteGetBBox, sl@0: m3gSpriteRayIntersect, sl@0: m3gSpriteSetupRender, sl@0: m3gNodeUpdateDuplicateReferences, sl@0: m3gNodeValidate sl@0: }; sl@0: sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Public API functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \brief Creates a Sprite object. sl@0: * sl@0: * \param hInterface M3G interface sl@0: * \param scaled scaled flag sl@0: * \param hImage Image2D object sl@0: * \param hAppearance Appearance object sl@0: * \retval Sprite new Sprite object sl@0: * \retval NULL Sprite creating failed sl@0: */ sl@0: M3G_API M3GSprite m3gCreateSprite(M3GInterface hInterface, sl@0: M3Gbool scaled, sl@0: M3GImage hImage, sl@0: M3GAppearance hAppearance) sl@0: { sl@0: Interface *m3g = (Interface *) hInterface; sl@0: M3G_VALIDATE_INTERFACE(m3g); sl@0: sl@0: if (hImage == 0) { sl@0: m3gRaiseError(m3g, M3G_NULL_POINTER); sl@0: return NULL; sl@0: } sl@0: sl@0: { sl@0: Sprite *sprite = m3gAllocZ(m3g, sizeof(Sprite)); sl@0: sl@0: if (sprite != NULL) { sl@0: if (!m3gInitSprite(m3g, sl@0: sprite, sl@0: scaled, sl@0: (Appearance *)hAppearance, sl@0: (Image *)hImage)) { sl@0: M3G_ASSIGN_REF(sprite->image, NULL); sl@0: M3G_ASSIGN_REF(sprite->appearance, NULL); sl@0: m3gFree(m3g, sprite); sl@0: return NULL; sl@0: } sl@0: } sl@0: sl@0: return (M3GSprite) sprite; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Get sprite scaled flag. sl@0: * sl@0: * \param handle Sprite object sl@0: * \retval M3G_TRUE sprite is scaled sl@0: * \retval M3G_FALSE sprite is not scaled sl@0: */ sl@0: M3G_API M3Gbool m3gIsScaledSprite(M3GSprite handle) sl@0: { sl@0: Sprite *sprite = (Sprite *) handle; sl@0: M3G_VALIDATE_OBJECT(sprite); sl@0: sl@0: return sprite->scaled; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Set sprite appearance. sl@0: * sl@0: * \param handle Sprite object sl@0: * \param hAppearance Appearance object sl@0: */ sl@0: M3G_API void m3gSetSpriteAppearance(M3GSprite handle, sl@0: M3GAppearance hAppearance) sl@0: { sl@0: Sprite *sprite = (Sprite *) handle; sl@0: M3G_VALIDATE_OBJECT(sprite); sl@0: sl@0: M3G_ASSIGN_REF(sprite->appearance, hAppearance); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Set sprite image sl@0: * sl@0: * \param handle Sprite object sl@0: * \param hImage Image2D object sl@0: * \retval M3G_TRUE image was set sl@0: * \retval M3G_FALSE failed to set image sl@0: */ sl@0: M3G_API M3Gbool m3gSetSpriteImage(M3GSprite handle, M3GImage hImage) sl@0: { sl@0: Sprite *sprite = (Sprite *) handle; sl@0: Image *image = (Image *)hImage; sl@0: M3G_VALIDATE_OBJECT(sprite); sl@0: sl@0: if (image == NULL) { sl@0: m3gRaiseError(M3G_INTERFACE(sprite), M3G_NULL_POINTER); sl@0: return M3G_FALSE; sl@0: } sl@0: sl@0: M3G_ASSIGN_REF(sprite->image, image); sl@0: sl@0: sprite->width = m3gGetWidth(image); sl@0: sprite->height = m3gGetHeight(image); sl@0: sl@0: sprite->crop.x = 0; sl@0: sprite->crop.y = 0; sl@0: sprite->crop.width = m3gClampInt(sprite->width, 0, M3G_MAX_TEXTURE_DIMENSION); sl@0: sprite->crop.height = m3gClampInt(sprite->height, 0, M3G_MAX_TEXTURE_DIMENSION); sl@0: sl@0: sprite->flip = 0; sl@0: sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Set sprite image crop rectangle. sl@0: * sl@0: * \param handle Sprite object sl@0: * \param cropX crop upper left x sl@0: * \param cropY crop upper left y sl@0: * \param width crop width sl@0: * \param height crop height sl@0: */ sl@0: M3G_API void m3gSetCrop(M3GSprite handle, sl@0: M3Gint cropX, M3Gint cropY, sl@0: M3Gint width, M3Gint height) sl@0: { sl@0: Sprite *sprite = (Sprite *) handle; sl@0: M3G_VALIDATE_OBJECT(sprite); sl@0: sl@0: /* Check for illegal crop size */ sl@0: if (!m3gInRange(width, -M3G_MAX_TEXTURE_DIMENSION, M3G_MAX_TEXTURE_DIMENSION) || sl@0: !m3gInRange(height, -M3G_MAX_TEXTURE_DIMENSION, M3G_MAX_TEXTURE_DIMENSION) ) { sl@0: m3gRaiseError(M3G_INTERFACE(sprite), M3G_INVALID_VALUE); sl@0: return; sl@0: } sl@0: sl@0: sprite->crop.x = cropX; sl@0: sprite->crop.y = cropY; sl@0: sl@0: if (width < 0) { sl@0: sprite->crop.width = -width; sl@0: sprite->flip |= FLIPX; sl@0: } sl@0: else { sl@0: sprite->crop.width = width; sl@0: sprite->flip &= ~FLIPX; sl@0: } sl@0: sl@0: if (height < 0) { sl@0: sprite->crop.height = -height; sl@0: sprite->flip |= FLIPY; sl@0: } sl@0: else { sl@0: sprite->crop.height = height; sl@0: sprite->flip &= ~FLIPY; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Get sprite image crop parameter. sl@0: * sl@0: * \param handle Sprite object sl@0: * \param which which crop parameter to return sl@0: * \arg M3G_GET_CROPX sl@0: * \arg M3G_GET_CROPY sl@0: * \arg M3G_GET_CROPWIDTH sl@0: * \arg M3G_GET_CROPHEIGHT sl@0: * \return image crop parameter sl@0: */ sl@0: M3Gint m3gGetCrop(M3GSprite handle, M3Gint which) sl@0: { sl@0: Sprite *sprite = (Sprite *) handle; sl@0: M3G_VALIDATE_OBJECT(sprite); sl@0: sl@0: switch(which) { sl@0: case M3G_GET_CROPX: sl@0: return sprite->crop.x; sl@0: case M3G_GET_CROPY: sl@0: return sprite->crop.y; sl@0: case M3G_GET_CROPWIDTH: sl@0: return (sprite->flip & FLIPX) ? -sprite->crop.width : sprite->crop.width; sl@0: case M3G_GET_CROPHEIGHT: sl@0: default: sl@0: return (sprite->flip & FLIPY) ? -sprite->crop.height : sprite->crop.height; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Gets sprite appearance. sl@0: * sl@0: * \param handle Sprite object sl@0: * \return Appearance object sl@0: */ sl@0: M3G_API M3GAppearance m3gGetSpriteAppearance(M3GSprite handle) sl@0: { sl@0: Sprite *sprite = (Sprite *) handle; sl@0: M3G_VALIDATE_OBJECT(sprite); sl@0: sl@0: return sprite->appearance; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Gets sprite image. sl@0: * sl@0: * \param handle Sprite object sl@0: * \return Image2D object sl@0: */ sl@0: M3G_API M3GImage m3gGetSpriteImage(M3GSprite handle) sl@0: { sl@0: Sprite *sprite = (Sprite *) handle; sl@0: M3G_VALIDATE_OBJECT(sprite); sl@0: sl@0: return sprite->image; sl@0: } sl@0: sl@0: #undef FLIPX sl@0: #undef FLIPY sl@0: