mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
459 lines
9.8 KiB
C++
459 lines
9.8 KiB
C++
/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
|
|
#include "stdafx.h"
|
|
|
|
#include "V8Kernel/Body.h"
|
|
#include "V8kernel/Kernel.h"
|
|
#include "V8Kernel/Constants.h"
|
|
#include "Util/Units.h"
|
|
|
|
#include "boost/utility.hpp"
|
|
#include "rbx/boost.hpp"
|
|
#include "rbx/atomic.h"
|
|
|
|
#include "btBulletCollisionCommon.h"
|
|
|
|
namespace RBX {
|
|
|
|
Body* Body::worldBody;
|
|
|
|
static rbx::atomic<int> gBodyStateIndex;
|
|
|
|
Body::Body()
|
|
: connectorUseCount(0)
|
|
, root(NULL)
|
|
, link(NULL)
|
|
, leafBodyIndex(-1)
|
|
, canThrottle(true)
|
|
, simBody(NULL)
|
|
, moment(Matrix3::identity())
|
|
, mass(0.0f)
|
|
, cofmOffset(Vector3::zero())
|
|
, stateIndex(getNextStateIndex())
|
|
, cofm(NULL)
|
|
, uid( 0 )
|
|
{
|
|
root = this;
|
|
simBody = new SimBody(this);
|
|
}
|
|
|
|
Body::~Body()
|
|
{
|
|
RBXASSERT(connectorUseCount == 0);
|
|
RBXASSERT(!link);
|
|
RBXASSERT(simBody);
|
|
RBXASSERT(leafBodyIndex == -1);
|
|
|
|
if( cofm )
|
|
{
|
|
delete cofm;
|
|
cofm = NULL;
|
|
}
|
|
delete simBody;
|
|
simBody = NULL;
|
|
|
|
RBXASSERT(!cofm);
|
|
RBXASSERT(root == this);
|
|
}
|
|
|
|
unsigned int Body::getNextStateIndex()
|
|
{
|
|
return ++gBodyStateIndex;
|
|
}
|
|
|
|
void Body::advanceStateIndex()
|
|
{
|
|
stateIndex = getNextStateIndex();
|
|
}
|
|
|
|
void Body::initStaticData()
|
|
{
|
|
worldBody = new Body();
|
|
}
|
|
|
|
Body* Body::getWorldBody()
|
|
{
|
|
static boost::once_flag flag = BOOST_ONCE_INIT;
|
|
boost::call_once(&initStaticData, flag);
|
|
return worldBody;
|
|
}
|
|
|
|
bool Body::validateParentCofmDirty()
|
|
{
|
|
RBXASSERT(cofm);
|
|
RBXASSERT(cofm->getIsDirty());
|
|
|
|
if (getParent()) {
|
|
getParent()->validateParentCofmDirty();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// Goes up the chain, making every Cofm dirty along the way
|
|
// In addition, forces the root body to become dirty
|
|
void Body::makeCofmDirty()
|
|
{
|
|
if (cofm && cofm->getIsDirty())
|
|
{
|
|
RBXASSERT(this->validateParentCofmDirty());
|
|
RBXASSERT(this->getRootSimBody()->getDirty());
|
|
}
|
|
else
|
|
{
|
|
if (getParent()) {
|
|
RBXASSERT(!simBody);
|
|
getParent()->makeCofmDirty();
|
|
}
|
|
else {
|
|
RBXASSERT(root == this);
|
|
if (simBody) {
|
|
simBody->makeDirty();
|
|
}
|
|
}
|
|
|
|
if (cofm) {
|
|
cofm->makeDirty();
|
|
RBXASSERT( (numChildren() > 0) || (cofmOffset != Vector3::zero()) );
|
|
}
|
|
else {
|
|
RBXASSERT(numChildren() == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Body::resetRoot(Body* newRoot)
|
|
{
|
|
RBXASSERT(newRoot == calcRoot());
|
|
root = newRoot;
|
|
for (int i = 0; i < numChildren(); ++i) {
|
|
getChild(i)->resetRoot(newRoot);
|
|
}
|
|
}
|
|
|
|
|
|
void Body::onParentChanging()
|
|
{
|
|
RBXASSERT_VERY_FAST(!getParent() || (!(getParent()->getConstRootSimBody()->isInKernel()))); // confirm not happening to bodies in kernel
|
|
|
|
if (link) {
|
|
link->setBody(NULL); // link must always set parent first, then
|
|
link = NULL;
|
|
}
|
|
|
|
if (getParent()) {
|
|
RBXASSERT(root == getParent()->getRoot()); // I keep my pvIndex, as do my children
|
|
RBXASSERT(!simBody);
|
|
}
|
|
else {
|
|
RBXASSERT(root = this);
|
|
RBXASSERT(simBody);
|
|
delete simBody;
|
|
simBody = NULL;
|
|
}
|
|
}
|
|
|
|
void Body::onParentChanged(IndexedTree* oldParent)
|
|
{
|
|
// confirm not happening to bodies in kernel
|
|
RBXASSERT_VERY_FAST(!getParent() || (!(getParent()->getConstRootSimBody()->isInKernel())));
|
|
|
|
if (getParent()) {
|
|
;
|
|
}
|
|
else {
|
|
simBody = new SimBody(this);
|
|
}
|
|
|
|
Body* newRoot = calcRoot();
|
|
newRoot->advanceStateIndex();
|
|
resetRoot(newRoot);
|
|
}
|
|
|
|
|
|
void Body::onChildAdding(IndexedTree* child)
|
|
{
|
|
makeCofmDirty();
|
|
}
|
|
|
|
void Body::refreshCofm()
|
|
{
|
|
bool hasChildren = (numChildren() > 0);
|
|
bool hasCofmOffset = (cofmOffset != Vector3::zero());
|
|
|
|
bool needsCofm = (hasChildren || hasCofmOffset);
|
|
|
|
if (needsCofm) {
|
|
if (!cofm) {
|
|
cofm = new Cofm(this);
|
|
RBXASSERT(cofm->getIsDirty());
|
|
}
|
|
}
|
|
else { // doesn't need Cofm
|
|
if (cofm) {
|
|
delete cofm;
|
|
cofm = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Body::setCofmOffset(const Vector3& _centerOfMassInBody)
|
|
{
|
|
|
|
if (cofmOffset != _centerOfMassInBody) {
|
|
cofmOffset = _centerOfMassInBody;
|
|
refreshCofm();
|
|
makeCofmDirty(); // yes - we need to make dirty down the chain
|
|
}
|
|
}
|
|
|
|
void Body::updatePV()
|
|
{
|
|
RBXASSERT((getParent() != NULL) != (getRoot() == this));
|
|
|
|
if (!getParent() || (stateIndex == getRoot()->getStateIndex())) {
|
|
;
|
|
}
|
|
else {
|
|
getParent()->getPvUnsafe(); // do this first - prevent infinite recursion
|
|
PV::pvAtLocalCoord(getParent()->getPvUnsafe(), getMeInParent(), pv);
|
|
|
|
RBXASSERT(stateIndex != getRoot()->getStateIndex()); // concurrency check
|
|
stateIndex = getRoot()->getStateIndex();
|
|
|
|
RBXASSERT_SLOW(Math::isOrthonormal(pv.position.rotation));
|
|
RBXASSERT_SLOW(!Math::isNanInfDenormVector3(pv.position.translation)); // - asserting all the time - John
|
|
}
|
|
}
|
|
|
|
void Body::onChildAdded(IndexedTree* child)
|
|
{
|
|
if (!cofm) {
|
|
RBXASSERT(numChildren() == 1);
|
|
refreshCofm();
|
|
RBXASSERT(cofm);
|
|
} // no need to make dirty - adding anyways
|
|
}
|
|
|
|
void Body::onChildRemoved(IndexedTree* child)
|
|
{
|
|
RBXASSERT(cofm);
|
|
if (numChildren() == 0) {
|
|
refreshCofm();
|
|
}
|
|
makeCofmDirty();
|
|
}
|
|
|
|
// Note - need to reset the root state index, because this is what everyone uses to determine if they're in synch
|
|
//
|
|
//
|
|
void Body::setMeInParent(const CoordinateFrame& _meInParent)
|
|
{
|
|
RBXASSERT_FISHING(Math::longestVector3Component(_meInParent.translation) < 1e6);
|
|
|
|
RBXASSERT_FISHING(Math::isOrthonormal(_meInParent.rotation));
|
|
|
|
if (link) {
|
|
RBXASSERT(0);
|
|
link->setBody(NULL);
|
|
link = NULL;
|
|
}
|
|
|
|
if (getParent()) {
|
|
meInParent = _meInParent;
|
|
makeCofmDirty();
|
|
makeStateDirty();
|
|
}
|
|
else {
|
|
RBXASSERT(0);
|
|
}
|
|
}
|
|
|
|
void Body::setMeInParent(Link* _link)
|
|
{
|
|
RBXASSERT(_link);
|
|
|
|
if (link && (link != _link)) {
|
|
link->setBody(NULL);
|
|
}
|
|
|
|
if (getParent()) {
|
|
link = _link;
|
|
link->setBody(this);
|
|
makeCofmDirty();
|
|
makeStateDirty();
|
|
}
|
|
else {
|
|
RBXASSERT(0);
|
|
}
|
|
}
|
|
|
|
|
|
void Body::setPv(const PV& _pv, const BodyPvSetter& bpv)
|
|
{
|
|
if (getParent())
|
|
{
|
|
RBXASSERT(0); // bad news here - this object has a parent, so setting it's position should only be done
|
|
// by changing the parent's position, or meInParent
|
|
}
|
|
else
|
|
{
|
|
pv = _pv;
|
|
if (simBody) {
|
|
simBody->makeDirty();
|
|
}
|
|
advanceStateIndex(); // I'm the root
|
|
}
|
|
|
|
//RBXASSERT(Math::longestVector3Component(pv.position.translation) < 1e6);
|
|
RBXASSERT_FISHING(Math::isOrthonormal(pv.position.rotation));
|
|
RBXASSERT_SLOW(!Math::isNanInfDenormVector3(pv.position.translation));
|
|
// RBXASSERT(!Math::isNanInfDenormMatrix3(pv.position.rotation)); -- This was asserting all the time. I had to comment out - John
|
|
RBXASSERT_SLOW(!Math::isNanInfDenormVector3(pv.velocity.linear));
|
|
RBXASSERT_SLOW(!Math::isNanInfDenormVector3(pv.velocity.rotational));
|
|
}
|
|
|
|
void Body::setCoordinateFrame(const CoordinateFrame& worldCoord, const BodyPvSetter& bpv)
|
|
{
|
|
setPv( PV(worldCoord, getVelocity()), bpv );
|
|
}
|
|
|
|
void Body::setVelocity(const Velocity& worldVelocity, const BodyPvSetter& bpv)
|
|
{
|
|
if (!getParent()) {
|
|
pv.velocity = worldVelocity;
|
|
advanceStateIndex();
|
|
if (simBody) {
|
|
simBody->makeDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Body::setCanThrottle(bool value, const BodyPvSetter& bpv)
|
|
{
|
|
canThrottle = value;
|
|
}
|
|
|
|
|
|
void Body::setMass(float _mass)
|
|
{
|
|
if (mass != _mass) {
|
|
makeCofmDirty();
|
|
mass = _mass;
|
|
}
|
|
}
|
|
|
|
void Body::setMoment(const Matrix3& _momentInBody)
|
|
{
|
|
if (moment != _momentInBody) {
|
|
makeCofmDirty();
|
|
moment = _momentInBody;
|
|
}
|
|
}
|
|
|
|
Matrix3 Body::getIBodyAtPoint(const Vector3& point)
|
|
{
|
|
return Math::getIBodyAtPoint( point,
|
|
getIBody(),
|
|
getMass() );
|
|
}
|
|
|
|
Matrix3 Body::getIWorldAtPoint(const Vector3& point)
|
|
{
|
|
return Math::getIWorldAtPoint( getPos(),
|
|
point,
|
|
getIWorld(),
|
|
getMass() );
|
|
}
|
|
|
|
Matrix3 Body::getBranchIWorldAtPoint(const Vector3& point)
|
|
{
|
|
return Math::getIWorldAtPoint( getBranchCofmPos(),
|
|
point,
|
|
getBranchIWorld(),
|
|
getBranchMass() );
|
|
}
|
|
|
|
const Vector3& Body::getBranchCofmOffset()
|
|
{
|
|
RBXASSERT(simBody);
|
|
RBXASSERT((numChildren() > 0) || !getCofm() || getCofm()->getCofmInBody() == cofmOffset);
|
|
|
|
return getCofm() ? getCofm()->getCofmInBody() : cofmOffset;
|
|
}
|
|
|
|
Vector3 Body::getBranchCofmPos()
|
|
{
|
|
return cofm ? getCoordinateFrame().pointToWorldSpace(cofm->getCofmInBody())
|
|
: getPos();
|
|
}
|
|
|
|
CoordinateFrame Body::getBranchCofmCoordinateFrame()
|
|
{
|
|
return CoordinateFrame( getCoordinateFrame().rotation,
|
|
getBranchCofmPos() );
|
|
}
|
|
|
|
|
|
float Body::potentialEnergy()
|
|
{
|
|
PV tempPv = getPvUnsafe();
|
|
|
|
float rbxGravity = Units::kmsAccelerationToRbx(Constants::getKmsGravity());
|
|
|
|
return -rbxGravity * tempPv.position.translation.y * mass;
|
|
}
|
|
|
|
float Body::kineticEnergy()
|
|
{
|
|
PV tempPv = getPvUnsafe();
|
|
|
|
// E = 0.5 (Iw) dot w
|
|
Vector3 Iw = getIWorld() * tempPv.velocity.rotational;
|
|
float out = 0.5f * Iw.dot(tempPv.velocity.rotational) + 0.5f * tempPv.velocity.linear.dot(tempPv.velocity.linear) * mass;
|
|
return out;
|
|
}
|
|
|
|
void Body::setUID( boost::uint64_t _uid )
|
|
{
|
|
uid = _uid;
|
|
if( simBody )
|
|
{
|
|
simBody->setUID( _uid );
|
|
}
|
|
}
|
|
|
|
void Body::updateBulletCollisionObject(btCollisionObject* object)
|
|
{
|
|
btTransform transform = getPvUnsafe().position.transformFromCFrame();
|
|
|
|
if (btCollisionShape* shape = object->getCollisionShape())
|
|
{
|
|
btMatrix3x3& basis = transform.getBasis();
|
|
|
|
if (shape->getShapeType() == SPHERE_SHAPE_PROXYTYPE)
|
|
{
|
|
basis.setIdentity();
|
|
}
|
|
else if (shape->getShapeType() == CYLINDER_SHAPE_PROXYTYPE)
|
|
{
|
|
// column 0 = axis X - cylinder is rotationally invariant around this axis
|
|
btVector3 axisX = basis.getColumn(0);
|
|
|
|
// find a basis with axisX as the axis
|
|
// unfortunately any basis like that has a discontinuity so sometimes the rotation will flip
|
|
// we'll try to pick the axis so that the discontinuity is when the cylinder mostly stands upright
|
|
// in this case it's less apparent since the cylinder is unlikely to spin fast while standing on its top
|
|
btVector3 axisP = fabsf(axisX.getY()) < 0.9f ? btVector3(0, 1, 0) : btVector3(1, 0, 0);
|
|
btVector3 axisZ = axisX.cross(axisP).normalized();
|
|
btVector3 axisY = axisZ.cross(axisX).normalized();
|
|
|
|
basis.setValue(axisX.getX(), axisY.getX(), axisZ.getX(), axisX.getY(), axisY.getY(), axisZ.getY(), axisX.getZ(), axisY.getZ(), axisZ.getZ());
|
|
}
|
|
}
|
|
|
|
object->setWorldTransform(transform);
|
|
}
|
|
|
|
} // namespace
|