/*************************************************************************
 *                                                                       *
 * Open Dynamics Engine, Copyright (C) 2001-2003 Russell L. Smith.       *
 * All rights reserved.  Email: russ@q12.org   Web: www.q12.org          *
 *                                                                       *
 * This library is free software; you can redistribute it and/or         *
 * modify it under the terms of EITHER:                                  *
 *   (1) The GNU Lesser General Public License as published by the Free  *
 *       Software Foundation; either version 2.1 of the License, or (at  *
 *       your option) any later version. The text of the GNU Lesser      *
 *       General Public License is included with this library in the     *
 *       file LICENSE.TXT.                                               *
 *   (2) The BSD-style license that is included with this library in     *
 *       the file LICENSE-BSD.TXT.                                       *
 *                                                                       *
 * This library is distributed in the hope that it will be useful,       *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files    *
 * LICENSE.TXT and LICENSE-BSD.TXT for more details.                     *
 *                                                                       *
 *************************************************************************/

/*

internal data structures and functions for collision detection.

*/

#ifndef _ODE_COLLISION_KERNEL_H_
#define _ODE_COLLISION_KERNEL_H_

#include "ode_common.hpp"
#include "ode_contact.hpp"
#include "ode_collision.hpp"
#include "objects.h"
#include "odetls.h"
#include "common.h"


//****************************************************************************
// constants and macros

// mask for the number-of-contacts field in the dCollide() flags parameter
#define NUMC_MASK (0xffff)

#define IS_SPACE(geom) \
    dIN_RANGE((geom)->type, dFirstSpaceClass, dLastSpaceClass + 1)

#define CHECK_NOT_LOCKED(space) \
    dUASSERT ((space) == NULL || (space)->lock_count == 0, \
        "Invalid operation for locked space")


//****************************************************************************
// geometry object base class


// geom flags.
//
// GEOM_DIRTY means that the space data structures for this geom are
// potentially not up to date. NOTE THAT all space parents of a dirty geom
// are themselves dirty. this is an invariant that must be enforced.
//
// GEOM_AABB_BAD means that the cached AABB for this geom is not up to date.
// note that GEOM_DIRTY does not imply GEOM_AABB_BAD, as the geom might
// recalculate its own AABB but does not know how to update the space data
// structures for the space it is in. but GEOM_AABB_BAD implies GEOM_DIRTY.
// the valid combinations are: 
//		0
//		GEOM_DIRTY
//		GEOM_DIRTY|GEOM_AABB_BAD
//		GEOM_DIRTY|GEOM_AABB_BAD|GEOM_POSR_BAD

enum {
    GEOM_DIRTY	= 1,    // geom is 'dirty', i.e. position unknown
    GEOM_POSR_BAD = 2,    // geom's final posr is not valid
    GEOM_AABB_BAD	= 4,    // geom's AABB is not valid
    GEOM_PLACEABLE = 8,   // geom is placeable
    GEOM_ENABLED = 16,    // geom is enabled
    GEOM_ZERO_SIZED = 32, // geom is zero sized

    GEOM_ENABLE_TEST_MASK = GEOM_ENABLED | GEOM_ZERO_SIZED,
    GEOM_ENABLE_TEST_VALUE = GEOM_ENABLED,

    // Ray specific
    RAY_FIRSTCONTACT = 0x10000,
    RAY_BACKFACECULL = 0x20000,
    RAY_CLOSEST_HIT  = 0x40000
};

enum dxContactMergeOptions {
    DONT_MERGE_CONTACTS,
    MERGE_CONTACT_NORMALS,
    MERGE_CONTACTS_FULLY
};


// geometry object base class. pos and R will either point to a separately
// allocated buffer (if body is 0 - pos points to the dxPosR object) or to
// the pos and R of the body (if body nonzero).
// a dGeomID is a pointer to this object.

struct dxGeom : public dBase {
    int type;		// geom type number, set by subclass constructor
    int gflags;		// flags used by geom and space
    void *data;		// user-defined data pointer
    dBodyID body;		// dynamics body associated with this object (if any)
    dxGeom *body_next;	// next geom in body's linked list of associated geoms
    dxPosR *final_posr;	// final position of the geom in world coordinates
    dxPosR *offset_posr;	// offset from body in local coordinates

    // information used by spaces
    dxGeom *next;		// next geom in linked list of geoms
    dxGeom **tome;	// linked list backpointer
    dxGeom *next_ex;	// next geom in extra linked list of geoms (for higher level structures)
    dxGeom **tome_ex;	// extra linked list backpointer (for higher level structures)
    dxSpace *parent_space;// the space this geom is contained in, 0 if none
    dReal aabb[6];	// cached AABB for this space
    unsigned long category_bits,collide_bits;

    dxGeom (dSpaceID _space, int is_placeable);
    virtual ~dxGeom();

    // Set or clear GEOM_ZERO_SIZED flag
    void updateZeroSizedFlag(bool is_zero_sized) { gflags = is_zero_sized ? (gflags | GEOM_ZERO_SIZED) : (gflags & ~GEOM_ZERO_SIZED); }
    // Get parent space TLS kind
    unsigned getParentSpaceTLSKind() const;

    const dVector3 &buildUpdatedPosition()
    {
        dIASSERT(gflags & GEOM_PLACEABLE);
        
        recomputePosr();
        return final_posr->pos;
    }

    const dMatrix3 &buildUpdatedRotation()
    {
        dIASSERT(gflags & GEOM_PLACEABLE);

        recomputePosr();
        return final_posr->R;
    }

    // recalculate our new final position if needed
    void recomputePosr()
    {
        if (gflags & GEOM_POSR_BAD) {
            computePosr();
            gflags &= ~GEOM_POSR_BAD;
        }
    }

    // calculate our new final position from our offset and body
    void computePosr();

    bool checkControlValueSizeValidity(void *dataValue, int *dataSize, int iRequiresSize) { return (*dataSize == iRequiresSize && dataValue != 0) ? true : !(*dataSize = iRequiresSize); } // Here it is the intent to return true for 0 required size in any case
    virtual bool controlGeometry(int controlClass, int controlCode, void *dataValue, int *dataSize);

    virtual void computeAABB()=0;
    // compute the AABB for this object and put it in aabb. this function
    // always performs a fresh computation, it does not inspect the
    // GEOM_AABB_BAD flag.

    virtual int AABBTest (dxGeom *o, dReal aabb[6]);
    // test whether the given AABB object intersects with this object, return
    // 1=yes, 0=no. this is used as an early-exit test in the space collision
    // functions. the default implementation returns 1, which is the correct
    // behavior if no more detailed implementation can be provided.

    // utility functions

    // compute the AABB only if it is not current. this function manipulates
    // the GEOM_AABB_BAD flag.

    void recomputeAABB() {
        if (gflags & GEOM_AABB_BAD) {
            // our aabb functions assume final_posr is up to date
            recomputePosr(); 
            computeAABB();
            gflags &= ~GEOM_AABB_BAD;
        }
    }

    inline void markAABBBad();

    // add and remove this geom from a linked list maintained by a space.

    void spaceAdd (dxGeom **first_ptr) {
        next = *first_ptr;
        tome = first_ptr;
        if (*first_ptr) (*first_ptr)->tome = &next;
        *first_ptr = this;
    }
    void spaceRemove() {
        if (next) next->tome = tome;
        *tome = next;
    }

    // add and remove this geom from a linked list maintained by a body.

    void bodyAdd (dxBody *b) {
        body = b;
        body_next = b->geom;
        b->geom = this;
    }
    void bodyRemove();
};

//****************************************************************************
// the base space class
//
// the contained geoms are divided into two kinds: clean and dirty.
// the clean geoms have not moved since they were put in the list,
// and their AABBs are valid. the dirty geoms have changed position, and
// their AABBs are may not be valid. the two types are distinguished by the
// GEOM_DIRTY flag. all dirty geoms come *before* all clean geoms in the list.

#if dTLS_ENABLED
#define dSPACE_TLS_KIND_INIT_VALUE OTK__DEFAULT
#define dSPACE_TLS_KIND_MANUAL_VALUE OTK_MANUALCLEANUP
#else
#define dSPACE_TLS_KIND_INIT_VALUE 0
#define dSPACE_TLS_KIND_MANUAL_VALUE 0
#endif

struct dxSpace : public dxGeom {
    int count;			// number of geoms in this space
    dxGeom *first;		// first geom in list
    int cleanup;			// cleanup mode, 1=destroy geoms on exit
    int sublevel;         // space sublevel (used in dSpaceCollide2). NOT TRACKED AUTOMATICALLY!!!
    unsigned tls_kind;	// space TLS kind to be used for global caches retrieval

    // cached state for getGeom()
    int current_index;		// only valid if current_geom != 0
    dxGeom *current_geom;		// if 0 then there is no information

    // locking stuff. the space is locked when it is currently traversing its
    // internal data structures, e.g. in collide() and collide2(). operations
    // that modify the contents of the space are not permitted when the space
    // is locked.
    int lock_count;

    dxSpace (dSpaceID _space);
    ~dxSpace();

    void computeAABB();

    void setCleanup (int mode) { cleanup = (mode != 0); }
    int getCleanup() const { return cleanup; }
    void setSublevel(int value) { sublevel = value; }
    int getSublevel() const { return sublevel; }
    void setManulCleanup(int value) { tls_kind = (value ? dSPACE_TLS_KIND_MANUAL_VALUE : dSPACE_TLS_KIND_INIT_VALUE); }
    int getManualCleanup() const { return (tls_kind == dSPACE_TLS_KIND_MANUAL_VALUE) ? 1 : 0; }
    int query (dxGeom *geom) const { dAASSERT(geom); return (geom->parent_space == this); }
    int getNumGeoms() const { return count; }

    virtual dxGeom *getGeom (int i);

    virtual void add (dxGeom *);
    virtual void remove (dxGeom *);
    virtual void dirty (dxGeom *);

    virtual void cleanGeoms()=0;
    // turn all dirty geoms into clean geoms by computing their AABBs and any
    // other space data structures that are required. this should clear the
    // GEOM_DIRTY and GEOM_AABB_BAD flags of all geoms.

    virtual void collide (void *data, dNearCallback *callback)=0;
    virtual void collide2 (void *data, dxGeom *geom, dNearCallback *callback)=0;
};


//////////////////////////////////////////////////////////////////////////

/*inline */
void dxGeom::markAABBBad() {
    gflags |= (GEOM_DIRTY | GEOM_AABB_BAD);
    CHECK_NOT_LOCKED(parent_space);
}


//****************************************************************************
// Initialization and finalization functions

void dInitColliders();
void dFinitColliders();

void dClearPosrCache(void);
void dFinitUserClasses();


#endif
