m3g/m3gcore11/src/m3g_node.c
author Gareth Stockwell <gareth.stockwell@accenture.com>
Fri, 05 Nov 2010 17:31:20 +0000
branchbug235_bringup_0
changeset 215 097e92a68d68
parent 0 5d03bc08d59c
permissions -rw-r--r--
Added GLES 1.x spinning cube-rendering code to eglbringuptest The coordinate, color and index data are uploaded to server-side buffers by the CGLES1Cube::KhrSetup function. CGLES1Cube::KhrPaint just sets the view matrix and issues a draw command. Which demo to display can be selected by passing its name on the command line, e.g. eglbringuptest vgline eglbringuptest gles1cube If no name is provided, the application defaults to vgline.

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