m3g/m3gcore11/src/m3g_node.c
author William Roberts <williamr@symbian.org>
Fri, 23 Jul 2010 11:46:39 +0100
changeset 126 0ee22b620a47
parent 0 5d03bc08d59c
permissions -rw-r--r--
Use buildrom ABI_DIR\BUILD_DIR conventions in the minigui .oby files

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