diff -r 000000000000 -r bde4ae8d615e os/graphics/m3g/m3gcore11/src/m3g_node.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/os/graphics/m3g/m3gcore11/src/m3g_node.c Fri Jun 15 03:10:57 2012 +0200 @@ -0,0 +1,1427 @@ +/* +* 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: Node implementation +* +*/ + + +/*! + * \internal + * \file + * \brief Node implementation + */ + +#ifndef M3G_CORE_INCLUDE +# error included by m3g_core.c; do not compile separately. +#endif + +#include "m3g_node.h" +#include "m3g_memory.h" +#include "m3g_animationtrack.h" +#include "m3g_skinnedmesh.h" +#include "m3g_tcache.h" +#include "m3g_transformable.h" + +#define TARGET_NONE 0 +#define TARGET_X_AXIS 1 +#define TARGET_Y_AXIS 2 +#define TARGET_Z_AXIS 3 +#define TARGET_ORIGIN 4 + +/*---------------------------------------------------------------------- + * Private functions + *--------------------------------------------------------------------*/ + +static M3Guint internalTarget(M3Genum target) +{ + switch (target) { + case M3G_NONE: + return TARGET_NONE; + case M3G_ORIGIN: + return TARGET_ORIGIN; + case M3G_X_AXIS: + return TARGET_X_AXIS; + case M3G_Y_AXIS: + return TARGET_Y_AXIS; + case M3G_Z_AXIS: + return TARGET_Z_AXIS; + default: + M3G_ASSERT(M3G_FALSE); + return TARGET_NONE; + } +} + +static M3Guint externalTarget(M3Genum target) +{ + switch (target) { + case TARGET_NONE: + return M3G_NONE; + case TARGET_ORIGIN: + return M3G_ORIGIN; + case TARGET_X_AXIS: + return M3G_X_AXIS; + case TARGET_Y_AXIS: + return M3G_Y_AXIS; + case TARGET_Z_AXIS: + return M3G_Z_AXIS; + default: + M3G_ASSERT(M3G_FALSE); + return M3G_NONE; + } +} + +/*---------------------------------------------------------------------- + * Constructor & destructor + *--------------------------------------------------------------------*/ + +/*! + * \internal + * \brief Initializes a Node object. See specification + * for default values. + * + * \param m3g M3G interface + * \param node Node object + * \param vfTable virtual function table + */ +static void m3gInitNode(Interface *m3g, Node *node, M3GClass classID) +{ + /* Node is derived from Transformable */ + m3gInitTransformable(&node->transformable, m3g, classID); + + /* Set default values */ + + node->enableBits = (NODE_RENDER_BIT|NODE_PICK_BIT); + node->alphaFactor = (1u << NODE_ALPHA_FACTOR_BITS) - 1; + node->scope = -1; + node->zTarget = TARGET_NONE; + node->yTarget = TARGET_NONE; +} + +/*! + * \internal + * \brief Destroys this Node object. + * + * \param obj Node object + */ +static void m3gDestroyNode(Object *obj) +{ + Node *node = (Node *) obj; + M3G_VALIDATE_OBJECT(node); + M3G_ASSERT(node->parent == NULL); + m3gDestroyTransformable((Object *) node); +} + +/*---------------------------------------------------------------------- + * Internal functions + *--------------------------------------------------------------------*/ + +/*! + * \internal + * \brief Checks if node is a child of the parent. + * + * \param parent assumed parent Node object + * \param child Node object to check + * \retval M3G_TRUE is a child + * \retval M3G_FALSE is not a child + */ +static M3Gbool m3gIsChildOf(const Node *parent, const Node *child) +{ + const Node *n; + + for (n = child; n != NULL; n = n->parent) { + if (n->parent == parent) return M3G_TRUE; + } + + return M3G_FALSE; +} + +/*! + * \internal + * \brief Executes the given function for each node in a subtree + * + * The function \c func is executed recursively in each branch, + * starting from the leaves. That is, the function is called for the + * children of each group before the group itself. + * + * \param node the node containing the subtree to process + * \param func pointer to the function to all for each node + * \param params pointer to function-dependent arguments to pass + * to each \c func invokation; this may be e.g. a structure + * modified by \c func + * + * \return The return value of the top-level call to \c func + */ +static void m3gForSubtree(Node *node, NodeFuncPtr func, void *params) +{ + M3GClass nodeClass; + M3G_VALIDATE_OBJECT(node); + + /* Recurse into the children first */ + + nodeClass = M3G_CLASS(node); + + if (nodeClass == M3G_CLASS_SKINNED_MESH) { + m3gForSubtree((Node*)((SkinnedMesh*)node)->skeleton, func, params); + } + else if (nodeClass == M3G_CLASS_GROUP || + nodeClass == M3G_CLASS_WORLD) { + Group *group = (Group*) node; + Node *child = group->firstChild; + if (child) { + do { + Node *next = child->right; + m3gForSubtree(child, func, params); + child = next; + } while (child != group->firstChild); + } + } + + /* Execute function on self */ + + (*func)(node, params); +} + +/*! + * \internal + * \brief Overloaded Object3D method + * + * \param property animation property + * \retval M3G_TRUE property supported + * \retval M3G_FALSE property not supported + */ +static M3Gbool m3gNodeIsCompatible(M3Gint property) +{ + switch (property) { + case M3G_ANIM_ALPHA: + case M3G_ANIM_PICKABILITY: + case M3G_ANIM_VISIBILITY: + return M3G_TRUE; + default: + return m3gTransformableIsCompatible(property); + } +} + +/*! + * \internal + * \brief Overloaded Object3D method + * + * \param self Node object + * \param property animation property + * \param valueSize size of value array + * \param value value array + */ +static void m3gNodeUpdateProperty(Object *self, + M3Gint property, + M3Gint valueSize, + const M3Gfloat *value) +{ + Node *node = (Node *)self; + M3G_VALIDATE_OBJECT(node); + M3G_ASSERT_PTR(value); + + switch (property) { + case M3G_ANIM_ALPHA: + M3G_ASSERT(valueSize >= 1); + node->alphaFactor = + m3gRoundToInt( + m3gMul(m3gClampFloat(value[0], 0.f, 1.f), + (float)((1 << NODE_ALPHA_FACTOR_BITS) - 1))); + break; + case M3G_ANIM_PICKABILITY: + M3G_ASSERT(valueSize >= 1); + node->enableBits &= ~NODE_PICK_BIT; + if (value[0] >= 0.5f) { + node->enableBits |= NODE_PICK_BIT; + } + break; + case M3G_ANIM_VISIBILITY: + M3G_ASSERT(valueSize >= 1); + node->enableBits &= ~NODE_RENDER_BIT; + if (value[0] >= 0.5f) { + node->enableBits |= NODE_RENDER_BIT; + } + break; + default: + m3gTransformableUpdateProperty(self, property, valueSize, value); + } +} + +/*! + * \internal + * \brief Overloaded Object3D method + * + * \param originalObj original Node object + * \param cloneObj pointer to cloned Node object + * \param pairs array for all object-duplicate pairs + * \param numPairs number of pairs + */ +static M3Gbool m3gNodeDuplicate(const Object *originalObj, + Object **cloneObj, + Object **pairs, + M3Gint *numPairs) +{ + Node *original = (Node *)originalObj; + Node *clone = (Node *)*cloneObj; + M3G_ASSERT_PTR(*cloneObj); /* abstract class, must be derived */ + + /* Duplicate base class data */ + + if (!m3gTransformableDuplicate(originalObj, cloneObj, pairs, numPairs)) { + return M3G_FALSE; + } + + /* Duplicate our own data */ + + clone->zReference = original->zReference; + clone->yReference = original->yReference; + clone->zTarget = original->zTarget; + clone->yTarget = original->yTarget; + clone->enableBits = original->enableBits; + clone->alphaFactor = original->alphaFactor; + clone->scope = original->scope; + clone->hasBones = original->hasBones; + clone->hasRenderables = original->hasRenderables; + + return M3G_TRUE; +} + +/*! + * \internal + * \brief Find corresponding duplicate for a Node + * + * \param node Node object + * \param pairs array for all object-duplicate pairs + * \param numPairs number of pairs + */ +static Node *m3gGetDuplicatedInstance(Node *node, Object **pairs, M3Gint numPairs) +{ + M3Gint i; + for (i = 0; i < numPairs; i++) + if (pairs[i * 2] == (Object *)node) + return (Node *)pairs[i * 2 + 1]; + return NULL; +} + +/*! + * \internal + * \brief Updates references of the duplicate object. + * + * When objects are duplicated scenegraph references + * must be updated to equivalent duplicated references. + * This function is overloaded by objects that have + * references that has to be updated. + * + * \param self Node object + * \param pairs array for all object-duplicate pairs + * \param numPairs number of pairs + */ +static void m3gNodeUpdateDuplicateReferences(Node *self, Object **pairs, M3Gint numPairs) +{ + if (self->zTarget != TARGET_NONE && self->zReference != NULL) { + Node *duplicatedInstance = m3gGetDuplicatedInstance(self, pairs, numPairs); + Node *duplicatedRef = m3gGetDuplicatedInstance(self->zReference, pairs, numPairs); + if (duplicatedRef != NULL + && m3gIsChildOf(m3gGetRoot(duplicatedInstance), duplicatedRef)) { + duplicatedInstance->zReference = duplicatedRef; + } + } + if (self->yTarget != TARGET_NONE && self->yReference != NULL) { + Node *duplicatedInstance = m3gGetDuplicatedInstance(self, pairs, numPairs); + Node *duplicatedRef = m3gGetDuplicatedInstance(self->yReference, pairs, numPairs); + if (duplicatedRef != NULL + && m3gIsChildOf(m3gGetRoot(duplicatedInstance), duplicatedRef)) { + duplicatedInstance->yReference = duplicatedRef; + } + } +} + +/*! + * \internal + * \brief Gets size of the subtree + * + * \param node Node object + * \param numRef number of references + */ +static void m3gDoGetSubtreeSize(Node *node, void *numRef) +{ + M3Gint *num = (M3Gint *)numRef; + M3G_UNREF(node); + (*num)++; +} + +/*! + * \internal + * \brief Default function for non-pickable objects + * + * \param self Camera object + * \param mask pick scope mask + * \param ray pick ray + * \param ri RayIntersection object + * \param toGroup transform to originating group + * \retval M3G_TRUE always return success + */ +static M3Gbool m3gNodeRayIntersect(Node *self, + M3Gint mask, + M3Gfloat *ray, + RayIntersection *ri, + Matrix *toGroup) +{ + M3G_UNREF(self); + M3G_UNREF(mask); + M3G_UNREF(ray); + M3G_UNREF(ri); + M3G_UNREF(toGroup); + + return M3G_TRUE; +} + +/*! + * \internal + * \brief Computes the bounding box for this node + * + * \param self node pointer + * \param bbox bounding box structure filled in for non-zero return values + * + * \return The "yield" factor for the node, i.e. the approximate + * rendering cost of the node \em including any internal bounding box + * checks; the yield factor is used to estimate the benefit of adding + * enclosing bounding boxes at higher levels in the scene tree + */ +static M3Gint m3gNodeGetBBox(Node *self, AABB *bbox) +{ + M3G_UNREF(self); + M3G_UNREF(bbox); + return 0; +} + +/*! + * \internal + * \brief Updates the bounding box for this node + */ +static M3Gbool m3gNodeValidate(Node *self, M3Gbitmask stateBits, M3Gint scope) +{ + M3G_UNREF(stateBits); + M3G_UNREF(scope); + + /* Invalidate parent state in case we've encountered a previously + * disabled node, then reset the dirty bits */ + + if (self->dirtyBits && self->parent) { + m3gInvalidateNode(self->parent, self->dirtyBits); + } + self->dirtyBits = 0; + return M3G_TRUE; +} + +/*! + * \internal + * \brief Gets a vector according to alignment target + * and transforms it with a given transform. + * + * \param target alignment target + * \param transform transform to be applied + * \param out vector to fill in + */ +static void m3gTransformAlignmentTarget(M3Genum target, + const Matrix *transform, + Vec4 *out) +{ + switch (target) { + case TARGET_ORIGIN: + *out = Vec4_ORIGIN; + break; + case TARGET_X_AXIS: + *out = Vec4_X_AXIS; + break; + case TARGET_Y_AXIS: + *out = Vec4_Y_AXIS; + break; + case TARGET_Z_AXIS: + *out = Vec4_Z_AXIS; + break; + default: + M3G_ASSERT(M3G_FALSE); + } + + m3gTransformVec4(transform, out); +} + +/*! + * \internal + * \brief Computes a single alignment rotation for a node. + * + * \param node Node object + * \param srcAxis source axis + * \param targetNode Node object + * \param targetAxisName target axis name + * \param constraint constraint + */ +static M3Gbool m3gComputeAlignmentRotation(Node *node, + const Vec3 *srcAxis, + const Node *targetNode, + M3Genum targetAxisName, + M3Genum constraint) +{ + const Node *parent = node->parent; + Matrix transform; + Vec4 targetAxis; + + M3G_VALIDATE_OBJECT(parent); + M3G_ASSERT(constraint == TARGET_NONE || constraint == TARGET_Z_AXIS); + + /* Get the transformation from the reference target node to the + * current node, omitting all components except translation. + * Rotation is also applied if this is a constrained alignment. */ + { + const Transformable *tf = &node->transformable; + + if (!m3gGetTransformTo((M3GNode) targetNode, (M3GNode) parent, + &transform)) { + return M3G_FALSE; + } + m3gPreTranslateMatrix(&transform, -tf->tx, -tf->ty, -tf->tz); + + if (constraint != TARGET_NONE) { + Quat rot = tf->orientation; + rot.w = -rot.w; + m3gPreRotateMatrixQuat(&transform, &rot); + } + } + + m3gTransformAlignmentTarget(targetAxisName, &transform, &targetAxis); + + /* Apply the Z constraint if enabled; this is done by simply + * zeroing the Z component of the target vector. If the X and Y + * alone don't span a non-zero vector, just exit as there's + * nothing defined to rotate about. */ + + if (constraint == TARGET_Z_AXIS) { + M3Gfloat norm = m3gAdd(m3gMul(targetAxis.x, targetAxis.x), + m3gMul(targetAxis.y, targetAxis.y)); + if (norm < 1.0e-5f) { + return M3G_TRUE; + } + norm = m3gRcpSqrt(norm); + + targetAxis.x = m3gMul(targetAxis.x, norm); + targetAxis.y = m3gMul(targetAxis.y, norm); + targetAxis.z = 0.0f; + + M3G_ASSERT(srcAxis->z == 0.0f); + } + else { + m3gNormalizeVec3((Vec3*)&targetAxis); /* srcAxis will be unit length */ + } + + if (constraint != TARGET_NONE) { + Quat rot; + m3gSetQuatRotation(&rot, srcAxis, (const Vec3*) &targetAxis); + m3gMulQuat(&node->transformable.orientation, &rot); + } + else { + m3gSetQuatRotation(&node->transformable.orientation, + srcAxis, (const Vec3*) &targetAxis); + } + + /* Invalidate transformations and bounding boxes after setting + * node orientation */ + + m3gInvalidateTransformable((Transformable*)node); + + return M3G_TRUE; +} + +/*! + * \internal + * \brief Computes alignment for a single node. + * + * \param node Node object + * \param refNode Node object + * \retval M3G_TRUE alignment ok + * \retval M3G_FALSE alignment failed + */ +static M3Gbool m3gComputeAlignment(Node *node, const Node *refNode) +{ + const Node *root = m3gGetRoot(node); + const Node *zRef = node->zReference; + const Node *yRef = node->yReference; + const M3Genum zTarget = node->zTarget; + const M3Genum yTarget = node->yTarget; + M3G_VALIDATE_OBJECT(node); + + /* Quick exit if nothing to do */ + + if (zTarget == TARGET_NONE && yTarget == TARGET_NONE) { + return M3G_TRUE; + } + + /* Check scene graph state */ + + if (zRef != NULL && (m3gIsChildOf(node, zRef) || m3gGetRoot(zRef) != root)) { + m3gRaiseError(M3G_INTERFACE(node), M3G_INVALID_OPERATION); + return M3G_FALSE; + } + if (yRef != NULL && (m3gIsChildOf(node, yRef) || m3gGetRoot(yRef) != root)) { + m3gRaiseError(M3G_INTERFACE(node), M3G_INVALID_OPERATION); + return M3G_FALSE; + } + + /* Compute the alignment rotations for Z and Y */ + { + if (node->zTarget != TARGET_NONE) { + if (zRef == NULL && refNode == node) { + m3gRaiseError(M3G_INTERFACE(node), M3G_INVALID_OPERATION); + return M3G_FALSE; + } + if (!m3gComputeAlignmentRotation( + node, + (const Vec3*) &Vec4_Z_AXIS, + zRef != NULL ? zRef : refNode, + zTarget, + TARGET_NONE)) { + return M3G_FALSE; + } + } + if (node->yTarget != TARGET_NONE) { + if (yRef == NULL && refNode == node) { + m3gRaiseError(M3G_INTERFACE(node), M3G_INVALID_OPERATION); + return M3G_FALSE; + } + if (!m3gComputeAlignmentRotation( + node, + (const Vec3*) &Vec4_Y_AXIS, + yRef != NULL ? yRef : refNode, + yTarget, + zTarget != TARGET_NONE ? TARGET_Z_AXIS : TARGET_NONE)) { + return M3G_FALSE; + } + } + } + return M3G_TRUE; +} + +/*! + * \internal + * \brief Gets the transformation to an ancestor node + */ +static void m3gGetTransformUpPath(const Node *node, const Node *ancestor, Matrix *transform) +{ + M3G_ASSERT(node); + + if (node == ancestor) { + m3gIdentityMatrix(transform); + } + else { + TCache *tc; + M3G_ASSERT(!ancestor || m3gIsChildOf(ancestor, node)); + + /* Look for a cached path */ + + tc = m3gGetTransformCache(M3G_INTERFACE(node)); + if (m3gGetCachedPath(tc, node, ancestor, transform)) { + return; + } + + /* No dice -- do a recursive search and cache the result */ + + if (node->parent == ancestor) { + m3gGetCompositeNodeTransform(node, transform); + } + else { + m3gGetTransformUpPath(node->parent, ancestor, transform); + { + Matrix mtx; + m3gGetCompositeNodeTransform(node, &mtx); + m3gMulMatrix(transform, &mtx); + } + } + m3gCachePath(tc, node, ancestor, transform); + } +} + +/*! + * \internal + * \brief Gets depth of a node in the scenegraph. + * + * \param node Node object + * \return Depth of the node + */ +static M3Gint m3gGetDepth(const Node *node) +{ + const Node *n = node; + M3Gint depth = 0; + + while (n->parent != NULL) { + n = n->parent; + depth++; + } + + return depth; +} + +/*! + * \internal + * \brief Gets root of a node in the scenegraph. + * + * \param node Node object + * \return root Node object + */ +static Node *m3gGetRoot(const Node *node) +{ + const Node *n = node; + + while (n->parent != NULL) { + n = n->parent; + } + + return (Node *)n; +} + +/*! + * \internal + * \brief Gets total alpha factor. + * + * \param node Node object + * \param root root Node object + */ +static M3Guint m3gGetTotalAlphaFactor(Node *node, const Node *root) +{ + const Node *n = node; + M3Guint f = node->alphaFactor; + + while (n->parent != NULL && n != root) { + n = n->parent; + f = ((f + 1) * n->alphaFactor) >> NODE_ALPHA_FACTOR_BITS; + } + return f; +} + +/*! + * \internal + * \brief Checks if node is enabled for rendering from the root. + * + * \param node Node object + * \param root root Node object + * \retval M3G_TRUE node is visible + * \retval M3G_FALSE node is not visible + */ +static M3Gbool m3gHasEnabledPath(const Node *node, const Node *root) +{ + const Node *n; + + for (n = node; n != NULL; n = n->parent) { + if (!(n->enableBits & NODE_RENDER_BIT)) { + return M3G_FALSE; + } + if (n == root) { + break; + } + } + + return M3G_TRUE; +} + +/*! + * \internal + * \brief Checks if node is pickable from the root. + * + * \param node Node object + * \param root root Node object + * \retval M3G_TRUE node is pickable + * \retval M3G_FALSE node is not pickable + */ +static M3Gbool m3gHasPickablePath(const Node *node, const Node *root) +{ + const Node *n; + + for (n = node; n != NULL; n = n->parent) { + if (!(n->enableBits & NODE_PICK_BIT)) { + return M3G_FALSE; + } + if (n == root) { + break; + } + } + + return M3G_TRUE; +} + +#if defined(M3G_ENABLE_VF_CULLING) +/*! + * \brief Invalidates the bounding box hierarchy from a node upwards + */ +static void m3gInvalidateNode(Node *node, M3Gbitmask flags) +{ + Interface *m3g = M3G_INTERFACE(node); + M3G_BEGIN_PROFILE(m3g, M3G_PROFILE_VFC_UPDATE); + + while (node && (node->dirtyBits & flags) != flags) { + node->dirtyBits |= flags; + node = node->parent; + } + M3G_END_PROFILE(m3g, M3G_PROFILE_VFC_UPDATE); +} +#endif /*M3G_ENABLE_VF_CULLING*/ + +/*! + * \internal + * \brief Aligns a node. + * + * \param node Node object + * \param ref reference Node object + * + * \retval M3G_TRUE continue align + * \retval M3G_FALSE abort align + */ +static M3Gbool m3gNodeAlign(Node *node, const Node *ref) +{ + if (ref == NULL) { + return m3gComputeAlignment(node, node); + } + else { + M3G_VALIDATE_OBJECT(ref); + return m3gComputeAlignment(node, ref); + } +} + +/*! + * \internal + * \brief Updates node counters when moving nodes around + */ +static void m3gUpdateNodeCounters(Node *node, + M3Gint nonCullableChange, + M3Gint renderableChange) +{ + Interface *m3g = M3G_INTERFACE(node); + M3Gbool hasRenderables = (renderableChange > 0); + M3G_BEGIN_PROFILE(m3g, M3G_PROFILE_VFC_UPDATE); + while (node) { + M3GClass nodeClass = M3G_CLASS(node); + if (nodeClass == M3G_CLASS_GROUP || nodeClass == M3G_CLASS_WORLD) { + Group *g = (Group *) node; + g->numNonCullables = (M3Gushort)(g->numNonCullables + nonCullableChange); + g->numRenderables = (M3Gushort)(g->numRenderables + renderableChange); + hasRenderables = (g->numRenderables > 0); + } + node->hasRenderables = hasRenderables; + node = node->parent; + } + M3G_END_PROFILE(m3g, M3G_PROFILE_VFC_UPDATE); +} + +/*! + * \internal + * \brief Sets the parent link of this node to a new value + * + * Relevant reference counts are updated accordingly, so note that + * setting the parent to NULL may lead to either the node or the + * parent itself being destroyed. + * + * \param node Node object + * \param parent parent Node object + */ +static void m3gSetParent(Node *node, Node *parent) +{ + M3GClass nodeClass; + M3Gint nonCullableChange = 0, renderableChange = 0; + M3G_VALIDATE_OBJECT(node); + + /* Determine the number of various kinds of nodes being moved around */ + + M3G_BEGIN_PROFILE(M3G_INTERFACE(node), M3G_PROFILE_VFC_UPDATE); + nodeClass = M3G_CLASS(node); + switch (nodeClass) { + case M3G_CLASS_GROUP: + { + const Group *g = (const Group *) node; + nonCullableChange = g->numNonCullables; + renderableChange = g->numRenderables; + break; + } + case M3G_CLASS_SPRITE: + renderableChange = 1; + if (m3gIsScaledSprite((M3GSprite) node)) { + break; + } + /* conditional fall-through! */ + case M3G_CLASS_LIGHT: + nonCullableChange = 1; + break; + case M3G_CLASS_SKINNED_MESH: + { + const SkinnedMesh *mesh = (const SkinnedMesh *) node; + nonCullableChange += mesh->skeleton->numNonCullables; + renderableChange += mesh->skeleton->numRenderables + 1; + break; + } + case M3G_CLASS_MESH: + case M3G_CLASS_MORPHING_MESH: + renderableChange = 1; + break; + default: + ; + } + M3G_END_PROFILE(M3G_INTERFACE(node), M3G_PROFILE_VFC_UPDATE); + + /* Invalidate any cached transformation paths through this node + * *before* we move the node */ + + m3gInvalidateCachedPaths(m3gGetTransformCache(M3G_INTERFACE(node)), node); + + /* Update bookkeeping for the old parent tree */ + + if (node->parent) { + m3gUpdateNodeCounters(node->parent, + -nonCullableChange, -renderableChange); + if (renderableChange) { + m3gInvalidateNode(node->parent, NODE_BBOX_BIT|NODE_TRANSFORMS_BIT); + } + } + + /* Change the parent link */ + + if (node->parent == NULL && parent != NULL) { + node->parent = parent; + m3gAddRef((Object *) node); + } + else if (node->parent != NULL && parent == NULL) { + node->parent = parent; + m3gDeleteRef((Object *) node); + } + + /* Update bookkeeping for the new parent tree */ + + if (parent) { + M3Gbitmask dirtyBits = node->dirtyBits; + if (renderableChange) { + dirtyBits |= NODE_BBOX_BIT; + } + if (node->hasBones) { + dirtyBits |= NODE_TRANSFORMS_BIT; + } + m3gUpdateNodeCounters(parent, nonCullableChange, renderableChange); + m3gInvalidateNode(parent, dirtyBits); + } +} + +/*! + * \brief Computes the "near" and "far" box vertices + * for plane testing + */ +static M3G_INLINE void m3gGetTestPoints(const Vec3 *planeNormal, + const AABB *box, + Vec3 *vNear, Vec3 *vFar) +{ + const M3Gfloat *fNormal = (const M3Gfloat*) planeNormal; + M3Gfloat *fNear = (M3Gfloat*) vNear; + M3Gfloat *fFar = (M3Gfloat*) vFar; + int i; + + for (i = 0; i < 3; ++i) { + M3Gfloat n = *fNormal++; + if (IS_NEGATIVE(n)) { + *fNear++ = box->max[i]; + *fFar++ = box->min[i]; + } + else { + *fNear++ = box->min[i]; + *fFar++ = box->max[i]; + } + } +} + +#if defined(M3G_ENABLE_VF_CULLING) +/*! + * \internal + * \brief Update the frustum culling mask for one level of an AABB hierarchy + * + * \param s the current traversal state + * \param bbox the bounding box to check against + */ +static void m3gUpdateCullingMask(SetupRenderState *s, + const Camera *cam, const AABB *bbox) +{ + M3Gbitmask cullMask = s->cullMask; + M3G_BEGIN_PROFILE(M3G_INTERFACE(cam), M3G_PROFILE_VFC_TEST); + + /* First, check whether any planes are previously marked as + * intersecting */ + + if (cullMask & CULLMASK_ALL) { + + /* We need to do some culling, so let's get the planes and the + * transformation matrix; note that the "toCamera" matrix is + * the inverse of the camera-to-node matrix, so we only need + * to transpose */ + + M3Gbitmask planeMask; + const Vec4 *camPlanes = m3gFrustumPlanes(cam); + + Matrix t; + m3gMatrixTranspose(&t, &s->toCamera); + + /* Loop over the active frustum planes, testing the ones we've + * previously intersected with */ + + planeMask = CULLMASK_INTERSECTS; + while (planeMask <= cullMask) { + if (cullMask & planeMask) { + + /* Transform the respective frustum plane into the node + * local space our AABB is in */ + + Vec4 plane; + plane = *camPlanes++; + m3gTransformVec4(&t, &plane); + + /* Test the AABB against the plane and update the mask + * based on the result */ + + m3gIncStat(M3G_INTERFACE(cam), M3G_STAT_CULLING_TESTS, 1); + { + /* Get the "near" and "far" corner points of the box */ + + const Vec3* normal = (Vec3*) &plane; + Vec3 vNear, vFar; + m3gGetTestPoints(normal, bbox, &vNear, &vFar); + + /* Our normals point inside, so flip this */ + + plane.w = m3gNegate(plane.w); + + /* "Far" point behind plane? */ + + if (m3gDot3(normal, &vFar) < plane.w) { + /* All outside, no need to test further! */ + cullMask = 0; + break; + } + + /* "Near" point in front of plane? */ + + if (m3gDot3(normal, &vNear) > plane.w) { + cullMask &= ~planeMask; + cullMask |= planeMask >> 1; /* intersects->inside */ + } + } + } + planeMask <<= 2; /* next plane */ + } + s->cullMask = cullMask; /* write the output mask */ + } + M3G_END_PROFILE(M3G_INTERFACE(cam), M3G_PROFILE_VFC_TEST); +} +#endif /*M3G_ENABLE_VF_CULLING*/ + +/*---------------------------------------------------------------------- + * Public API functions + *--------------------------------------------------------------------*/ + +/*! + * \brief Gets transform from node to another. + * + * \param handle Node object + * \param hTarget target Node object + * \param transform transform to fill in + * \retval M3G_TRUE success + * \retval M3G_FALSE failed + */ +M3G_API M3Gbool m3gGetTransformTo(M3GNode handle, + M3GNode hTarget, + M3GMatrix *transform) +{ + const Node *node = (Node *) handle; + const Node *target = (Node *) hTarget; + TCache *tc; + + M3G_VALIDATE_OBJECT(node); + M3G_BEGIN_PROFILE(M3G_INTERFACE(node), M3G_PROFILE_TRANSFORM_TO); + + /* Look for quick exits first */ + + tc = m3gGetTransformCache(M3G_INTERFACE(node)); + + if (node == target) { + m3gIdentityMatrix(transform); + M3G_END_PROFILE(M3G_INTERFACE(node), M3G_PROFILE_TRANSFORM_TO); + return M3G_TRUE; + } + else if (m3gGetCachedPath(tc, node, target, transform)) { + M3G_END_PROFILE(M3G_INTERFACE(node), M3G_PROFILE_TRANSFORM_TO); + return M3G_TRUE; + } + else { + + /* No luck, must recompute the whole thing -- begin by finding + * a common ancestor node for the pivot point of the path */ + + const Node *pivot = NULL; + { + const Node *s = node; + const Node *t = target; + + /* First traverse to the same depth */ + { + int sd = m3gGetDepth(s); + int td = m3gGetDepth(t); + + while (sd > td) { + s = s->parent; + --sd; + } + while (td > sd) { + t = t->parent; + --td; + } + } + + /* Then traverse until we reach a common node or run out of + * ancestors in both branches, meaning there is no path + * between the nodes */ + + while (s != t) { + s = s->parent; + t = t->parent; + } + pivot = s; + } + if (!pivot) { + M3G_END_PROFILE(M3G_INTERFACE(node), M3G_PROFILE_TRANSFORM_TO); + return M3G_FALSE; + } + + /* Now, fetch the transformations for both branches and + * combine into the complete transformation; optimize by + * skipping most of that altogether for paths where the target + * node is the topmost node of the path */ + + if (pivot != target) { + Matrix targetPath; + Matrix sourcePath; + + /* Look for a cached version of the to-target path to + * avoid the inversion if possible */ + + if (!m3gGetCachedPath(tc, pivot, target, &targetPath)) { + m3gGetTransformUpPath(target, pivot, &targetPath); + + /* Invert the target-side path since we want the + * downstream transformation for that one */ + + M3G_BEGIN_PROFILE(M3G_INTERFACE(node), M3G_PROFILE_TRANSFORM_INVERT); + if (!m3gInvertMatrix(&targetPath)) { + M3G_END_PROFILE(M3G_INTERFACE(node), M3G_PROFILE_TRANSFORM_TO); + m3gRaiseError(M3G_INTERFACE(node), M3G_ARITHMETIC_ERROR); + return M3G_FALSE; + } + + /* Cache the inverse for future use */ + m3gCachePath(tc, pivot, target, &targetPath); + } + + M3G_ASSERT(m3gIsWUnity(&targetPath)); + + /* Paste in the from-source path to get the complete + * transformation for the path */ + + if (pivot != node) { + m3gGetTransformUpPath(node, pivot, &sourcePath); + + M3G_END_PROFILE(M3G_INTERFACE(node), M3G_PROFILE_TRANSFORM_INVERT); + m3gRightMulMatrix(&targetPath, &sourcePath); + m3gCopyMatrix(transform, &targetPath); + M3G_ASSERT(m3gIsWUnity(transform)); + + /* Cache the combined result for future use */ + m3gCachePath(tc, node, target, transform); + } + else { + *transform = targetPath; + } + } + else { + /* For many cases, we only need this upstream path */ + m3gGetTransformUpPath(node, pivot, transform); + } + + M3G_END_PROFILE(M3G_INTERFACE(node), M3G_PROFILE_TRANSFORM_TO); + return M3G_TRUE; + } +} + +/*! + * \brief Sets alignment targets. + * + * \param handle Node object + * \param hZReference Z target Node object + * \param zTarget Z target type + * \param hYReference Y target Node object + * \param yTarget Y target type + */ +M3G_API void m3gSetAlignment(M3GNode handle, + M3GNode hZReference, M3Gint zTarget, + M3GNode hYReference, M3Gint yTarget) +{ + Node *node = (Node *) handle; + Node *zReference = (Node *) hZReference; + Node *yReference = (Node *) hYReference; + M3G_VALIDATE_OBJECT(node); + + /* Check for errors */ + if (!m3gInRange(zTarget, M3G_NONE, M3G_Z_AXIS) || + !m3gInRange(yTarget, M3G_NONE, M3G_Z_AXIS)) { + m3gRaiseError(M3G_INTERFACE(node), M3G_INVALID_VALUE); + return; + } + + if (zReference == node || yReference == node) { + m3gRaiseError(M3G_INTERFACE(node), M3G_INVALID_VALUE); + return; + } + + if (zReference == yReference && zTarget == yTarget && zTarget != M3G_NONE) { + m3gRaiseError(M3G_INTERFACE(node), M3G_INVALID_VALUE); + return; + } + + node->zReference = (zTarget != M3G_NONE) ? zReference : NULL; + node->yReference = (yTarget != M3G_NONE) ? yReference : NULL; + node->zTarget = internalTarget(zTarget); + node->yTarget = internalTarget(yTarget); +} + +/*! + * \brief Aligns a node. + * + * \param hNode Node object + * \param hRef reference Node object + */ +M3G_API void m3gAlignNode(M3GNode hNode, M3GNode hRef) +{ + Node *node = (Node *)hNode; + const Node *ref = (const Node *)hRef; + M3G_VALIDATE_OBJECT(node); + + if (ref != NULL && (m3gGetRoot(node) != m3gGetRoot(ref))) { + m3gRaiseError(M3G_INTERFACE(node), M3G_INVALID_VALUE); + } + else { + M3G_VFUNC(Node, node, align)(node, !ref ? node : ref); + } +} + +/*! + * \brief Sets node alpha factor. + * + * \param handle Node object + * \param alphaFactor node alpha factor + */ +M3G_API void m3gSetAlphaFactor(M3GNode handle, M3Gfloat alphaFactor) +{ + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + if (alphaFactor >= 0.f && alphaFactor <= 1.0f) { + node->alphaFactor = (M3Guint) + m3gRoundToInt(m3gMul(alphaFactor, + (1 << NODE_ALPHA_FACTOR_BITS) - 1)); + } + else { + m3gRaiseError(M3G_INTERFACE(node), M3G_INVALID_VALUE); + } +} + + +/*! + * \brief Gets node alpha factor. + * + * \param handle Node object + * \return node alpha factor + */ +M3G_API M3Gfloat m3gGetAlphaFactor(M3GNode handle) +{ + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + return m3gMul((M3Gfloat) node->alphaFactor, + 1.f / ((1 << NODE_ALPHA_FACTOR_BITS) - 1)); +} + +/*! + * \brief Sets node redering or picking enable flag. + * + * \param handle Node object + * \param which which flag to enable + * \arg M3G_SETGET_RENDERING + * \arg M3G_SETGET_PICKING + * \param enable enable flag + */ +M3G_API void m3gEnable(M3GNode handle, M3Gint which, M3Gbool enable) +{ + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + switch (which) { + case M3G_SETGET_RENDERING: + node->enableBits &= ~NODE_RENDER_BIT; + if (enable) { + node->enableBits |= NODE_RENDER_BIT; + } + break; + case M3G_SETGET_PICKING: + default: + node->enableBits &= ~NODE_PICK_BIT; + if (enable) { + node->enableBits |= NODE_PICK_BIT; + } + break; + } +} + +/*! + * \brief Gets node redering or picking enable flag. + * + * \param handle Node object + * \param which which flag to return + * \arg M3G_SETGET_RENDERING + * \arg M3G_SETGET_PICKING + * \return enable flag + */ +M3G_API M3Gint m3gIsEnabled(M3GNode handle, M3Gint which) +{ + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + switch(which) { + case M3G_SETGET_RENDERING: + return (node->enableBits & NODE_RENDER_BIT) != 0; + case M3G_SETGET_PICKING: + default: + return (node->enableBits & NODE_PICK_BIT) != 0; + } +} + +/*! + * \brief Sets node scope. + * + * \param handle Node object + * \param id node scope id + */ +M3G_API void m3gSetScope(M3GNode handle, M3Gint id) +{ + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + node->scope = id; +} + +/*! + * \brief Gets node scope. + * + * \param handle Node object + * \return node scope + */ +M3G_API M3Gint m3gGetScope(M3GNode handle) +{ + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + return node->scope; +} + +/*! + * \brief Gets node parent. + * + * \param handle Node object + * \return parent Node object + */ +M3G_API M3GNode m3gGetParent(M3GNode handle) +{ + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + return node->parent; +} + +/*! + * \brief Gets node alignment Z reference. + * + * \param handle Node object + * \return Z reference Node object + */ +M3G_API M3GNode m3gGetZRef(M3GNode handle) +{ + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + return node->zReference; +} + +/*! + * \brief Gets node alignment Y reference. + * + * \param handle Node object + * \return Y reference Node object + */ +M3G_API M3GNode m3gGetYRef(M3GNode handle) +{ + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + return node->yReference; +} + +/*! + * \brief Gets node alignment target + * + * \param handle Node object + * \param axis axis + * \return alignment target + */ +M3G_API M3Gint m3gGetAlignmentTarget(M3GNode handle, M3Gint axis) +{ + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + switch (axis) { + case M3G_Y_AXIS: return externalTarget(node->yTarget); + case M3G_Z_AXIS: return externalTarget(node->zTarget); + default: + m3gRaiseError(M3G_INTERFACE(node), M3G_INVALID_VALUE); + return 0; + } +} + +/*! + * \brief Gets node subtree size. + * + * \param handle Node object + * \return subtree size + */ +M3G_API M3Gint m3gGetSubtreeSize(M3GNode handle) +{ + M3Gint numRef = 0; + Node *node = (Node *) handle; + M3G_VALIDATE_OBJECT(node); + + m3gForSubtree(node, m3gDoGetSubtreeSize, (void *)&numRef); + return numRef; +} + +#undef TARGET_ORIGIN +#undef TARGET_Z_AXIS +#undef TARGET_Y_AXIS +#undef TARGET_X_AXIS +#undef TARGET_NONE +