diff -r 000000000000 -r bde4ae8d615e os/graphics/m3g/m3gcore11/src/m3g_skinnedmesh.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/os/graphics/m3g/m3gcore11/src/m3g_skinnedmesh.c Fri Jun 15 03:10:57 2012 +0200 @@ -0,0 +1,2074 @@ +/* +* Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of the License "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: SkinnedMesh implementation +* +*/ + + +/*! + * \internal + * \file + * \brief SkinnedMesh implementation + */ + +#ifndef M3G_CORE_INCLUDE +# error included by m3g_core.c; do not compile separately. +#endif + +#include "m3g_skinnedmesh.h" +#include "m3g_memory.h" +#include "m3g_animationtrack.h" + +/*---------------------------------------------------------------------- + * Internal structures + *--------------------------------------------------------------------*/ + +struct BoneRecord +{ + Node *node; + + /*! \internal \brief "At-rest" transformation from skinned mesh to bone */ + Matrix toBone; + + /*! \internal \brief Cached animated transformation for positions */ + M3Gshort baseMatrix[9]; + M3Gshort posVec[3]; + M3Gshort baseExp, posExp, maxExp; + + /*! \internal \brief Cached animated transformation for normals */ + M3Gshort normalMatrix[9]; +}; + +/*---------------------------------------------------------------------- + * Internal functions + *--------------------------------------------------------------------*/ + +/*! + * \internal + * \brief Destroys this SkinnedMesh object. + * + * \param obj SkinnedMesh object + */ +static void m3gDestroySkinnedMesh(Object *obj) +{ + SkinnedMesh *mesh = (SkinnedMesh *) obj; + M3G_VALIDATE_OBJECT(mesh); + { + int i; + Interface *m3g = M3G_INTERFACE(mesh); + + m3gDeleteVertexBuffer(mesh->morphedVB); + + for (i = 0; i < mesh->bonesPerVertex; ++i) { + m3gFree(m3g, mesh->boneIndices[i]); + m3gFree(m3g, mesh->boneWeights[i]); + m3gFree(m3g, mesh->normalizedWeights[i]); + } + m3gFree(m3g, mesh->weightShifts); + + for (i = 0; i < m3gArraySize(&mesh->bones); ++i) { + m3gFree(m3g, m3gGetArrayElement(&mesh->bones, i)); + } + m3gDestroyArray(&mesh->bones, m3g); + + if (mesh->skeleton != NULL) { + m3gSetParent((Node*) mesh->skeleton, NULL); + M3G_ASSIGN_REF(mesh->skeleton, NULL); + } + } + m3gDestroyMesh(obj); +} + + +/*! + * \internal + * \brief Get a bone index for a given node + * + * This finds an existing record if the bone has been added + * previously, or creates a new one if no record exists yet. + * + * \note Inline because only called from AddTransform. + */ +static M3G_INLINE M3Gint m3gBoneIndex(SkinnedMesh *mesh, Node *node) +{ + PointerArray *boneArray = &mesh->bones; + const int numBones = m3gArraySize(boneArray); + + /* First look for an existing record in the array */ + { + int i; + + for (i = 0; i < numBones; ++i) { + Bone *b = m3gGetArrayElement(boneArray, i); + if (b->node == node) { + return i; + } + } + } + + /* Not found; create a new one, append to the array, and set up + * the "at-rest" transformation for the bone. Note, however, that + * we can only store a maximum of 256 bones with byte indices! */ + { + Interface *m3g = M3G_INTERFACE(mesh); + + if (numBones >= 256) { + /* Out of available bone indices */ + m3gRaiseError(m3g, M3G_OUT_OF_MEMORY); + return -1; + } + else { + M3Gint idx; + Bone *bone = (Bone*) m3gAllocZ(m3g, sizeof(Bone)); + if (!bone || !m3gGetTransformTo((Node*) mesh, node, + &bone->toBone)) { + m3gFree(m3g, bone); + return -1; /* out of memory or singular transform */ + } + bone->node = node; + + idx = m3gArrayAppend(boneArray, bone, m3g); + if (idx < 0) { + m3gFree(m3g, bone); + return -1; /* out of memory */ + } + return idx; + } + } +} + +/*! + * \internal + * \brief Reallocate the per-vertex data if necessary. + * + * \note Inline because only called from AddTransform. + */ +static M3G_INLINE M3Gbool m3gEnsureVertexCount(SkinnedMesh *mesh, M3Gint count) +{ + /* Reallocate only if vertex count increased */ + + if (count > mesh->weightedVertexCount) { + + Interface *m3g = M3G_INTERFACE(mesh); + + int i; + + /* Reallocate the weight shift array */ + { + M3Gubyte *pNew = (M3Gubyte*) m3gAllocZ(m3g, count); + if (!pNew) { + return M3G_FALSE; + } + m3gCopy(pNew, mesh->weightShifts, mesh->weightedVertexCount); + m3gFree(m3g, mesh->weightShifts); + mesh->weightShifts = pNew; + } + + /* Reallocate each of the bone index and weight arrays */ + + for (i = 0; i < mesh->bonesPerVertex; ++i) { + + M3Gubyte *pNew; + + /* Weights */ + pNew = (M3Gubyte*) m3gAllocZ(m3g, count); + if (!pNew) { + return M3G_FALSE; /* out of memory */ + } + m3gCopy(pNew, mesh->boneWeights[i], mesh->weightedVertexCount); + m3gFree(m3g, mesh->boneWeights[i]); + mesh->boneWeights[i] = pNew; + + pNew = (M3Gubyte*) m3gAllocZ(m3g, count); + if (!pNew) { + return M3G_FALSE; /* out of memory */ + } + m3gCopy(pNew, mesh->normalizedWeights[i], + mesh->weightedVertexCount); + m3gFree(m3g, mesh->normalizedWeights[i]); + mesh->normalizedWeights[i] = pNew; + + /* Indices */ + pNew = (M3Gubyte*) m3gAllocZ(m3g, count); + if (!pNew) { + return M3G_FALSE; /* out of memory */ + } + m3gCopy(pNew, mesh->boneIndices[i], mesh->weightedVertexCount); + m3gFree(m3g, mesh->boneIndices[i]); + mesh->boneIndices[i] = pNew; + } + + mesh->weightedVertexCount = count; + } + return M3G_TRUE; +} + +/*! + * \internal + * \brief Reallocate the per-vertex data if necessary. + * + * \note Inline because only called from AddTransform. + */ +static M3G_INLINE M3Gbool m3gEnsureBonesPerVertex(SkinnedMesh *mesh, + M3Gint count) +{ + M3G_ASSERT(count <= M3G_MAX_VERTEX_TRANSFORMS); + + /* Allocate only if per-vertex bone count increased */ + + if (count > mesh->bonesPerVertex) { + + Interface *m3g = M3G_INTERFACE(mesh); + + const M3Gint vertexCount = mesh->weightedVertexCount; + M3Gubyte *pNew; + + int i; + + /* Allocate new arrays for bone indices and weights until + * we're satisfied */ + + for (i = mesh->bonesPerVertex; i < count; ++i) { + pNew = (M3Gubyte*) m3gAllocZ(m3g, vertexCount); + if (!pNew) { + goto AllocFailed; /* out of memory */ + } + mesh->boneIndices[i] = pNew; + + pNew = (M3Gubyte*) m3gAllocZ(m3g, vertexCount); + if (!pNew) { + goto AllocFailed; /* out of memory */ + } + mesh->boneWeights[i] = pNew; + + pNew = (M3Gubyte*) m3gAllocZ(m3g, vertexCount); + if (!pNew) { + goto AllocFailed; /* out of memory */ + } + mesh->normalizedWeights[i] = pNew; + } + + mesh->bonesPerVertex = count; + return M3G_TRUE; + + /* In case of failure, clean up to keep the bonesPerVertex + * counter in sync with the actual number of arrays + * allocated */ + + AllocFailed: + for (i = mesh->bonesPerVertex; i < count; ++i) { + m3gFree(m3g, mesh->boneIndices[i]); + m3gFree(m3g, mesh->boneWeights[i]); + m3gFree(m3g, mesh->normalizedWeights[i]); + + mesh->boneIndices[i] = NULL; + mesh->boneWeights[i] = NULL; + mesh->normalizedWeights[i] = NULL; + } + return M3G_FALSE; + } + return M3G_TRUE; +} + +/*! + * \internal + * \brief Add a new bone influence to a vertex + * + * If the target vertex is already affected by + * M3G_MAX_VERTEX_TRANSFORMS bones, the one with the lowest weight is + * discarded. + */ +static M3G_INLINE void m3gAddInfluence(SkinnedMesh *mesh, + M3Gint vertexIndex, + M3Gint boneIndex, + M3Gint weight) +{ + M3Gint bonesPerVertex = mesh->bonesPerVertex; + M3Guint minWeight = weight; + M3Gint minWeightIndex = -1; + int i; + + /* Shift the weight into the same scale with the other weights for + * this vertex. */ + + weight >>= mesh->weightShifts[vertexIndex]; + + /* Look for an existing weight for our bone, or find the index + * with the lowest weight if not found, and store it in + * minWeightIndex. Note that we're not separately tagging indices + * as used/unused; unused ones will merely have a weight of + * zero. */ + + for (i = 0; i < bonesPerVertex; ++i) { + + /* If we find an existing weight for our bone, just add to + * that and break out. Otherwise, keep track of the minimum + * weight encountered so far. */ + + if (mesh->boneIndices[i][vertexIndex] == boneIndex) { + weight += mesh->boneWeights[i][vertexIndex]; + minWeightIndex = i; + break; + } + else { + M3Guint tempWeight = mesh->boneWeights[i][vertexIndex]; + if (tempWeight < minWeight) { + minWeight = tempWeight; + minWeightIndex = i; + } + } + } + + /* Check whether our total weight exceeds the allocated range, + * shifting all existing weights down if necessary */ + + while (weight >= (1 << 8)) { /* byte range */ + weight >>= 1; + mesh->weightShifts[vertexIndex] += 1; + for (i = 0; i < bonesPerVertex; ++i) { + mesh->boneWeights[i][vertexIndex] >>= 1; + } + M3G_ASSERT(mesh->weightShifts[vertexIndex] <= 31); + } + + /* Add the index and weight contribution of the new + * transformation, provided that the minimum weight found was + * indeed smaller than the one we're adding */ + + if (minWeightIndex >= 0) { + mesh->boneIndices[minWeightIndex][vertexIndex] = (M3Gubyte) boneIndex; + mesh->boneWeights[minWeightIndex][vertexIndex] = (M3Gubyte) weight; + + /* Need an update of the normalizing scales, too, as well as + * the actual transformed vertices */ + + mesh->weightsDirty = M3G_TRUE; + m3gInvalidateNode((Node*) mesh, NODE_TRANSFORMS_BIT|NODE_BBOX_BIT); + } +} + +/*! + * \internal + * \brief Computes the normalization scales for vertex weights + */ +static void m3gNormalizeWeights(SkinnedMesh *mesh) +{ + const M3Gint bonesPerVertex = mesh->bonesPerVertex; + const M3Gint vertexCount = mesh->weightedVertexCount; + M3Gint vi; + + for (vi = 0; vi < vertexCount; ++vi) { + M3Gint k; + + /* Sum up the 8-bit (possibly downshifted) weights */ + + M3Guint sum = 0; + for (k = 0; k < bonesPerVertex; ++k) { + sum += mesh->boneWeights[k][vi]; + } + + /* Compute an 8.24 reciprocal of the weights, then scale with + * that to normalize, and shift to 1.7 fixed point */ + { + M3Guint s = (sum > 0 ? (1U << 24) / sum : 0); + + sum = 0; + for (k = 0; k < bonesPerVertex; ++k) { + M3Guint normalized = (s * mesh->boneWeights[k][vi]) >> 17; + M3G_ASSERT(m3gInRange((M3Gint)normalized, 0, 128)); + sum += normalized; + mesh->normalizedWeights[k][vi] = (M3Gubyte) normalized; + } + + /* NOTE there is a maximum of ½ rounding error per + * component, plus the rounding error from the reciprocal + * calculation, so the sum of weights will often not sum + * to 128 exactly! We therefore only assert against + * clearly out-of-range values here */ + + M3G_ASSERT(sum == 0 || m3gInRange((M3Gint) sum, 96, 128)); + } + } + + mesh->weightsDirty = M3G_FALSE; +} + +/*! + * \internal + * \brief Computes an optimal exponent value for a fixed point + * transformation + * + * This scales the translation exponent up to optimally utilize the + * 32-bit intermediate precision if the matrix exponent is smaller. + */ +static M3Gint m3gOptimalExponent(M3Gint matrixExp, M3Gint transExp) +{ + M3Gint maxExp = matrixExp; + M3Gint shift = transExp - matrixExp; + if (shift > 0) { + + /* The matrix part will always occupy less than half of the + * available range if shifted down by at least one bit, so we + * can shift the translation up by a maximum of 15 bits. If + * the matrix is shifted by more than 31 bits, it will always + * flush to zero, freeing the full 32-bit range for the + * translation alone. */ + + if (shift >= 32) { /* matrix will flush to zero */ + shift = 16; + } + else if (shift >= 16) { /* matrix always < half of the range */ + shift = 15; + } + else { + shift -= 1; /* shift matrix by at least one bit */ + } + + maxExp = transExp - shift; + } + + M3G_ASSERT(maxExp >= matrixExp && maxExp >= transExp - 16); + return maxExp; +} + +/* + * \brief Fixed point vertex transformation + * + * \param mtx pointer to a 3x3 16-bit matrix + * \param mtxExp exponent for the matrix elements (upshift from int) + * \param trans pointer to 3-element 16-bit translation vector + * \param transExp exponent for the translation vector + * \param maxExp precalculated "optimal" exponent + * \param vx vertex X coordinate (16-bit range) + * \param vy vertex Y coordinate (16-bit range) + * \param vz vertex Z coordinate (16-bit range) + * \param out output vertex, 25 bits of precision + * \return exponent value for \c out + */ +static M3Gint m3gFixedPointTransform(const M3Gshort *mtx, M3Gint mtxExp, + const M3Gshort *trans, M3Gint transExp, + M3Gint maxExp, + M3Gint vx, M3Gint vy, M3Gint vz, + M3Gint *out) +{ + M3Gint shift; + M3Gint ox = 0, oy = 0, oz = 0; + + /* First put in the translation part, upscaled to the optimal + * range for this bone */ + + if (trans) { + shift = maxExp - (transExp - 16); + M3G_ASSERT(shift >= 0); + if (shift < 32) { + ox += ((M3Gint) trans[0] << 16) >> shift; + oy += ((M3Gint) trans[1] << 16) >> shift; + oz += ((M3Gint) trans[2] << 16) >> shift; + } + } + + /* Add the input multiplied with the base 3x3 matrix and shifted + * to the "maxExp" scale, provided that it has any effect on the + * outcome */ + + shift = maxExp - mtxExp; + M3G_ASSERT(shift >= 0); + if (shift < 32) { + +# if defined(M3G_DEBUG) + M3Gint iMin = (-1 << 31) + (65535 * 32768 >> shift); + M3Gint iMax = (M3Gint)((1u << 31)-1) - (65535 * 32768 >> shift); + M3G_ASSERT(m3gInRange(ox, iMin, iMax)); + M3G_ASSERT(m3gInRange(oy, iMin, iMax)); + M3G_ASSERT(m3gInRange(oz, iMin, iMax)); +# endif /* M3G_DEBUG */ + + ox += (mtx[0] * vx + mtx[3] * vy + mtx[6] * vz) >> shift; + oy += (mtx[1] * vx + mtx[4] * vy + mtx[7] * vz) >> shift; + oz += (mtx[2] * vx + mtx[5] * vy + mtx[8] * vz) >> shift; + } + + /* Shift the output down to fit into 25 bits; we're dropping 7 + * bits of precision here, so adjust the exponent accordingly */ + + out[0] = ox >> 7; + out[1] = oy >> 7; + out[2] = oz >> 7; + return maxExp + 7; +} + +/*! + * \internal + * \brief Applies scale and bias to a vertex + * + * This is required for vertices that have no bones attached. + * + * \param mesh the SkinnedMesh object + * \param vx vertex X coordinate (16-bit range) + * \param vy vertex Y coordinate (16-bit range) + * \param vz vertex Z coordinate (16-bit range) + * \param upshift scaling value for the input coordinates and the + * translation component of the transformation + * \param vertex output vertex position + * \return exponent value for \c vertex + */ +static M3Gint m3gScaleAndBiasVertex(const SkinnedMesh *mesh, + M3Gint vx, M3Gint vy, M3Gint vz, + M3Gint upshift, + M3Gshort *vertex) +{ + M3Gint temp[3]; + M3Gint expo; + + M3G_ASSERT(m3gInRange(vx, -1 << 15, (1 << 15) - 1)); + M3G_ASSERT(m3gInRange(vy, -1 << 15, (1 << 15) - 1)); + M3G_ASSERT(m3gInRange(vz, -1 << 15, (1 << 15) - 1)); + + expo = m3gFixedPointTransform(mesh->scaleMatrix, mesh->scaleExp, + mesh->biasVector, mesh->biasExp + upshift, + mesh->scaleBiasExp, + vx << upshift, vy << upshift, vz << upshift, + temp) - upshift; + + /* Scale down from 25 to 16 bits, adjusting the exponent + * accordingly */ + + vertex[0] = (M3Gshort)(temp[0] >> 9); + vertex[1] = (M3Gshort)(temp[1] >> 9); + vertex[2] = (M3Gshort)(temp[2] >> 9); + expo += 9; + + M3G_ASSERT(m3gInRange(expo, -127, 127)); + return expo; +} + +/*! + * \internal + * \brief Computes the blended position for a single vertex + * + * \param mesh the SkinnedMesh object + * \param vidx vertex index (for accessing bone data) + * \param vx vertex X coordinate (16-bit range) + * \param vy vertex Y coordinate (16-bit range) + * \param vz vertex Z coordinate (16-bit range) + * \param upshift scaling value for the input coordinates and the + * translation component of the transformation + * \param vertex output vertex position + * \return exponent value for \c vertex + */ +static M3Gint m3gBlendVertex(const SkinnedMesh *mesh, + M3Gint vidx, + M3Gint vx, M3Gint vy, M3Gint vz, + M3Gint upshift, + M3Gshort *vertex) +{ + const M3Gint boneCount = mesh->bonesPerVertex; + const PointerArray *boneArray = &mesh->bones; + M3Gint i; + + M3Gint outExp = -128; + M3Gint sumWeights = 0; + + M3Gint ox = 0, oy = 0, oz = 0; + + vx <<= upshift; + vy <<= upshift; + vz <<= upshift; + + M3G_ASSERT(m3gInRange(vx, -1 << 15, (1 << 15) - 1)); + M3G_ASSERT(m3gInRange(vy, -1 << 15, (1 << 15) - 1)); + M3G_ASSERT(m3gInRange(vz, -1 << 15, (1 << 15) - 1)); + + /* Loop over the bones and sum the contribution from each */ + + for (i = 0; i < boneCount; ++i) { + + M3Gint weight = (M3Gint) mesh->normalizedWeights[i][vidx]; + sumWeights += weight; + + /* Skip bones with zero weights */ + + if (weight > 0) { + + const Bone *bone = (const Bone *) + m3gGetArrayElement(boneArray, mesh->boneIndices[i][vidx]); + M3Gint temp[3]; + M3Gint shift; + + shift = m3gFixedPointTransform(bone->baseMatrix, bone->baseExp, + bone->posVec, bone->posExp + upshift, + bone->maxExp, + vx, vy, vz, + temp); + + shift = outExp - shift; + if (shift < 0) { + shift = -shift; + if (shift < 31) { + ox >>= shift; + oy >>= shift; + oz >>= shift; + } + else { + ox = oy = oz = 0; + } + outExp += shift; + shift = 0; + } + + /* Apply the vertex weights: 1.7 * 25.0 -> 26.7, but since + * the weights are positive and sum to 1, we should stay + * within the 32-bit range */ + + if (shift < 31) { + + M3G_ASSERT(m3gInRange(temp[0], -1 << 24, (1 << 24) - 1)); + M3G_ASSERT(m3gInRange(temp[1], -1 << 24, (1 << 24) - 1)); + M3G_ASSERT(m3gInRange(temp[2], -1 << 24, (1 << 24) - 1)); + + ox += (weight * temp[0]) >> shift; + oy += (weight * temp[1]) >> shift; + oz += (weight * temp[2]) >> shift; + } + } + } + + /* Before returning, we still need to check for the special case + * of all-zero weights, and shift the values from the post-scaling + * 32-bit precision back into the 16-bit range; we're essentially + * dropping the (25 - 16) bits of the blended result, so the + * exponent must change accordingly */ + + if (sumWeights > 0) { + vertex[0] = (M3Gshort)(ox >> 16); + vertex[1] = (M3Gshort)(oy >> 16); + vertex[2] = (M3Gshort)(oz >> 16); + outExp = outExp - upshift + 9; + + M3G_ASSERT(m3gInRange(outExp, -127, 127)); + return outExp; + } + else { + vx >>= upshift; + vy >>= upshift; + vz >>= upshift; + return m3gScaleAndBiasVertex(mesh, vx, vy, vz, upshift, vertex); + } +} + +/*! + * \internal + * \brief Computes the blended normal vector for a single vertex + * + * \param mesh the SkinnedMesh object + * \param vidx vertex index (for accessing bone data) + * \param nx normal X coordinate (16-bit range) + * \param ny normal Y coordinate (16-bit range) + * \param nz normal Z coordinate (16-bit range) + * \param upshift scaling for input coordinates to increase precision + * \param normal output normal vector (8-bit range!) + * \return a shift value for the output vertex (scale from integer) + */ +static void m3gBlendNormal(const SkinnedMesh *mesh, + M3Gint vidx, + M3Gint nx, M3Gint ny, M3Gint nz, + M3Gint upshift, + M3Gbyte *normal) +{ + const M3Gint boneCount = mesh->bonesPerVertex; + const PointerArray *boneArray = &mesh->bones; + M3Gint i; + + M3Gint outExp = -128; + M3Gint sumWeights = 0; + + M3Gint ox = 0, oy = 0, oz = 0; + + nx <<= upshift; + ny <<= upshift; + nz <<= upshift; + + M3G_ASSERT(m3gInRange(nx, -1 << 15, (1 << 15) - 1)); + M3G_ASSERT(m3gInRange(ny, -1 << 15, (1 << 15) - 1)); + M3G_ASSERT(m3gInRange(nz, -1 << 15, (1 << 15) - 1)); + + /* Loop over the bones and sum the contribution from each */ + + for (i = 0; i < boneCount; ++i) { + + M3Gint weight = (M3Gint) mesh->normalizedWeights[i][vidx]; + sumWeights += weight; + + /* Skip bones with zero weights */ + + if (weight > 0) { + + const Bone *bone = (const Bone *) + m3gGetArrayElement(boneArray, mesh->boneIndices[i][vidx]); + M3Gint temp[3]; + M3Gint shift; + + shift = m3gFixedPointTransform(bone->normalMatrix, 0, + NULL, 0, + 0, + nx, ny, nz, + temp); + + shift = outExp - shift; + if (shift < 0) { + shift = -shift; + if (shift < 31) { + ox >>= shift; + oy >>= shift; + oz >>= shift; + } + else { + ox = oy = oz = 0; + } + outExp += shift; + shift = 0; + } + + /* Apply the vertex weights: 1.7 * 25.0 -> 26.7, but since + * the weights are positive and sum to 1, we should stay + * within the 32-bit range */ + + if (shift < 31) { + + M3G_ASSERT(m3gInRange(temp[0], -1 << 24, (1 << 24) - 1)); + M3G_ASSERT(m3gInRange(temp[1], -1 << 24, (1 << 24) - 1)); + M3G_ASSERT(m3gInRange(temp[2], -1 << 24, (1 << 24) - 1)); + + ox += (weight * temp[0]) >> shift; + oy += (weight * temp[1]) >> shift; + oz += (weight * temp[2]) >> shift; + } + } + } + + /* Before returning, we still need to check for the special case + * of all-zero weights, and shift the values from the post-scaling + * 32-bit precision down into the 8-bit range */ + + if (sumWeights > 0) { + normal[0] = (M3Gbyte)(ox >> 24); + normal[1] = (M3Gbyte)(oy >> 24); + normal[2] = (M3Gbyte)(oz >> 24); + } + else { + normal[0] = (M3Gbyte)(ox >> 8); + normal[1] = (M3Gbyte)(oy >> 8); + normal[2] = (M3Gbyte)(oz >> 8); + } +} + +/*! + * \internal + * \brief Updates internal vertex buffer + * + * \param mesh SkinnedMesh object + * + * \retval M3G_TRUE VertexBuffer is up to date + * \retval M3G_FALSE Failed to update VertexBuffer, out of memory exception raised + */ +static M3Gbool m3gSkinnedMeshUpdateVB(SkinnedMesh *mesh) +{ + M3Gint vbTimestamp; + M3G_ASSERT(mesh->mesh.vertexBuffer != NULL); + M3G_ASSERT(mesh->morphedVB != NULL); + + /* Source vertex buffer array configuration changed since last + * update? */ + + vbTimestamp = m3gGetTimestamp(mesh->mesh.vertexBuffer); + + if (mesh->vbTimestamp != vbTimestamp) { + Interface *m3g = M3G_INTERFACE(mesh); + VertexArray *array; + M3Gint vcount = m3gGetVertexCount(mesh->mesh.vertexBuffer); + + /* Must ensure that our internal morphing buffer matches the + * configuration of the source buffer, with dedicated arrays + * for the morphed positions and normals */ + + if (!m3gMakeModifiedVertexBuffer(mesh->morphedVB, + mesh->mesh.vertexBuffer, + M3G_POSITION_BIT|M3G_NORMAL_BIT, + M3G_FALSE)) { + return M3G_FALSE; /* out of memory */ + } + + /* We always have the vertex positions as shorts, but the + * array may not be actually initialized yet, so we must check + * whether to create a copy or not */ + + if (mesh->mesh.vertexBuffer->vertices) { + array = m3gCreateVertexArray(m3g, vcount, 3, M3G_SHORT); + if (!array) { + return M3G_FALSE; + } + m3gSetVertexArray(mesh->morphedVB, array, 1.f, NULL, 0); + } + + /* Normals (always bytes) only exist if in the original VB */ + + if (mesh->mesh.vertexBuffer->normals) { + array = m3gCreateVertexArray(m3g, vcount, 3, M3G_BYTE); + if (!array) { + return M3G_FALSE; + } + m3gSetNormalArray(mesh->morphedVB, array); + } + + mesh->vbTimestamp = vbTimestamp; + } + + /* The default color must always be updated, because it can be + * animated (doesn't affect timestamp) */ + + mesh->morphedVB->defaultColor = mesh->mesh.vertexBuffer->defaultColor; + return M3G_TRUE; +} + + +/*! + * \internal + * \brief Gets the transformation(s) for a single bone record + * + * Also stores the normal transformation matrix if needed. + * + * \param mesh pointer to the mesh object + * \param bone pointer to the bone record + * \param hasNormals flag indicating whether the normals transformation + * should be computed and cached in the bone record + * \param mtx matrix to store the vertex transformation in + */ +static M3G_INLINE M3Gbool m3gGetBoneTransformInternal(SkinnedMesh *mesh, + Bone *bone, + M3Gbool hasNormals, + Matrix *mtx) +{ + const VertexBuffer *vb = mesh->mesh.vertexBuffer; + + /* Get the vertex transformation and concatenate it with the + * at-rest matrix and the vertex scale and bias transformations. + * The resulting 3x4 transformation matrix is then split into a + * fixed point 3x3 matrix and translation vector */ + + if (!m3gGetTransformTo(bone->node, (Node*) mesh, mtx)) { + return M3G_FALSE; /* no path or singular transform */ + } + m3gMulMatrix(mtx, &bone->toBone); + + /* If normals are enabled, compute and store the inverse transpose + * matrix for transforming normals at this stage */ + + if (hasNormals) { + Matrix t; + if (!m3gInverseTranspose(&t, mtx)) { + m3gRaiseError(M3G_INTERFACE(mesh), M3G_ARITHMETIC_ERROR); + return M3G_FALSE; /* singular transform */ + } + m3gGetFixedPoint3x3Basis(&t, bone->normalMatrix); + } + + /* Apply the vertex bias and scale to the transformation */ + + m3gTranslateMatrix( + mtx, vb->vertexBias[0], vb->vertexBias[1], vb->vertexBias[2]); + m3gScaleMatrix(mtx, vb->vertexScale, vb->vertexScale, vb->vertexScale); + + return M3G_TRUE; +} + +/*! + * \internal + * \brief Compute and cache the bone transformations for morphing + * + * \param mesh the SkinnedMesh object + * \param posShift vertex position value "gain" + */ +static M3Gbool m3gPreComputeTransformations(SkinnedMesh *mesh, + M3Gint posShift, + M3Gbool hasNormals) +{ + M3Gint boneCount = m3gArraySize(&mesh->bones); + M3Gint i; + Matrix *tBone = NULL; + + /* First, just compute the floating point transformation matrices + * for the bones, caching them in a temp array */ + + if (boneCount > 0) { + tBone = m3gAllocTemp(M3G_INTERFACE(mesh), boneCount * sizeof(Matrix)); + if (!tBone) { + return M3G_FALSE; /* out of memory */ + } + for (i = 0; i < boneCount; ++i) { + Bone *bone = m3gGetArrayElement(&mesh->bones, i); + if (!m3gGetBoneTransformInternal(mesh, bone, hasNormals, &tBone[i])) { + return M3G_FALSE; + } + } + } + + /* Find the value range of the bone translations, and offset the + * bones to center output vertex values (roughly) around the + * origin */ + { + const VertexBuffer *vb = mesh->mesh.vertexBuffer; + M3Gfloat min[3], max[3], bias[3]; + M3Gint maxExp; + Vec4 t; + + /* Find the minimum and maximum values; start with the plain + * vertex bias for non-weighted bones */ + + min[0] = max[0] = vb->vertexBias[0]; + min[1] = max[1] = vb->vertexBias[1]; + min[2] = max[2] = vb->vertexBias[2]; + + for (i = 0; i < boneCount; ++i) { + m3gGetMatrixColumn(&tBone[i], 3, &t); + min[0] = M3G_MIN(min[0], t.x); + max[0] = M3G_MAX(max[0], t.x); + min[1] = M3G_MIN(min[1], t.y); + max[1] = M3G_MAX(max[1], t.y); + min[2] = M3G_MIN(min[2], t.z); + max[2] = M3G_MAX(max[2], t.z); + } + + /* Divide to get the mean translation, store in the + * destination VB, and invert for de-biasing the bones */ + + for (i = 0; i < 3; ++i) { + bias[i] = m3gMul(0.5f, m3gAdd(min[i], max[i])); + mesh->morphedVB->vertexBias[i] = bias[i]; + bias[i] = m3gNegate(bias[i]); + } + + /* Offset bones by the (now inverted) bias vector, and store + * the fixed point matrix & vector parts in the bone record; + * also set the maximum bone exponent into the mesh */ + + maxExp = -128; + for (i = 0; i < boneCount; ++i) { + Bone *bone = m3gGetArrayElement(&mesh->bones, i); + m3gPreTranslateMatrix(&tBone[i], bias[0], bias[1], bias[2]); /*lint !e613 tBone not null if boneCount > 0 */ + + bone->baseExp = (M3Gshort) + m3gGetFixedPoint3x3Basis(&tBone[i], bone->baseMatrix); /*lint !e613 tBone not null if boneCount > 0 */ + bone->posExp = (M3Gshort) + m3gGetFixedPointTranslation(&tBone[i], bone->posVec); /*lint !e613 tBone not null if boneCount > 0 */ + bone->maxExp = (M3Gshort) + m3gOptimalExponent(bone->baseExp, bone->posExp + posShift); + + maxExp = M3G_MAX(maxExp, bone->maxExp); + } + + /* Make a fixed-point matrix for applying the scale and bias as + * well, for vertices not attached to any bone (this is not the + * optimal way to store the information, but we can just reuse + * existing code this way) */ + { + Matrix sb; + m3gTranslationMatrix(&sb, + m3gAdd(bias[0], vb->vertexBias[0]), + m3gAdd(bias[1], vb->vertexBias[1]), + m3gAdd(bias[2], vb->vertexBias[2])); + m3gScaleMatrix(&sb, + vb->vertexScale, vb->vertexScale, vb->vertexScale); + + mesh->scaleExp = (M3Gshort) + m3gGetFixedPoint3x3Basis(&sb, mesh->scaleMatrix); + mesh->biasExp = (M3Gshort) + m3gGetFixedPointTranslation(&sb, mesh->biasVector); + mesh->scaleBiasExp = (M3Gshort) + m3gOptimalExponent(mesh->scaleExp, mesh->biasExp + posShift); + + maxExp = M3G_MAX(mesh->scaleBiasExp, maxExp); + } + + /* Compute the maximum post-blending exponent and store it as the + * morphed vertex buffer scale -- this is dependent on the + * implementations of m3gBlendVertex, m3gScaleAndBiasVertex, and + * m3gFixedPointTransform! */ + + maxExp = maxExp + 16 - posShift; + M3G_ASSERT(m3gInRange(maxExp, -127, 127)); + *(M3Gint*)&mesh->morphedVB->vertexScale = (maxExp + 127) << 23; + mesh->maxExp = (M3Gshort) maxExp; + } + + if (boneCount > 0) { + m3gFreeTemp(M3G_INTERFACE(mesh)); + } + + return M3G_TRUE; +} + +/*! + * \internal + * \brief Computes derived data required for bounding volumes and skinning + */ +static M3Gbool m3gSkinnedMeshPreMorph(SkinnedMesh *mesh) +{ + const VertexBuffer *srcVB = mesh->mesh.vertexBuffer; + M3Gint posShift = 0, normalShift = 0; + + /* Compute upscaling shift values for positions and normals so + * that we can maximize precision even for absurdly small + * vertex values */ + { + M3Gint minVal, maxVal; + + if (srcVB->normals) { + m3gGetArrayValueRange(srcVB->normals, &minVal, &maxVal); + maxVal = M3G_MAX(-minVal, maxVal); + M3G_ASSERT(maxVal >= 0); + if (maxVal) { + while ((maxVal << normalShift) < (1 << 14)) { + ++normalShift; + } + } + } + + m3gGetArrayValueRange(srcVB->vertices, &minVal, &maxVal); + maxVal = M3G_MAX(-minVal, maxVal); + M3G_ASSERT(maxVal >= 0); + if (maxVal) { + while ((maxVal << posShift) < (1 << 14)) { + ++posShift; + } + } + + mesh->posShift = (M3Gshort) posShift; + mesh->normalShift = (M3Gshort) normalShift; + } + + /* Now that we can compute the optimized exponents for the + * transformations based on the position upshift value, let's + * resolve the bone transformations; this will also cache the + * maximum bone exponent in mesh->maxExp */ + + if (!m3gPreComputeTransformations(mesh, + posShift, + srcVB->normals != NULL)) { + return M3G_FALSE; /* invalid transform */ + } + + return M3G_TRUE; +} + +/*! + * \internal + * \brief Does the actual vertex morphing into the internal vertex buffer + * + * \param mesh SkinnedMesh object + * \retval M3G_TRUE skinning ok + * \retval M3G_FALSE skinning failed, exception raised + */ +static void m3gSkinnedMeshMorph(SkinnedMesh *mesh) +{ + const VertexBuffer *srcVB = mesh->mesh.vertexBuffer; + const void *srcPositions; + const void *srcNormals = NULL; + VertexBuffer *dstVB = mesh->morphedVB; + M3Gshort *dstPositions; + M3Gbyte *dstNormals = NULL; + M3Gint vertexCount = mesh->weightedVertexCount; + M3Gint maxExp = mesh->maxExp; + M3Gint posShift = mesh->posShift, normShift = mesh->normalShift; + M3Gint i; + + M3G_ASSERT(!((Node*) mesh)->dirtyBits); + + /* Let's update the vertex weights if we need to */ + + if (mesh->weightsDirty) { + m3gNormalizeWeights(mesh); + } + + /* Get pointers to source and destination position and normal + * data; the latter will always be shorts and bytes, + * respectively, while the former can be either */ + + srcPositions = m3gMapVertexArrayReadOnly(srcVB->vertices); + dstPositions = (M3Gshort*) m3gMapVertexArray(dstVB->vertices); + if (srcVB->normals) { + srcNormals = m3gMapVertexArrayReadOnly(srcVB->normals); + dstNormals = (M3Gbyte*) m3gMapVertexArray(dstVB->normals); + } + + /* Transform the vertices that are affected by bones */ + { + M3Gshort *dst = dstPositions; + + if (srcVB->vertices->elementType == GL_BYTE) { + const M3Gbyte *src = (const M3Gbyte*) srcPositions; + for (i = 0; i < vertexCount; ++i) { + M3Gint shift = + maxExp - m3gBlendVertex(mesh, i, + src[0], src[1], src[2], + posShift, + dst); + if (shift > 31) { + *dst++ = 0; + *dst++ = 0; + *dst++ = 0; + } + else { + *dst++ >>= shift; + *dst++ >>= shift; + *dst++ >>= shift; + } + + src += 4; /* byte data always padded to 32 bits */ + } + } + else { + const M3Gshort *src = (const M3Gshort*) srcPositions; + for (i = 0; i < vertexCount; ++i) { + M3Gint shift = + maxExp - m3gBlendVertex(mesh, i, + src[0], src[1], src[2], + posShift, + dst); + if (shift > 31) { + *dst++ = 0; + *dst++ = 0; + *dst++ = 0; + } + else { + *dst++ >>= shift; + *dst++ >>= shift; + *dst++ >>= shift; + } + + src += 3; + } + } + } + + /* Transform the normals (if enabled). Normals will be + * normalized when rendering, so no need to keep track of + * scales here */ + + if (srcNormals) { + M3Gbyte *dst = dstNormals; + + if (srcVB->normals->elementType == GL_BYTE) { + const M3Gbyte *src = (const M3Gbyte*) srcNormals; + for (i = 0; i < vertexCount; ++i) { + m3gBlendNormal(mesh, i, + src[0], src[1], src[2], + normShift, + dst); + src += 4; /* byte data padded to 32 bits */ + dst += 4; + } + } + else { + const M3Gshort *src = (const M3Gshort*) srcNormals; + for (i = 0; i < vertexCount; ++i) { + m3gBlendNormal(mesh, i, + src[0], src[1], src[2], + normShift, + dst); + src += 3; + dst += 4; + } + } + } + + /* Finally, handle the remaining vertices, which have no bones + * attached; these just need to have the scale and bias + * applied */ + + vertexCount = m3gGetNumVertices(srcVB); + if (i < vertexCount) { + + M3Gint startIndex = i; + M3Gshort *dstPos = dstPositions + startIndex * 3; + M3Gshort temp[3]; + + if (srcVB->vertices->elementType == GL_BYTE) { + const M3Gbyte *src = ((const M3Gbyte*) srcPositions) + startIndex * 4; + for (i = startIndex ; i < vertexCount; ++i) { + M3Gint shift = + maxExp - m3gScaleAndBiasVertex(mesh, + src[0], src[1], src[2], + posShift, + temp); + *dstPos++ = (M3Gshort)(temp[0] >> shift); + *dstPos++ = (M3Gshort)(temp[1] >> shift); + *dstPos++ = (M3Gshort)(temp[2] >> shift); + src += 4; /* byte data, padded to 32 bits */ + } + } + else { + const M3Gshort *src = ((const M3Gshort*) srcPositions) + startIndex * 3; + for (i = startIndex ; i < vertexCount; ++i) { + M3Gint shift = + maxExp - m3gScaleAndBiasVertex(mesh, + src[0], src[1], src[2], + posShift, + temp); + *dstPos++ = (M3Gshort)(temp[0] >> shift); + *dstPos++ = (M3Gshort)(temp[1] >> shift); + *dstPos++ = (M3Gshort)(temp[2] >> shift); + src += 3; + } + } + + /* Byte normals can just use a memcopy, as we don't have + * to scale them at all; shorts will require a conversion, + * after prescaling with the normal upshift to avoid + * underflowing to zero */ + + if (srcNormals) { + M3Gbyte *dstNorm = dstNormals + startIndex * 4; + if (srcVB->normals->elementType == GL_BYTE) { + const M3Gbyte *src = + ((const M3Gbyte*) srcNormals) + startIndex * 4; + m3gCopy(dstNorm, src, (vertexCount - startIndex) * 4); + } + else { + const M3Gshort *src = + ((const M3Gshort*) srcNormals) + startIndex * 3; + for (i = startIndex ; i < vertexCount; ++i) { + *dstNorm++ = (M3Gbyte)((*src++ << normShift) >> 8); + *dstNorm++ = (M3Gbyte)((*src++ << normShift) >> 8); + *dstNorm++ = (M3Gbyte)((*src++ << normShift) >> 8); + ++dstNorm; /* again, padding for byte values */ + } + } + } + } + + /* All done! Clean up and exit */ + + m3gUnmapVertexArray(srcVB->vertices); + m3gUnmapVertexArray(dstVB->vertices); + if (srcNormals) { + m3gUnmapVertexArray(srcVB->normals); + m3gUnmapVertexArray(dstVB->normals); + } +} + +/*! + * \internal + * \brief Overloaded Node method. + * + * Setup skinned mesh render. Call mesh render setup, + * do skinning calculations and traverse into the skeleton or the parent + * + * \param self SkinnedMesh object + * \param toCamera transform to camera + * \param alphaFactor total alpha factor + * \param caller caller node + * \param renderQueue RenderQueue + * + * \retval M3G_TRUE continue render setup + * \retval M3G_FALSE abort render setup + */ +static M3Gbool m3gSkinnedMeshSetupRender(Node *self, + const Node *caller, + SetupRenderState *s, + RenderQueue *renderQueue) +{ + SkinnedMesh *mesh = (SkinnedMesh *)self; + Node *skeleton = (Node*) mesh->skeleton; + M3Gbool enabled, success = M3G_TRUE; + m3gIncStat(M3G_INTERFACE(self), M3G_STAT_RENDER_NODES, 1); + + /* Optimize the rendering-enable checking for top-down traversal */ + + enabled = (self->enableBits & NODE_RENDER_BIT) != 0; + if (caller != self->parent) { + enabled = m3gHasEnabledPath(self, renderQueue->root); + s->cullMask = CULLMASK_ALL; + } + + /* Handle self and the skeleton if enabled */ + + if (enabled) { + + /* Traverse into the skeleton unless coming from there */ + + if (skeleton != caller) { + SetupRenderState cs; + cs.cullMask = s->cullMask; + + M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS); + m3gGetCompositeNodeTransform(skeleton, &cs.toCamera); + m3gPreMultiplyMatrix(&cs.toCamera, &s->toCamera); + M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS); + + success = M3G_VFUNC(Node, skeleton, setupRender)(skeleton, + self, + &cs, + renderQueue); + } + + /* Handle self if in scope */ + + if ((self->scope & renderQueue->scope) != 0) { + + /* Try view frustum culling */ + +# if defined(M3G_ENABLE_VF_CULLING) + m3gUpdateCullingMask(s, renderQueue->camera, &mesh->bbox); +# endif + + if (s->cullMask == 0) { + m3gIncStat(M3G_INTERFACE(self), + M3G_STAT_RENDER_NODES_CULLED, 1); + } + else { + success &= m3gQueueMesh((Mesh*) self, &s->toCamera, renderQueue); + + if (success) { + M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SKIN); + m3gSkinnedMeshMorph(mesh); + M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SKIN); + } + } + } + } + + /* Traverse into the parent node unless coming from there. Again, + * discard the old traversal state at this point, as we're not + * coming back. */ + + if (success && self != renderQueue->root) { + Node *parent = self->parent; + if (parent != NULL && parent != caller) { + Matrix t; + + M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS); + if (!m3gGetInverseNodeTransform(self, &t)) { + return M3G_FALSE; + } + m3gMulMatrix(&s->toCamera, &t); + M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS); + + success = M3G_VFUNC(Node, parent, setupRender)(parent, + self, + s, + renderQueue); + } + } + + return success; +} + +/*! + * \internal + * \brief Overloaded Node method. + * + * Renders one skinned submesh. + * + * \param self SkinnedMesh object + * \param ctx current render context + * \param patchIndex submesh index + */ +static void m3gSkinnedMeshDoRender(Node *self, + RenderContext *ctx, + const Matrix *toCamera, + int patchIndex) +{ + SkinnedMesh *mesh = (SkinnedMesh *)self; + IndexBuffer *indexBuffer = mesh->mesh.indexBuffers[patchIndex]; + Appearance *appearance = mesh->mesh.appearances[patchIndex]; + + if (indexBuffer == NULL || appearance == NULL) + return; + + m3gDrawMesh(ctx, + mesh->morphedVB, + indexBuffer, + appearance, + toCamera, + mesh->mesh.totalAlphaFactor + 1, + mesh->mesh.node.scope); +} + +/*! + * \internal + * \brief Overloaded Node method. + * + * Do skinning calculations and forward to Mesh internal ray intersect. + * + * \param self SkinnedMesh object + * \param mask pick scope mask + * \param ray pick ray + * \param ri RayIntersection object + * \param toGroup transform to originating group + * \retval M3G_TRUE continue pick + * \retval M3G_FALSE abort pick + */ +static M3Gbool m3gSkinnedMeshRayIntersect( Node *self, + M3Gint mask, + M3Gfloat *ray, + RayIntersection *ri, + Matrix *toGroup) +{ + SkinnedMesh *mesh = (SkinnedMesh *)self; + M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SKIN); + + if ((((Node *)mesh)->scope & mask) == 0) { + return M3G_TRUE; + } + + if (!m3gSkinnedMeshPreMorph(mesh)) { + return M3G_FALSE; + } + m3gSkinnedMeshMorph(mesh); + M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SKIN); + return m3gMeshRayIntersectInternal( &mesh->mesh, + mesh->morphedVB, + mask, + ray, + ri, + toGroup); +} + +/*! + * \internal + * \brief Overloaded Object3D method. + * + * \param self SkinnedMesh object + * \param time current world time + * \return minimum validity + */ +static M3Gint m3gSkinnedMeshApplyAnimation(Object *self, M3Gint time) +{ + SkinnedMesh *mesh = (SkinnedMesh *)self; + + M3Gint validity = m3gMeshApplyAnimation((Object*) &mesh->mesh, time); + + if (validity > 0) { + M3Gint validity2 = + M3G_VFUNC(Object, mesh->skeleton, applyAnimation)( + (Object *)mesh->skeleton, time); + return (validity < validity2 ? validity : validity2); + } + return 0; +} + +/*! + * \internal + * \brief Overloaded Object3D method. + * + * \param self SkinnedMesh object + * \param references array of reference objects + * \return number of references + */ +static M3Gint m3gSkinnedMeshDoGetReferences(Object *self, Object **references) +{ + SkinnedMesh *smesh = (SkinnedMesh *)self; + M3Gint num = m3gMeshDoGetReferences(self, references); + if (smesh->skeleton != NULL) + { + if (references != NULL) + references[num] = (Object *)smesh->skeleton; + num++; + } + return num; +} + +/*! + * \internal + * \brief Overloaded Object3D method. + */ +static Object *m3gSkinnedMeshFindID(Object *self, M3Gint userID) +{ + SkinnedMesh *smesh = (SkinnedMesh *)self; + Object *found = m3gMeshFindID(self, userID); + + if (!found && smesh->skeleton != NULL) { + found = m3gFindID((Object*) smesh->skeleton, userID); + } + return found; +} + +/*! + * \internal + * \brief Overloaded Object3D method. + * + * \param originalObj original SkinnedMesh object + * \param cloneObj pointer to cloned SkinnedMesh object + * \param pairs array for all object-duplicate pairs + * \param numPairs number of pairs + */ +static M3Gbool m3gSkinnedMeshDuplicate(const Object *originalObj, + Object **cloneObj, + Object **pairs, + M3Gint *numPairs) +{ + M3Gint i; + SkinnedMesh *original = (SkinnedMesh *)originalObj; + Group *skeleton = NULL; + SkinnedMesh *clone; + M3G_ASSERT(*cloneObj == NULL); /* no derived classes */ + + /* Duplicate the skeleton group first, as this is a prerequisite + * for creating the clone SkinnedMesh. If this fails, we must + * manually delete the skeleton, as no record of it will be stored + * anywhere else; we also need to hold a reference until ownership + * of the skeleton transfers to the clone SkinnedMesh. */ + + if (!M3G_VFUNC(Object, original->skeleton, duplicate)( + (Object*) original->skeleton, + (Object**) &skeleton, pairs, numPairs)) { + m3gDeleteObject((Object*) skeleton); + return M3G_FALSE; + } + m3gAddRef((Object*) skeleton); /* don't leave this floating */ + + /* Create the actual clone object */ + + clone = (SkinnedMesh*) + m3gCreateSkinnedMesh(originalObj->interface, + original->mesh.vertexBuffer, + original->mesh.indexBuffers, + original->mesh.appearances, + original->mesh.trianglePatchCount, + skeleton); + m3gDeleteRef((Object*) skeleton); /* ownership transferred to clone */ + if (!clone) { + return M3G_FALSE; + } + *cloneObj = (Object *)clone; + + /* Duplicate base class data; we're OK for normal deletion at this + * point, so can just leave it up to the caller on failure */ + + if (!m3gMeshDuplicate(originalObj, cloneObj, pairs, numPairs)) { + return M3G_FALSE; + } + + /* Duplicate the rest of our own data */ + + if (!m3gEnsureVertexCount(clone, original->weightedVertexCount) || + !m3gEnsureBonesPerVertex(clone, original->bonesPerVertex)) { + return M3G_FALSE; /* out of memory */ + } + + for (i = 0; i < clone->bonesPerVertex; i++) { + m3gCopy(clone->boneIndices[i], original->boneIndices[i], + clone->weightedVertexCount); + m3gCopy(clone->boneWeights[i], original->boneWeights[i], + clone->weightedVertexCount); + m3gCopy(clone->normalizedWeights[i], original->normalizedWeights[i], + clone->weightedVertexCount); + } + clone->weightsDirty = original->weightsDirty; + m3gCopy(clone->weightShifts, original->weightShifts, + clone->weightedVertexCount); + + for (i = 0; i < m3gArraySize(&original->bones); i++) { + Bone *cloneBone = (Bone*) m3gAllocZ(originalObj->interface, + sizeof(Bone)); + if (!cloneBone) { + return M3G_FALSE; /* out of memory */ + } + /* this line looks odd, but really just copies the *contents* + * of the bone structure... */ + *cloneBone = *(Bone*)m3gGetArrayElement(&original->bones, i); + + if (m3gArrayAppend(&clone->bones, cloneBone, originalObj->interface) < 0) { + m3gFree(originalObj->interface, cloneBone); + return M3G_FALSE; /* out of memory */ + } + } + + return M3G_TRUE; +} + +/*! + * \internal + * \brief Overloaded Node method + */ +static M3Gint m3gSkinnedMeshGetBBox(Node *self, AABB *bbox) +{ + SkinnedMesh *mesh = (SkinnedMesh*) self; + Node *skeleton = (Node*) mesh->skeleton; + + /* First update our local bounding box if necessary */ + + if (self->dirtyBits & NODE_BBOX_BIT) { + + /* Compute an estimated bounding box from the morphed vertex + * buffer scale and bias (from PreComputeTransformations). + * The morphed vertex array is always scaled to utilize most + * of the 16-bit short range, so we just use that as the + * extents. */ + { + const GLfloat scale = mesh->morphedVB->vertexScale; + const GLfloat *bias = mesh->morphedVB->vertexBias; + int i; + + for (i = 0; i < 3; ++i) { + mesh->bbox.min[i] = m3gMadd(scale, -1 << 15, bias[i]); + mesh->bbox.max[i] = m3gMadd(scale, (1 << 15) - 1, bias[i]); + } + } + } + *bbox = mesh->bbox; + + /* Mix in the skeleton bounding box if we need to -- but only into + * the output bbox, as we're handling the local mesh bbox + * specially in SetupRender! */ + + if (skeleton->hasRenderables && skeleton->enableBits) { + AABB skeletonBBox; + if (m3gGetNodeBBox(skeleton, &skeletonBBox)) { + Matrix t; + m3gGetCompositeNodeTransform(self, &t); + m3gTransformAABB(&skeletonBBox, &t); + m3gFitAABB(bbox, &skeletonBBox, bbox); + } + } + return m3gArraySize(&mesh->bones) * VFC_NODE_OVERHEAD; +} + +/*! + * \internal + * \brief Overloaded Node method + */ +static M3Gbool m3gSkinnedMeshValidate(Node *self, M3Gbitmask stateBits, M3Gint scope) +{ + SkinnedMesh *mesh = (SkinnedMesh*) self; + Interface *m3g = M3G_INTERFACE(mesh); + Node *skeleton = (Node*) mesh->skeleton; + const VertexBuffer *srcVB = mesh->mesh.vertexBuffer; + M3Gint vertexCount = mesh->weightedVertexCount; + + if ((scope & self->scope) != 0) { + if (stateBits & self->enableBits) { + + /* Check for invalid SkinnedMesh state */ + + if (srcVB->vertices == NULL || vertexCount > srcVB->vertexCount) { + m3gRaiseError(m3g, M3G_INVALID_OPERATION); + return M3G_FALSE; + } + if (!m3gSkinnedMeshUpdateVB(mesh)) { /* Memory allocation failed */ + return M3G_FALSE; + } + + /* Validate the skeleton */ + + if (!m3gValidateNode(skeleton, stateBits, scope)) { + return M3G_FALSE; + } + + /* Validate our local state */ + + if ((self->dirtyBits & NODE_TRANSFORMS_BIT) != 0 || + m3gGetTimestamp(srcVB) != mesh->mesh.vbTimestamp) { + if (!m3gSkinnedMeshPreMorph((SkinnedMesh*) self)) { + return M3G_FALSE; + } + } + if (self->dirtyBits & NODE_BBOX_BIT) { + M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_VFC_UPDATE); + m3gGetNodeBBox(self, &mesh->bbox); + M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_VFC_UPDATE); + } + + return m3gMeshValidate(self, stateBits, scope); + } + } + return M3G_TRUE; +} + +/*! + * \internal + * \brief Overloaded Object3D method. + * + * \param self SkinnedMesh object + * \param pairs array for all object-duplicate pairs + * \param numPairs number of pairs + */ +static void m3gSkinnedMeshUpdateDuplicateReferences(Node *self, Object **pairs, M3Gint numPairs) +{ + SkinnedMesh *skinned = (SkinnedMesh *)self; + SkinnedMesh *duplicate = (SkinnedMesh *)m3gGetDuplicatedInstance(self, pairs, numPairs); + M3Gint i, n; + + m3gNodeUpdateDuplicateReferences(self, pairs, numPairs); + + n = m3gArraySize(&duplicate->bones); + for (i = 0; i < n; i++) { + Bone *bone = (Bone*) m3gGetArrayElement(&duplicate->bones, i); + Node *boneDuplicate = m3gGetDuplicatedInstance(bone->node, pairs, numPairs); + if (boneDuplicate != NULL) { + bone->node = boneDuplicate; + } + } + + M3G_VFUNC(Node, skinned->skeleton, updateDuplicateReferences)( + (Node *)skinned->skeleton, pairs, numPairs); +} + +/*! + * \internal + * \brief Initializes a SkinnedMesh object. See specification + * for default values. + * + * \param m3g M3G interface + * \param mesh SkinnedMesh object + * \param hVertices VertexBuffer object + * \param hTriangles array of IndexBuffer objects + * \param hAppearances array of Appearance objects + * \param trianglePatchCount number of submeshes + * \param hSkeleton Group containing the skeleton + * \retval M3G_TRUE success + * \retval M3G_FALSE failure + */ +static M3Gbool m3gInitSkinnedMesh(Interface *m3g, + SkinnedMesh *mesh, + M3GVertexBuffer hVertices, + M3GIndexBuffer *hTriangles, + M3GAppearance *hAppearances, + M3Gint trianglePatchCount, + M3GGroup hSkeleton) +{ + /* SkinnedMesh is derived from Mesh */ + if (!m3gInitMesh(m3g, &mesh->mesh, + hVertices, hTriangles, hAppearances, + trianglePatchCount, + M3G_CLASS_SKINNED_MESH)) + { + return M3G_FALSE; + } + + /* Make sure our mesh gets blended even if no bones are added */ + ((Node*)mesh)->dirtyBits |= NODE_TRANSFORMS_BIT; + + /* Set default values, see RI SkinnedMesh.java for reference */ + m3gSetParent(&((Group *)hSkeleton)->node, &mesh->mesh.node); + M3G_ASSIGN_REF(mesh->skeleton, (Group *)hSkeleton); + + m3gInitArray(&mesh->bones); + + mesh->morphedVB = (VertexBuffer *)m3gCreateVertexBuffer(m3g); + if (mesh->morphedVB == NULL + || m3gSkinnedMeshUpdateVB(mesh) == M3G_FALSE) { + + /* We're sufficiently initialized at this point that the + * destructor can be called for cleaning up */ + + m3gDestroySkinnedMesh((Object *)mesh); + return M3G_FALSE; + } + return M3G_TRUE; +} + +/*---------------------------------------------------------------------- + * Virtual function table + *--------------------------------------------------------------------*/ + +static const NodeVFTable m3gvf_SkinnedMesh = { + { + { + m3gSkinnedMeshApplyAnimation, + m3gNodeIsCompatible, + m3gNodeUpdateProperty, + m3gSkinnedMeshDoGetReferences, + m3gSkinnedMeshFindID, + m3gSkinnedMeshDuplicate, + m3gDestroySkinnedMesh + } + }, + m3gNodeAlign, + m3gSkinnedMeshDoRender, + m3gSkinnedMeshGetBBox, + m3gSkinnedMeshRayIntersect, + m3gSkinnedMeshSetupRender, + m3gSkinnedMeshUpdateDuplicateReferences, + m3gSkinnedMeshValidate +}; + + +/*---------------------------------------------------------------------- + * Public API functions + *--------------------------------------------------------------------*/ + +/*! + * \brief Creates a SkinnedMesh object. + * + * \param interface M3G interface + * \param hVertices VertexBuffer object + * \param hTriangles array of IndexBuffer objects + * \param hAppearances array of Appearance objects + * \param trianglePatchCount number of submeshes + * \param hSkeleton Group containing the skeleton + * \retval SkinnedMesh new SkinnedMesh object + * \retval NULL SkinnedMesh creating failed + */ +M3G_API M3GSkinnedMesh m3gCreateSkinnedMesh(M3GInterface interface, + M3GVertexBuffer hVertices, + M3GIndexBuffer *hTriangles, + M3GAppearance *hAppearances, + M3Gint trianglePatchCount, + M3GGroup hSkeleton) +{ + Interface *m3g = (Interface *) interface; + M3G_VALIDATE_INTERFACE(m3g); + { + SkinnedMesh *mesh = NULL; + Group *skeleton = (Group *) hSkeleton; + if (skeleton == NULL) { + m3gRaiseError(m3g, M3G_NULL_POINTER); + return NULL; + } + if (skeleton->node.parent != NULL || + M3G_CLASS(skeleton) == M3G_CLASS_WORLD) { + m3gRaiseError(m3g, M3G_INVALID_VALUE); + return NULL; + } + + mesh = m3gAllocZ(m3g, sizeof(SkinnedMesh)); + if (mesh) { + if (!m3gInitSkinnedMesh(m3g, mesh, + hVertices, hTriangles, hAppearances, + trianglePatchCount, + hSkeleton)) { + m3gFree(m3g, mesh); + return NULL; + } + } + return (M3GSkinnedMesh)mesh; + } +} + +/*! + * \brief Add new weighted transformation (bone) to range of vertices + * + * + * \param handle SkinnedMesh object + * \param hNode bone to transform the vertices with + * \param weight weight of the bone + * \param firstVertex index to the first affected vertex + * \param numVertices number of affected vertices + */ +M3G_API void m3gAddTransform(M3GSkinnedMesh handle, + M3GNode hNode, + M3Gint weight, + M3Gint firstVertex, M3Gint numVertices) +{ + SkinnedMesh *mesh = (SkinnedMesh *)handle; + Node *boneNode = (Node *)hNode; + Interface *m3g = M3G_INTERFACE(mesh); + + M3Gint lastVertex = firstVertex + numVertices; + M3G_VALIDATE_OBJECT(mesh); + + /* Check for errors */ + + if (!boneNode) { + m3gRaiseError(m3g, M3G_NULL_POINTER); + return; + } + M3G_VALIDATE_OBJECT(boneNode); + if (!m3gIsChildOf((const Node*) mesh, boneNode) + || numVertices <= 0 || weight <= 0) { + m3gRaiseError(m3g, M3G_INVALID_VALUE); + return; + } + if (firstVertex < 0 || lastVertex > 65535) { + m3gRaiseError(m3g, M3G_INVALID_INDEX); + return; + } + + /* Make sure we have enough per-vertex data */ + + if (!m3gEnsureVertexCount(mesh, lastVertex)) { + return; /* out of memory */ + } + + /* Check whether we may need to increase the number of bone + * entries per vertex, or whether we're already maxed out */ + + if (mesh->bonesPerVertex < M3G_MAX_VERTEX_TRANSFORMS) { + + /* Scan the input vertex range to find the maximum number of + * transforms per vertex (with non-zero weights) already in + * use, then make sure we can fit one more */ + + int numBones = mesh->bonesPerVertex; + int maxBones = 0; + + int vertex; + for (vertex = firstVertex; vertex < lastVertex; ++vertex) { + int k; + for (k = numBones; k > 0; --k) { + if (mesh->boneWeights[k-1][vertex] > 0) { + maxBones = M3G_MAX(maxBones, k); + break; + } + } + } + if (!m3gEnsureBonesPerVertex(mesh, maxBones + 1)) { + return; /* out of memory */ + } + } + + /* Get a bone record for the bone node, and add the bone influence + * to all affected vertices */ + { + int i; + + M3Gint boneIndex = m3gBoneIndex(mesh, boneNode); + if (boneIndex < 0) { + return; /* out of memory */ + } + + for (i = firstVertex; i < lastVertex; i++) { + m3gAddInfluence(mesh, i, boneIndex, weight); + } + } + + /* Update the bone flag for the bone node and its parents up to + * the SkinnedMesh node */ + + while (boneNode != (Node*) mesh) { /* boneNode must be a child of ours */ + M3G_ASSERT(boneNode); + boneNode->hasBones = M3G_TRUE; + boneNode = boneNode->parent; + } +} + +/*! + * \brief Getter for skeleton. + * + * \param handle SkinnedMesh object + * \return Group object + */ +M3G_API M3GGroup m3gGetSkeleton(M3GSkinnedMesh handle) +{ + SkinnedMesh *mesh = (SkinnedMesh *)handle; + M3G_VALIDATE_OBJECT(mesh); + + return mesh->skeleton; +} + +/*! + * \brief Getter for bone transform. + * + * \param handle SkinnedMesh object + * \param hBone Bone + * \param transform Transform + */ +M3G_API void m3gGetBoneTransform(M3GSkinnedMesh handle, + M3GNode hBone, + M3GMatrix *transform) +{ + SkinnedMesh *mesh = (SkinnedMesh *)handle; + Node *node = (Node *)hBone; + M3Gint i; + M3Gint boneCount; + + M3G_VALIDATE_OBJECT(mesh); + M3G_VALIDATE_OBJECT(node); + + if (!m3gIsChildOf((Node*) mesh->skeleton, node)) { + m3gRaiseError(M3G_INTERFACE(mesh), M3G_INVALID_VALUE); + return; + } + + boneCount = m3gArraySize(&mesh->bones); + + for (i = 0; i < boneCount; ++i) { + Bone *bone = m3gGetArrayElement(&mesh->bones, i); + + if (bone->node == node) { + m3gCopyMatrix(transform, &bone->toBone); + break; + } + } +} + +/*! + * \brief Getter for bone vertices. + * + * \param handle SkinnedMesh object + * \param hBone Bone + * \param indices Influenced indices + * \param weights Weights + * \return Number of influenced vertices + */ +M3G_API M3Gint m3gGetBoneVertices(M3GSkinnedMesh handle, + M3GNode hBone, + M3Gint *indices, M3Gfloat *weights) +{ + SkinnedMesh *mesh = (SkinnedMesh *)handle; + Node *node = (Node *)hBone; + M3Gint boneIndex, boneCount, count = 0; + + M3G_VALIDATE_OBJECT(mesh); + M3G_VALIDATE_OBJECT(node); + + /* Check for errors */ + + if (!m3gIsChildOf((Node*) mesh->skeleton, node)) { + m3gRaiseError(M3G_INTERFACE(mesh), M3G_INVALID_VALUE); + return 0; + } + + /* Find the bone index corresponding to our bone node */ + + boneCount = m3gArraySize(&mesh->bones); + + for (boneIndex = 0; boneIndex < boneCount; ++boneIndex) { + Bone *bone = m3gGetArrayElement(&mesh->bones, boneIndex); + if (bone->node == node) { + break; + } + } + + /* Loop over the vertices, outputting index-weight pairs for each + * vertex influenced by the bone */ + + if (boneIndex < boneCount) { + M3Gint i, j; + + for (i = 0; i < mesh->weightedVertexCount; ++i) { + for (j = 0; j < mesh->bonesPerVertex; ++j) { + if (mesh->boneIndices[j][i] == boneIndex && mesh->boneWeights[j][i] > 0) { + if (indices != NULL && weights != NULL) { + M3Gint k, sum = 0; + for (k = 0; k < mesh->bonesPerVertex; ++k) { + sum += mesh->boneWeights[k][i]; + } + indices[count] = i; + if (sum != 0) { + weights[count] = ((M3Gfloat) mesh->boneWeights[j][i]) / sum; + } + else { + weights[count] = 0; + } + } + ++count; + } + } + } + } + return count; +} +