/*
* 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;
}