// Filename: odeUtil.cxx
// Created by:  joswilso (27Dec06)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "odeUtil.h"

#ifdef HAVE_PYTHON
  #include "py_panda.h"
  #include "typedReferenceCount.h"
  #ifndef CPPPARSER
    extern EXPCL_PANDAODE Dtool_PyTypedObject Dtool_OdeGeom;
  #endif
#endif

dReal OdeUtil::OC_infinity = dInfinity;

#ifdef HAVE_PYTHON
PyObject* OdeUtil::_python_callback = NULL;
#endif

////////////////////////////////////////////////////////////////////
//     Function: OdeUtil::get_connecting_joint
//       Access: Public, Static
//  Description: Returns the joint that connects the given bodies.
////////////////////////////////////////////////////////////////////
OdeJoint OdeUtil::
get_connecting_joint(const OdeBody &body1, const OdeBody &body2) {
  return OdeJoint(dConnectingJoint(body1.get_id(),body2.get_id()));
}

////////////////////////////////////////////////////////////////////
//     Function: OdeUtil::get_connecting_joint_list
//       Access: Public, Static
//  Description: Returns a collection of joints connecting the
//               specified bodies.
////////////////////////////////////////////////////////////////////
OdeJointCollection OdeUtil::
get_connecting_joint_list(const OdeBody &body1, const OdeBody &body2) {
  const int max_possible_joints = min(body1.get_num_joints(), body1.get_num_joints());
  
  dJointID *joint_list = (dJointID *)PANDA_MALLOC_ARRAY(max_possible_joints * sizeof(dJointID));
  int num_joints = dConnectingJointList(body1.get_id(), body2.get_id(),
          joint_list);
  OdeJointCollection joints;
  for (int i = 0; i < num_joints; i++) {
    joints.add_joint(OdeJoint(joint_list[i]));
  }
  
  PANDA_FREE_ARRAY(joint_list);
  return joints;
}

////////////////////////////////////////////////////////////////////
//     Function: OdeUtil::are_connected
//       Access: Public, Static
//  Description: Returns 1 if the given bodies are connected
//               by a joint, returns 0 otherwise.
////////////////////////////////////////////////////////////////////
int OdeUtil::
are_connected(const OdeBody &body1, const OdeBody &body2) {
  return dAreConnected(body1.get_id(),body2.get_id());
}

////////////////////////////////////////////////////////////////////
//     Function: OdeUtil::are_connected_excluding
//       Access: Public, Static
//  Description: Returns 1 if the given bodies are connected
//               by a joint that does not match the given
//               joint_type, returns 0 otherwise. This is useful
//               for deciding whether to add contact joints between
//               two bodies: if they are already connected by
//               non-contact joints then it may not be appropriate
//               to add contacts, however it is okay to add more
//               contact between bodies that already have contacts.
////////////////////////////////////////////////////////////////////
int OdeUtil::
are_connected_excluding(const OdeBody &body1,
                        const OdeBody &body2,
                        const int joint_type) {
  return dAreConnectedExcluding(body1.get_id(),
        body2.get_id(),
        joint_type);
}

////////////////////////////////////////////////////////////////////
//     Function: OdeUtil::collide
//       Access: Public, Static
//  Description: Given two geometry objects that potentially touch
//               (geom1 and geom2), generate contact information
//               for them. Returns an OdeCollisionEntry.
////////////////////////////////////////////////////////////////////
PT(OdeCollisionEntry) OdeUtil::
collide(const OdeGeom &geom1, const OdeGeom &geom2, const short int max_contacts) {
  dContactGeom *contact_list = (dContactGeom *)PANDA_MALLOC_ARRAY(max_contacts * sizeof(dContactGeom));
  int num_contacts = dCollide(geom1.get_id(), geom2.get_id(), max_contacts, contact_list, sizeof(dContactGeom));
  PT(OdeCollisionEntry) entry = new OdeCollisionEntry();
  entry->_geom1 = geom1.get_id();
  entry->_geom2 = geom2.get_id();
  entry->_body1 = dGeomGetBody(geom1.get_id());
  entry->_body2 = dGeomGetBody(geom2.get_id());
  entry->_num_contacts = num_contacts;
  entry->_contact_geoms = new OdeContactGeom[num_contacts];
  for (int i = 0; i < num_contacts; i++) {
    entry->_contact_geoms[i] = contact_list[i];
  }
  
  PANDA_FREE_ARRAY(contact_list);
  return entry;
}

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: OdeUtil::collide2
//       Access: Public, Static
//  Description: Calls the callback for all potentially intersecting
//               pairs that contain one geom from geom1 and one geom
//               from geom2.
////////////////////////////////////////////////////////////////////
int OdeUtil::
collide2(const OdeGeom &geom1, const OdeGeom &geom2, PyObject* arg, PyObject* callback) {
  nassertr(callback != NULL, -1);
  if (!PyCallable_Check(callback)) {
    PyErr_Format(PyExc_TypeError, "'%s' object is not callable", callback->ob_type->tp_name);
    return -1;
  } else {
    _python_callback = (PyObject*) callback;
    Py_XINCREF(_python_callback);
    dSpaceCollide2(geom1.get_id(), geom2.get_id(), (void*) arg, &near_callback);
    Py_XDECREF(_python_callback);
    return 0;
  }
}

void OdeUtil::
near_callback(void *data, dGeomID o1, dGeomID o2) {
  ode_cat.spam() << "near_callback called, data: " << data << ", dGeomID1: " << o1 << ", dGeomID2: " << o2 << "\n";
  OdeGeom g1 (o1);
  OdeGeom g2 (o2);
  PyObject* p1 = DTool_CreatePyInstanceTyped(&g1, Dtool_OdeGeom, true, false, g1.get_type_index());
  PyObject* p2 = DTool_CreatePyInstanceTyped(&g2, Dtool_OdeGeom, true, false, g2.get_type_index());
  PyObject* result = PyEval_CallFunction(_python_callback, "OOO", (PyObject*) data, p1, p2);
  if (!result) {
    ode_cat.error() << "An error occurred while calling python function!\n";
    PyErr_Print();
  }
}
#endif

OdeGeom OdeUtil::
space_to_geom(const OdeSpace &space) {
  return OdeGeom((dGeomID)space.get_id());
}

