This commit is contained in:
watrabi
2025-10-28 14:05:46 -04:00
parent 977f1ff4b8
commit c93494f795
452 changed files with 47860 additions and 152 deletions
+415
View File
@@ -0,0 +1,415 @@
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "V8Kernel/KernelIndex.h"
#include "V8Kernel/Cofm.h"
#include "V8Kernel/SimBody.h"
#include "V8Kernel/Link.h"
#include "Util/IndexArray.h"
#include "Util/IndexedTree.h"
#include "Util/Memory.h"
#include "rbx/threadsafe.h"
class btCollisionObject;
namespace RBX {
/*
Body class. Handles rigid joints and kinematic/dynamic joints. Automatically calculates
mass properties. Automatically updates state.
StateRoot: Body or Joint that contains the latest state id.
RigidRoot: Body that I am clumped with
COFM: Calculated for every body in a rigid group - chain is broken for linked bodies
SIMBody: Only Free Bodies have this
Whenever adjusting a joint, must increment the stateRoot of the assembly
World Body // Special Body - no COFM
| |
| -- Body // anchored body - rigid join to the world body - when moving, adjust state index
|
-- Body // anchored body - rigid join to the world body
|
-- Body
|
-- Body
0 // this is a link
-- Body // this is the StateRoot for the chain below it
|
Body
Body (free) // this is the StateRoot for the free body and chain
| // this body has group COFM for the top three bodies + SimBody
-- Body
|
-- Body
0
-- Body // this body has a group COFM for the bodies below it
|
Body
COFM: body, kinematic, assembly (assumes a floating object)
*/
class BodyPvSetter;
class Body : public IndexedTree
, public Allocator<Body>
{
public:
friend class KernelData;
friend class Kernel;
friend class SimBody;
private:
rbx::spin_mutex mutex; // for safe calls that require update
// Unique identifier set by the World
boost::uint64_t uid;
int guidIndex;
int leafBodyIndex;
int& getLeafBodyIndex() {return leafBodyIndex;}
int connectorUseCount; // how many connectors connect this body
static Body* worldBody;
static void initStaticData(); // inits the world body
Body* root; // top body - either anchored or 6 dof
Cofm* cofm;
Cofm* getCofm() {return cofm;} // use these to insure const correctness;
SimBody* simBody; // Only present for parent && in kernel
SimBody* getSimBody() {return simBody;}
const SimBody* getConstSimBody() const {return simBody;}
void refreshCofm();
Link* link; // if link != NULL, use for "getMeInParent()"
// defining variables
bool canThrottle; // this body can throttle - i.e., slow down
CoordinateFrame meInParent; // used if no link, i.e. - rigid connection
Matrix3 moment;
float mass;
Vector3 cofmOffset;
// resulting variables
unsigned int stateIndex;
PV pv;
void resetRoot(Body* newRoot);
bool validateParentCofmDirty();
const CoordinateFrame& getMeInParent() {
RBXASSERT(getParent());
return getLink() ? getLink()->getChildInParent() : meInParent;
}
const CoordinateFrame& getConstMeInParent() const {
RBXASSERT(getConstParent());
RBXASSERT(!getConstLink()); // fails if linked (i.e. only works in same clump);
return meInParent;
}
void updatePV(); // Does not inline anyhow.
bool pvIsUpToDate() const {
if (!getConstParent()) {
return true;
}
else {
if (stateIndex != getRoot()->getStateIndexNoUpdate()) {
return false;
}
else {
return getConstParent()->pvIsUpToDate();
}
}
}
void onChildAdded(Body* newChild);
void onChildRemoved(Body* newChild);
Body* calcRoot() {return getParent() ? getParent()->calcRoot() : this;}
const Body* calcRootConst() const {return getConstParent() ? getConstParent()->calcRootConst() : this;}
///////////////////////////////////////////////
// Indexed Tree
/*override*/ void onParentChanging();
/*override*/ void onParentChanged(IndexedTree* oldParent);
/*override*/ void onChildAdding(IndexedTree* child);
/*override*/ void onChildAdded(IndexedTree* child);
/*override*/ void onChildRemoved(IndexedTree* child);
public:
Body();
~Body();
static unsigned int getNextStateIndex();
// Static world body
static Body* getWorldBody();
SimBody* getRootSimBody() {return getRoot()->getSimBody();}
const SimBody* getConstRootSimBody() const {return getRoot()->getConstSimBody();}
void setUID( boost::uint64_t _uid );
boost::uint64_t getUID() const { return uid; }
// Used only by the solver inspector to id the objects between different clients
void setGuidIndex( int _guidIndex ) { guidIndex = _guidIndex; }
int getGuidIndex() const { return guidIndex; }
//////////////////////////////////////////////////////
// From Cofm, SimBody
bool cofmIsClean() {return getCofm() ? !getCofm()->getIsDirty() : true;}
//////////////////////////////////////////////////////
// From Child
void makeCofmDirty();
void advanceStateIndex();
void makeStateDirty() {
getRoot()->advanceStateIndex();
}
unsigned int getStateIndex() {
updatePV();
return stateIndex;
}
unsigned int getStateIndexNoUpdate() const {
return stateIndex;
}
//////////////////////////////////////////////////////
// Base vs. Branch
Body* getChild(int i) {return getTypedChild<Body>(i);}
const Body* getConstChild(int i) const {return getConstTypedChild<Body>(i);}
Body* getParent() {return getTypedParent<Body>();}
const Body* getConstParent() const {return getConstTypedParent<Body>();}
Link* getLink() {return link;}
const Link* getConstLink() const {return link;}
const Body* getRoot() const {
RBXASSERT_SLOW(root == calcRootConst());
return root;
}
Body* getRoot() {
RBXASSERT_SLOW(root == calcRootConst());
return root;
}
const Vector3& getCofmOffset() {
return cofmOffset;
}
const Vector3& getBranchCofmOffset();
// Only works on bodies in same clump - otherwise not const for the link and will assert
CoordinateFrame getMeInAncestor(const Body* ancestor) const {
if (ancestor == this) {
return CoordinateFrame();
}
else if (ancestor == getConstParent()) {
return getConstMeInParent();
}
else {
return getConstParent()->getMeInAncestor(ancestor) * getConstMeInParent();
}
}
inline float getMass() const {return mass;}
inline Matrix3 getIBody() const {return moment;}
inline Vector3 getIBodyV3() const {return Math::toDiagonal(getIBody());}
Matrix3 getIBodyAtPoint(const Vector3& point);
inline Matrix3 getMoment() const {return getIBody();}
inline Vector3 getPrincipalMoment() const {return getIBodyV3();}
Matrix3 getIWorld() {return Math::momentToWorldSpace(getIBody(), getCoordinateFrame().rotation);}
Matrix3 getIWorldAtPoint(const Vector3& point);
// Branch refers to everything below me....
float getBranchMass() {return getCofm() ? getCofm()->getMass() : mass;}
Matrix3 getBranchIBody() {return getCofm() ? getCofm()->getMoment() : moment;}
Vector3 getBranchIBodyV3() {return Math::toDiagonal(getBranchIBody());}
Matrix3 getBranchIWorld() {return Math::momentToWorldSpace(getBranchIBody(), getCoordinateFrame().rotation);}
Matrix3 getBranchIWorldAtPoint(const Vector3& point);
Vector3 getBranchCofmPos();
CoordinateFrame getBranchCofmCoordinateFrame();
//
const PV& getPvFast() const {
RBXASSERT_FISHING(pvIsUpToDate());
return pv;
}
// Current Job should hold the Data Model write lock or somehow have locked the Body::mutex.
const PV& getPvUnsafe() {
updatePV();
return pv;
}
const PV& getPV_Spin_Lock() {
rbx::spin_mutex::scoped_lock lock(mutex);
updatePV();
return pv;
}
const PV& getPvSafe() const {
Body* thisNotConst = const_cast<Body*>(this);
return thisNotConst->getPV_Spin_Lock();
}
const Vector3& getPosFast() const {
RBXASSERT_FISHING(pvIsUpToDate());
return pv.position.translation;
}
const Vector3& getPos() {
updatePV();
return pv.position.translation;
}
const CoordinateFrame& getCoordinateFrameFast() const {
RBXASSERT_FISHING(pvIsUpToDate());
return pv.position;
}
const CoordinateFrame& getCoordinateFrame() {
updatePV();
return pv.position;
}
const Velocity& getVelocity() {
updatePV();
return pv.velocity;
}
bool getCanThrottle() const {
return canThrottle;
}
void accumulateImpulseAtBranchCofm(const Vector3& impulse) {
if (SimBody* s = getRootSimBody()) {
s->accumulateImpulseAtBranchCofm(impulse);
}
}
void accumulateLinearImpulse(const Vector3& impulse, const Vector3& worldPos) {
if (SimBody* s = getRootSimBody()) {
s->accumulateImpulse(impulse, worldPos);
}
}
void accumulateRotationalImpulse(const Vector3& impulse) {
if (SimBody* s = getRootSimBody()) {
s->accumulateRotationalImpulse(impulse);
}
}
void accumulateForceAtBranchCofm(const Vector3& force) {
RBXASSERT(getRoot() == this); // should only be called on Root objects
if (SimBody* s = getRootSimBody()) {
s->accumulateForceCofm(force);
}
}
void accumulateForce(const Vector3& force, const Vector3& worldPos) {
if (SimBody* s = getRootSimBody()) {
s->accumulateForce(force, worldPos);
}
}
void accumulateTorque(const Vector3& torque) {
if (SimBody* s = getRootSimBody()) {
s->accumulateTorque(torque);
}
}
void resetForceAccumulators() {
if (SimBody* s = getRootSimBody()) {
s->resetForceAccumulators();
}
}
void resetImpulseAccumulators() {
if (SimBody* s = getRootSimBody()) {
s->resetImpulseAccumulators();
}
}
const Vector3& getBranchForce() const {
RBXASSERT(getRoot() == this); // should only be called on Root objects
const SimBody* s = getConstRootSimBody();
return s ? s->getForce() : Vector3::zero();
}
const Vector3& getBranchTorque() const {
RBXASSERT(getRoot() == this); // should only be called on Root objects
const SimBody* s = getConstRootSimBody();
return s ? s->getTorque() : Vector3::zero();
}
const Velocity& getBranchVelocity() { // velocity at the COFM of the assembly
RBXASSERT(getRoot() == this); // should only be called on Root objects
const SimBody* s = getRootSimBody();
return s ? s->getPV().velocity : Velocity::zero();
}
/////////////////////////////////////////////////////
// setting properties
//
void setParent(Body* parent) {setIndexedTreeParent(parent);}
void setMeInParent(const CoordinateFrame& _meInParent);
void setMeInParent(Link* _link);
void setMass(float _mass);
void setMoment(const Matrix3& _momentInBody);
void setCofmOffset(const Vector3& _centerOfMassInBody);
// Only Primitive can set these - make sure we keep byproducts updated - fuzzy extents, etc.
// ToDo: Hack - is there a better way to limit access to only primitives, while not including "friend class Primitive"???
void setPv(const PV& _pv, const BodyPvSetter& bpv);
void setCoordinateFrame(const CoordinateFrame& worldCoord, const BodyPvSetter& bpv);
void setVelocity(const Velocity& worldVelocity, const BodyPvSetter& bpv);
void setCanThrottle(bool value, const BodyPvSetter& bpv);
void updateBulletCollisionObject(btCollisionObject* object);
public:
/////////////////////////////////////////////////////////
// Debugging / reporting functions / complex stuff
//
inline bool isLeafBody() const {return leafBodyIndex >= 0;}
float kineticEnergy();
float potentialEnergy();
};
} // namespace
+13
View File
@@ -0,0 +1,13 @@
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
#pragma once
namespace RBX {
// Stub class - primitive descends from this to protect body::setCoordinateFrame() from others
class BodyPvSetter
{
};
} // namespace
+111
View File
@@ -0,0 +1,111 @@
/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "V8Kernel/ContactParams.h"
#include "V8Kernel/PolyConnectors.h"
#include "Util/G3DCore.h"
#include "rbx/Debug.h"
#include "BulletCollision/NarrowphaseCollision/btPersistentManifold.h"
#include "BulletCollision/CollisionDispatch/btCollisionDispatcher.h"
#include "BulletCollision/CollisionDispatch/btCollisionObject.h"
#include "btBulletCollisionCommon.h"
namespace RBX {
class BulletShapeConnector : public PolyConnector,
public Allocator<BulletShapeConnector>
{
protected:
btCollisionObject* bulletCollisionObject0;
btCollisionObject* bulletCollisionObject1;
btCollisionAlgorithm* bulletAlgo;
int bulletManifoldIndex;
int bulletPointCacheIndex;
void updateConnectorPointFromManifold(bool refreshContacts = true);
void realignConnectorsToBulletContacts();
bool foundValidContactPointFromBulletManifold(btPersistentManifold* man, Vector3& p0World, Vector3& p1World);
private:
/*override*/ GeoPairType getConnectorType() const {return BULLET_SHAPE_CONNECTOR;}
bool validObjectCFrames();
virtual void updateBulletCollisionObjects();
public:
BulletShapeConnector(
Body* b0,
Body* b1,
const ContactParams& contactParams,
btCollisionObject* bulletColObj0,
btCollisionObject* bulletColObj1,
btCollisionAlgorithm* algo,
int manifoldIndex,
int cacheIndex
)
: PolyConnector(b0, b1, contactParams, 0, 0)
, bulletCollisionObject0(bulletColObj0)
, bulletCollisionObject1(bulletColObj1)
, bulletAlgo(algo)
, bulletManifoldIndex(manifoldIndex)
, bulletPointCacheIndex(cacheIndex)
{
}
~BulletShapeConnector();
/*override*/ void updateContactPoint();
void findValidContactAfterNarrowphase();
bool recalculateValidPoints(btManifoldArray& btManArray, Vector3& pt0InWorld, Vector3& pt1InWorld);
void setBulletManifoldPointIndex(int index) { bulletPointCacheIndex = index; }
int getBulletManifoldIndex(void) { return bulletManifoldIndex;}
int getBulletPointCacheIndex(void) { return bulletPointCacheIndex;}
void refreshIndividualPoint(bool swapped, Vector3 pt0InWorld, Vector3 pt1InWorld, btManifoldArray& manArray);
void updatePointWithTransform(bool swapped, btManifoldPoint& manifoldPoint);
bool isPointInvalid( btManifoldPoint& manifoldPoint, double validThreshold);
static bool match(BulletShapeConnector* oldCon, BulletShapeConnector* newCon)
{
return ((oldCon->bulletManifoldIndex == newCon->bulletManifoldIndex)
&& (oldCon->bulletPointCacheIndex == newCon->bulletPointCacheIndex)
&& (oldCon->getConnectorType() == newCon->getConnectorType()));
}
};
class BulletShapeCellConnector : public BulletShapeConnector
{
private:
/*override*/ GeoPairType getConnectorType() const {return BULLET_SHAPE_CELL_CONNECTOR;}
/*override*/ void updateBulletCollisionObjects();
public:
BulletShapeCellConnector(
Body* b0,
Body* b1,
const ContactParams& contactParams,
btCollisionObject* bulletColObj0,
btCollisionObject* bulletColObj1,
btCollisionAlgorithm* algo,
int manifoldIndex,
int cacheIndex
)
: BulletShapeConnector(b0, b1, contactParams, bulletColObj0, bulletColObj1, algo, manifoldIndex, cacheIndex)
{
}
~BulletShapeCellConnector() {}
/*override*/ void updateContactPoint();
static bool match(BulletShapeCellConnector* oldCon, BulletShapeCellConnector* newCon)
{
return ((oldCon->bulletManifoldIndex == newCon->bulletManifoldIndex)
&& (oldCon->bulletPointCacheIndex == newCon->bulletPointCacheIndex)
&& (oldCon->getConnectorType() == newCon->getConnectorType()));
}
};
} // namespace RBX
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include "v8kernel/ContactConnector.h"
namespace RBX {
class BuoyancyConnector : public RBX::ContactConnector
{
private:
Vector3 position; // force application point in object space
Vector3 force;
Vector3 torque;
float floatDistance;
float sinkDistance;
float submergeRatio;
protected:
/*override*/ void computeForce( bool throttling );
/*override*/ virtual KernelType getConnectorKernelType() const { return Connector::BUOYANCY; }
public:
void updateContactPoint(); // Only for debug rendering now
const Vector3& getPosition() { return position; }
const Vector3 getWorldPosition();
void setForce( const Vector3& f ) { force = f; }
void setTorque( const Vector3& t ) { torque = t; }
void getWaterBand( float& up, float& down ) { up = floatDistance; down = sinkDistance; }
void setWaterBand( const float& up, const float& down ) { floatDistance = up; sinkDistance = down; }
float getSubMergeRatio() { return submergeRatio; }
void setSubMergeRatio( const float& ratio ) { submergeRatio = ratio; }
BuoyancyConnector(Body* b0, Body* b1, const Vector3& pos);
};
}
+48
View File
@@ -0,0 +1,48 @@
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "Util/G3DCore.h"
#include "Util/Memory.h"
namespace RBX {
class Body;
class Cofm : public Allocator<Cofm>
{
private:
Body* body;
bool dirty;
Vector3 cofmInBody;
float mass;
Matrix3 moment;
void updateIfDirty(); // true if was dirty
public:
Cofm(Body* body);
bool getIsDirty() const {return dirty;}
void makeDirty() {
dirty = true;
}
const Vector3& getCofmInBody() {
updateIfDirty();
return cofmInBody;
}
float getMass() {
updateIfDirty();
return mass;
}
const Matrix3& getMoment() {
updateIfDirty();
return moment;
}
};
} // namespace
+218
View File
@@ -0,0 +1,218 @@
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "Util/NormalID.h"
#include "rbx/Debug.h"
#include "Util/Memory.h"
#include "Util/Math.h"
namespace RBX {
class Point;
class Body;
class Kernel;
class RBXBaseClass Connector
{
friend class KernelData;
friend class Kernel;
private:
int humanoidIndex;
int realTimeIndex;
int secondPassIndex;
int jointIndex;
int buoyancyIndex;
int contactIndex;
protected:
// Used by kernel only. Only add types that KernelData.h can handle.
typedef enum {
CONTACT,
JOINT,
HUMANOID,
KERNEL_JOINT,
BUOYANCY
} KernelType;
/*implement*/ virtual KernelType getConnectorKernelType() const = 0;
public:
int& getHumanoidIndex() {return humanoidIndex;}
int& getRealTimeIndex() {return realTimeIndex;}
int& getSecondPassIndex() {return secondPassIndex;}
int& getJointIndex() {return jointIndex;}
int& getBuoyancyIndex() {return buoyancyIndex;}
int& getContactIndex() {return contactIndex;}
bool isHumanoid() {return humanoidIndex >= 0;}
bool isRealTime() {return realTimeIndex >= 0;}
bool isSecondPass() {return secondPassIndex >= 0;}
bool isJoint() {return jointIndex >= 0;}
bool isBuoyancy() {return buoyancyIndex >= 0;}
bool isContact() {return contactIndex >= 0;}
bool isInKernel() {return isHumanoid() || isRealTime() || isSecondPass() || isJoint() || isBuoyancy() || isContact();}
Connector() : humanoidIndex(-1), realTimeIndex(-1), secondPassIndex(-1),
jointIndex(-1), buoyancyIndex(-1), contactIndex(-1) {}
virtual ~Connector() {}
virtual bool computeCanThrottle();
/////////// Called by kernel //////////////////////////////
virtual void computeForce(bool throttling) = 0;
virtual bool computeImpulse(float& residualVelocity) {return false;}
virtual bool getBroken() {return false;}
typedef enum { body0, body1 } BodyIndex;
virtual Body* getBody(BodyIndex id) = 0;
// DEBUGGING
virtual float potentialEnergy() {return 0.0;}
};
class JointConnector
: public Connector
{
protected:
/*override*/ virtual KernelType getConnectorKernelType() const {return JOINT;}
};
//////////////////////////////////////////////////////////////////////////
// Force = kForce * d_length
// Torque = kTorque * d_angle
// d_length = d_angle * L;
// d_angle= d_length / L
// This should produce an equivalent "force" at a length L from the center.
// so, force at a distance of L is Force = L * torque;
// F = kTorque * d_angle / L = kForce * d_L
// kTorque = kForce * d_L * L * L / d_L = kForce * L * L;
class RotateConnector
: public JointConnector
{
private:
float baseRotation; // rotation when assembled
protected:
Body* b0;
Body* b1;
CoordinateFrame j0;
CoordinateFrame j1;
float k; // spring constant
// Integrator properties
float currentAngle;
float desiredAngle;
float increment;
bool zeroVelocity;
float computeNormalRotation(Vector3& normal);
float computeNormalRotationFromBase(Vector3& normal);
float computeNormalRotationFromBaseFast(Vector3& normal);
virtual void stepGoals();
/*override*/ Body* getBody(BodyIndex id);
/////////// Called by kernel //////////////////////////////
/*override*/ virtual void computeForce(bool throttling);
public:
RotateConnector(
Body* _b0,
Body* _b1,
const CoordinateFrame& _j0,
const CoordinateFrame& _j1,
float _baseAngle,
float kValue,
float armLength);
void reset(); // after networking receive - update to synch internal desiredRotation
void setRotationalGoal(float rotationalGoal);
void setVelocityGoal(float velocity);
static float computeJointAngle(
const CoordinateFrame& b0,
const CoordinateFrame& b1,
const CoordinateFrame& j0,
const CoordinateFrame& j1,
Vector3& normal);
};
//////////////////////////////////////////////////////////////////////////
class PointToPointBreakConnector
: public JointConnector
{
protected:
Point* point0;
Point* point1;
float k; // spring constant
float breakForce;
// state variable
bool broken;
void forceToPoints(const G3D::Vector3& force);
/*override*/ Body* getBody(BodyIndex id);
public:
// initialize
PointToPointBreakConnector(Point* point0, Point* point1, float k, float breakForce) :
point0(point0),
point1(point1),
k(k),
breakForce(breakForce),
broken(false)
{}
/////////// Called by kernel //////////////////////////////
/*override*/ virtual void computeForce(bool throttling);
/*override*/ virtual bool getBroken() { return broken; }
/* override */ virtual float potentialEnergy();
///////// for breakage - one "Joint" may need to break all connectors
inline void setBroken() { broken = true; }
float getStiffness() const { return k; }
void setStiffness( float value ) { k = value; }
};
//////////////////////////////////////////////////////////////////////////
// TODO: update normal very infrequently....
class NormalBreakConnector
: public PointToPointBreakConnector
, public Allocator<NormalBreakConnector>
{
private:
NormalId normalIdBody0;
public:
NormalBreakConnector(
Point* point0,
Point* point1,
float k,
float breakForce,
NormalId normalIdBody0)
: PointToPointBreakConnector(point0, point1, k, breakForce)
, normalIdBody0(normalIdBody0)
{}
/////////// Called by kernel //////////////////////////////
/*override*/ virtual void computeForce(bool throttling);
};
} // namespace
+62
View File
@@ -0,0 +1,62 @@
/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "Util/G3DCore.h"
namespace RBX {
class Constants {
private:
static const int JOINT_FORCE_DATA = 7;
static const float MAX_LEGO_JOINT_FORCES_THEORY[JOINT_FORCE_DATA];
static const float MAX_LEGO_JOINT_FORCES_MEASURED[JOINT_FORCE_DATA];
static float LEGO_GRID_MASS(); // kg
static float LEGO_JOINT_K(); // kg/s^2
static float LEGO_DEFAULT_ELASTIC_K();
static float unitJointK();
static float getJointKMultiplier(const Vector3& clippedSortedSize, bool ball);
Constants();
public:
///////////////////////////////////////////////////////////////////
//
// Timestep related stuff
//
static int uiStepsPerSec() {return longUiStepsPerSec() * 2;}
static int worldStepsPerUiStep();
static int longUiStepsPerSec() {return 30;}
static int worldStepsPerLongUiStep();
static int kernelStepsPerWorldStep();
static int freeFallStepsPerWorldStep();
static int worldStepsPerSec();
static int kernelStepsPerSec();
static int kernelStepsPerUiStep();
static int freeFallStepsPerSec();
static int impulseSolverMaxIterations();
static float impulseSolverAccuracy();
static int impulseSolverAccuracyScalar();
static float impulseSolverSymStateTorqueBound();
static float impulseSolverSymStateForceBound();
static float uiDt();
static float longUiStepDt();
static float worldDt();
static float kernelDt();
static float freeFallDt();
static const Vector3& denormalSmall();
//////////////////////////////////////////////////////////////////
//
// Dimenensions and K related stuff
//
static inline float getKmsGravity() {return -9.81f;}
static float getKmsMaxJointForce(float grid1, float grid2);
static float getElasticMultiplier(float elasticity);
static float getJointK(const Vector3& size, bool ball); // kg/s^2
};
} // namespace
+220
View File
@@ -0,0 +1,220 @@
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "V8Kernel/Connector.h"
#include "V8Kernel/ContactParams.h"
#include "V8Kernel/Pair.h"
#include "V8Kernel/Body.h"
#include "v8kernel/SimBody.h"
#include "v8kernel/Constants.h"
#include "Util/NormalID.h"
#include "rbx/Debug.h"
#include "Util/Memory.h"
namespace RBX {
//////////////////////////////////////////////////////////////////////////
class ContactConnector : public Connector
{
private:
static int inContactHit;
static int outOfContactHit;
int age;
// Cache for computeImpulse
Matrix3 deltaVelPerUnitImpulse;
Matrix3 impulsePerUnitDeltaVel;
float inverseMass;
float penetrationVelocity;
float reboundVelocity;
bool impulseComputed;
protected:
GeoPair geoPair;
ContactParams contactParams;
PairParams oldContactPoint;
PairParams contactPoint;
// state variables
float firstApproach;
float threshold;
// delay variables
float forceMagLast; // contact only variable
Vector3 frictionOffset;
/*override*/ virtual KernelType getConnectorKernelType() const { return Connector::CONTACT; }
public:
virtual void updateContactPoint();
static float overlapGoal() {return 0.01f;} // standard goal seek for overlapping objects
/*override*/ Body* getBody(BodyIndex id) {return (id == body0) ? geoPair.body0 : geoPair.body1;}
void setBody(int id, Body* b) {
if (id == 0) {
geoPair.body0 = b;
}
else {
geoPair.body1 = b;
}
}
ContactConnector(Body* b0, Body* b1, const ContactParams& contactParams)
: contactParams(contactParams), inverseMass(0.0f), impulseComputed(false)
{
geoPair.body0 = b0;
geoPair.body1 = b1;
reset();
}
void reset() { // cleans up state variables for buffered version
firstApproach = 0.0;
threshold = 0.0;
forceMagLast = 0.0;
age = 0;
penetrationVelocity = 0.0;
reboundVelocity = 0.0;
}
inline void clearImpulseComputed() { impulseComputed = false; }
bool isIntersecting() {
RBXASSERT(geoPair.geoPairType == POINT_PLANE_PAIR);
return (contactPoint.length < -overlapGoal());
}
// Reorder the SimBody(s) so that simBody0 is always in kernel and adjust contact point data accordingly
bool getReordedSimBody(SimBody*& simBody0, SimBody*& simBody1, Body*& bodyNotInKernel, PairParams& params);
bool getReordedSimBody(SimBody*& simBody0, SimBody*& simBody1, PairParams& params);
// Compute the relative velocities between the two bodies
bool getSimBodyAndContactVelocity(SimBody*& simBody0, SimBody*& simBody1, PairParams& params,
float& normalVel, Vector3& perpVel);
float computeRelativeVelocity(const PairParams &params, Vector3* deltaVnormal, Vector3* perpVel);
float computeRelativeVelocity();
void applyContactPointForSymmetryDetection(SimBody* simBody0, SimBody* simBody1,
const PairParams& params, float direction);
const ContactParams& getContactParams() const { return contactParams; }
void setContactParams(const ContactParams& params) { contactParams = params; }
/////////// Called by kernel //////////////////////////////
/* override*/ virtual void computeForce(bool throttling);
/* override*/ virtual bool computeImpulse(float& residualVelocity);
/* override*/ bool canThrottle() const;
// Debug
static float percentActive();
float computeOverlap() { // positive == bigger overlap
updateContactPoint();
return -contactPoint.length;
}
inline PairParams& getContactPoint() { return contactPoint; }
inline const PairParams& getContactPoint() const { return contactPoint; }
void getLengthNormalPosition(Vector3& position, Vector3& normal, float& length) {
position = contactPoint.position;
normal = contactPoint.normal;
length = contactPoint.length;
}
inline bool isRestingContact() { return age > 4; }
};
class GeoPairConnector
: public ContactConnector
, public Allocator<GeoPairConnector>
{
public:
GeoPairConnector(Body* b0, Body* b1, const ContactParams& contactParams) : ContactConnector(b0, b1, contactParams)
{}
//////////////////////////////////////////////////////////////////////////////////
/*override*/ void updateContactPoint()
{
geoPair.computeLengthNormalPosition(contactPoint);
ContactConnector::updateContactPoint();
}
void setPointPlane(const Vector3* oPoint, const Vector3* oPlane,
int pointId, NormalId planeId)
{
geoPair.setPointPlane(oPoint, oPlane, pointId, planeId);
}
void setEdgeEdgePlane(const Vector3* e0, const Vector3* e1,
NormalId n0, NormalId n1, NormalId planeId, float edgeLength0, float edgeLength1)
{
geoPair.setEdgeEdgePlane(e0, e1, n0, n1, planeId, edgeLength0, edgeLength1);
}
void setEdgeEdge(const Vector3* e0, const Vector3* e1, NormalId n0, NormalId n1)
{
geoPair.setEdgeEdge(e0, e1, n0, n1);
}
bool match(Body* b0, Body* b1, GeoPairType pairType, int param0, int param1)
{
return geoPair.match(b0, b1, pairType, param0, param1);
}
};
////////////////////////////////////////////////////////////////////////////////
class BallBallConnector : public ContactConnector
, public Allocator<BallBallConnector>
{
private:
float radius0;
float radiusSum;
public:
BallBallConnector(Body* b0, Body* b1, const ContactParams& contactParams)
: ContactConnector(b0, b1, contactParams)
{}
/*override*/ void updateContactPoint();
void setRadius(float r0, float r1) {
radius0 = r0;
radiusSum = r0 + r1;
}
};
////////////////////////////////////////////////////////////////////////////////
class BallBlockConnector : public ContactConnector,
public Allocator<BallBlockConnector>
{
private:
float radius0; // ball
Vector3 offset1;
NormalId normalId1;
GeoPairType geoPairType;
void computeBallPoint(PairParams& params);
void computeBallEdge(PairParams& params);
void computeBallPlane(PairParams& params);
public:
BallBlockConnector(Body* b0, Body* b1, const ContactParams& contactParams)
: ContactConnector(b0, b1, contactParams)
{}
/*override*/ void updateContactPoint();
void setBallBlock(float _radius0, const Vector3* _offset1, RBX::NormalId _normalID, GeoPairType _geoPairType) {
offset1 = *_offset1;
radius0 = _radius0;
normalId1 = _normalID;
geoPairType = _geoPairType;
}
};
} // namespace
+44
View File
@@ -0,0 +1,44 @@
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
#pragma once
namespace RBX {
class ContactParams {
public:
float kSpring; // spring constant
float kNeg; // elastic - spring constant on bounceback
float kFriction; // contact only variable stored as true value * -0.5;???
float kElasticity;
ContactParams()
: kSpring(0.0)
, kFriction(0.0)
, kNeg(0.0)
, kElasticity(0.0f)
{}
};
enum GeoPairType { BALL_POINT_PAIR, // BALL to BLOCK
BALL_EDGE_PAIR,
BALL_PLANE_PAIR,
// BLOCK to BLOCK
POINT_PLANE_PAIR,
EDGE_EDGE_PLANE_PAIR, // two edges, plane needed to supply normal
EDGE_EDGE_PAIR, // two edges, guaranteed to be overlapping
VERTEX_PLANE_CONNECTOR,
EDGE_EDGE_CONNECTOR,
EDGE_EDGE_PLANE_CONNECTOR,
BALL_VERTEX_CONNECTOR,
BALL_EDGE_CONNECTOR,
BALL_PLANE_CONNECTOR,
BULLET_SHAPE_CONNECTOR,
BULLET_SHAPE_CELL_CONNECTOR };
} // namespace
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "RBX/Debug.h"
// Engine assertions often cause the game to stop running.
// Until we can fix these, turn them off.
//#define RBX_DEBUGENGINE
#ifdef RBX_DEBUGENGINE
#define RBX_ENGINE_ASSERT(expr) RBXASSERT(expr)
#else
#define RBX_ENGINE_ASSERT(expr) ((void)0)
#endif
+74
View File
@@ -0,0 +1,74 @@
/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "rbx/Debug.h"
#include "Util/G3DCore.h"
namespace RBX {
class Kernel;
class RBXBaseClass IStage {
public:
typedef enum { CLEAN_STAGE,
JOINT_STAGE,
GROUND_STAGE,
EDGE_STAGE,
CONTACT_STAGE,
TREE_STAGE,
MOVING_STAGE,
SPATIAL_FILTER,
MECH_TO_ASSEMBLY_STAGE,
ASSEMBLY_STAGE,
MOVING_ASSEMBLY_STAGE,
STEP_JOINTS_STAGE,
HUMANOID_STAGE,
SLEEP_STAGE,
SIMULATE_STAGE,
KERNEL_STAGE} StageType;
private:
IStage* upstream;
IStage* downstream;
const IStage* findStageImpl(StageType stageType) const {
const IStage* answer = this;
while (answer->getStageType() != stageType) {
answer = answer->getDownstream();
}
return answer;
}
public:
IStage(IStage* upstream, IStage* downstream)
: upstream(upstream), downstream(downstream)
{}
virtual ~IStage() {
if (downstream) {
delete downstream;
}
}
IStage* getUpstream() {return upstream;}
IStage* getDownstream() {return downstream;}
const IStage* getDownstream() const {return downstream;}
virtual StageType getStageType() const = 0;
const IStage* findStage(StageType stageType) const {
return findStageImpl(stageType);
}
IStage* findStage(StageType stageType) {
return const_cast<IStage*>(findStageImpl(stageType));
}
virtual Kernel* getKernel() {
RBXASSERT(downstream);
return downstream->getKernel();
}
};
} // namespace
+128
View File
@@ -0,0 +1,128 @@
#pragma once
#include "V8Kernel/IStage.h"
#include "V8Kernel/BodyPvSetter.h"
#include "boost/scoped_ptr.hpp"
#include "solver/Solver.h"
namespace RBX {
namespace Profiling
{
class CodeProfiler;
}
class Connector;
class Body;
class Point;
class KernelData;
class Kernel : public IStage,
public BodyPvSetter
{
private:
static int numKernels;
int maxBodies;
bool inStepCode;
int numLastIterations;
int numOfMaxIterations;
float error;
float maxError;
bool validateBody(Body* b);
bool validateConnector(Connector* connector) const;
bool validateConnectorBody(Body* b) const;
KernelData* kernelData;
// Funny Physics
void stepWorldFunnyPhysics(int worldStepId);
void stepFunnyPhysics(const Vector3& move);
void stepFunnyPhysicsBody(Body* b, const Vector3& move);
Point* searchForDuplicatePoint(Point* tempPoint);
void preStep();
void preStepThrottled();
void stepWorld( boost::uint64_t distDebugTime );
void stepWorldThrottled( boost::uint64_t debugTime );
bool usingPGSSolver;
public:
PGSSolver pgsSolver;
Kernel(IStage* upstream);
~Kernel();
///////////////////////////////////////////
// IStage
/*override*/ IStage::StageType getStageType() const {return IStage::KERNEL_STAGE;}
/*override*/ Kernel* getKernel() {return this;}
void step(bool throttling, int numThreads, boost::uint64_t debugTime);
void insertBody(Body* b);
void insertPoint(Point* p);
void insertConnector(Connector* c);
void removeBody(Body* b);
void removePoint(Point* p);
void removeConnector(Connector* c);
// double up on points if same body, position....
// TODO: move this to the point class - reference counted pointer
//
Point* newPointLocal(class Body* _body, const Vector3& worldPos);
Point* newPoint(class Body* _body, const Vector3& worldPos);
void deletePoint(class Point* point);
////////////////////////////////////////////////////////
//
// Debugging Stuff
//
void report(); // system energy to log file
static void reportMemorySizes();
float connectorSpringEnergy() const;
float bodyPotentialEnergy() const;
float bodyKineticEnergy() const;
float totalEnergy() const {
return connectorSpringEnergy() + bodyPotentialEnergy() + bodyKineticEnergy();
}
float totalKineticEnergy() const {
return connectorSpringEnergy() + bodyKineticEnergy();
}
int numFreeFallBodies() const;
int numRealTimeBodies() const;
int numJointBodies() const;
int numContactBodies() const;
int numBodies() const {return numFreeFallBodies() + numRealTimeBodies() + numJointBodies() + numContactBodies();}
int numBodiesMax() const {return maxBodies;}
int numLeafBodies() const;
int numPoints() const;
int numConnectors() const;
int numHumanoidConnectors() const;
int numRealTimeConnectors() const;
int numSecondPassConnectors() const;
int numJointConnectors() const;
int numBuoyancyConnectors() const;
int numContactConnectors() const;
inline int numIterations() const {return numLastIterations;}
inline int numMaxIterations() const {return numOfMaxIterations;}
inline float getSolverError() const {return error;}
inline float getMaxSolverError() const {return maxError;}
int fakeDeceptiveSolverIterations() const;
int fakeDeceptiveMatrixSize() const;
///////////////////////////////////////////
// Profiler
boost::scoped_ptr<Profiling::CodeProfiler> profilingKernelBodies;
boost::scoped_ptr<Profiling::CodeProfiler> profilingKernelConnectors;
void setUsingPGSSolver(bool pgsOn) { usingPGSSolver = pgsOn; }
bool getUsingPGSSolver() const { return usingPGSSolver; }
void dumpLog( bool enable ) { pgsSolver.dumpLog( enable ); }
};
} // namespace
+380
View File
@@ -0,0 +1,380 @@
#pragma once
#include "v8kernel/Body.h"
#include "V8Kernel/SimBody.h"
#include "V8Kernel/Point.h"
#include "V8Kernel/Connector.h"
#include "V8Kernel/ContactConnector.h"
#include "V8Kernel/BuoyancyConnector.h"
#include "V8kernel/Constants.h"
#include "V8datamodel/FastLogSettings.h"
namespace RBX {
class KernelData {
public:
// main object types
IndexArray<SimBody, &SimBody::getFreeFallBodyIndex> freeFallBodies; // bodies with no connectors
IndexArray<SimBody, &SimBody::getRealTimeBodyIndex> realTimeBodies; // humanoid bodies that are not throttle-able
IndexArray<SimBody, &SimBody::getJointBodyIndex> jointBodies; // bodies with joint connectors
IndexArray<SimBody, &SimBody::getContactBodyIndex> contactBodies; // bodies with contact connectors but no joint connectors
IndexArray<Body, &Body::getLeafBodyIndex> leafBodies; // need update PV every step, NOT in kernel!
IndexArray<Point, &Point::getKernelIndex> points;
IndexArray<Connector, &Connector::getHumanoidIndex> humanoidConnectors; // The humanoids
IndexArray<Connector, &Connector::getSecondPassIndex> secondPassConnectors; // kernel joints
IndexArray<Connector, &Connector::getRealTimeIndex> realTimeConnectors; // connectors on humanoid body parts
IndexArray<Connector, &Connector::getJointIndex> jointConnectors;
IndexArray<Connector, &Connector::getBuoyancyIndex> buoyancyConnectors;
IndexArray<Connector, &Connector::getContactIndex> contactConnectors;
KernelData()
{
}
~KernelData() {
RBXASSERT(freeFallBodies.size() == 0);
RBXASSERT(realTimeBodies.size() == 0);
RBXASSERT(jointBodies.size() == 0);
RBXASSERT(contactBodies.size() == 0);
RBXASSERT(leafBodies.size() == 0);
RBXASSERT(points.size() == 0);
RBXASSERT(humanoidConnectors.size() == 0);
RBXASSERT(secondPassConnectors.size() == 0);
RBXASSERT(realTimeConnectors.size() == 0);
RBXASSERT(jointConnectors.size() == 0);
RBXASSERT(buoyancyConnectors.size() == 0);
RBXASSERT(contactConnectors.size() == 0);
}
inline void addLeafBodies(Body* b)
{
for (int i = 0; i < b->numChildren(); ++i)
{
Body* child = b->getChild(i);
RBXASSERT(!child->isLeafBody());
RBXASSERT(child != child->getRoot());
if (child->connectorUseCount > 0)
{
addLeafBody(child);
}
addLeafBodies(child);
}
}
inline void insertBody(Body* b)
{
RBXASSERT(b->getRoot() == b);
SimBody* simBody = b->getSimBody();
RBXASSERT(!b->isLeafBody() && !simBody->isInKernel());
addBodyToNewList(simBody);
RBXASSERT(simBody->validateBodyLists());
}
inline void removeBody(Body* b)
{
RBXASSERT(b->getRoot() == b);
RBXASSERT(!b->isLeafBody());
SimBody* simBody = b->getSimBody();
removeBodyFromCurrentList(simBody);
simBody->clearSymStateAndAccummulator();
RBXASSERT(simBody->validateBodyLists());
}
inline void addConnector(Connector* c, bool pgsOn)
{
RBXASSERT(!c->isInKernel());
Body* body0 = c->getBody(Connector::body0);
Body* body1 = c->getBody(Connector::body1);
SimBody* simBody0 = body0 ? body0->getRootSimBody() : NULL;
SimBody* simBody1 = body1 ? body1->getRootSimBody() : NULL;
Connector::KernelType connectorType = c->getConnectorKernelType();
if ((simBody0 == NULL || !simBody0->isInKernel()) &&
(simBody1 == NULL || !simBody1->isInKernel()) &&
connectorType != Connector::HUMANOID &&
connectorType != Connector::JOINT &&
connectorType != Connector::KERNEL_JOINT)
return;
if (connectorType == Connector::HUMANOID)
{
humanoidConnectors.fastAppend(c);
if (simBody0)
simBody0->incrementHumanoidConnectorCount();
if (simBody1)
simBody1->incrementHumanoidConnectorCount();
}
else if (connectorType == Connector::KERNEL_JOINT)
{
secondPassConnectors.fastAppend(c);
if (simBody0)
simBody0->incrementSecondPassConnectorCount();
if (simBody1)
simBody1->incrementSecondPassConnectorCount();
}
else if (pgsOn && connectorType == Connector::JOINT)
{
jointConnectors.fastAppend(c);
if (simBody0)
simBody0->incrementJointConnetorCount();
if (simBody1)
simBody1->incrementJointConnetorCount();
}
else if (pgsOn && connectorType == Connector::BUOYANCY)
{
buoyancyConnectors.fastAppend(c);
if (simBody0)
simBody0->incrementBuoyancyConnectorCount();
if (simBody1)
simBody1->incrementBuoyancyConnectorCount();
}
else if ((simBody0 && !simBody0->getBody()->getCanThrottle()) ||
(simBody1 && !simBody1->getBody()->getCanThrottle()))
{
realTimeConnectors.fastAppend(c);
if (simBody0)
simBody0->incrementRealTimeConnectorCount();
if (simBody1)
simBody1->incrementRealTimeConnectorCount();
}
else if (!pgsOn &&
(connectorType == Connector::JOINT ||
connectorType == Connector::BUOYANCY ||
((simBody0 && simBody0->isJointBody()) || // Contact connectors in touch with joint bodies
(simBody1 && simBody1->isJointBody())))) // are considered joint connectors.
{
jointConnectors.fastAppend(c);
if (simBody0)
simBody0->incrementJointConnetorCount();
if (simBody1)
simBody1->incrementJointConnetorCount();
}
else
{
RBXASSERT(connectorType == Connector::CONTACT);
contactConnectors.fastAppend(c);
if (simBody0)
simBody0->incrementContactConnectorCount();
if (simBody1)
simBody1->incrementContactConnectorCount();
}
if (body0)
addConnectorToBody(c, body0);
if (body1)
addConnectorToBody(c, body1);
RBXASSERT(simBody0 == NULL || simBody0->validateBodyLists());
RBXASSERT(simBody1 == NULL || simBody1->validateBodyLists());
}
inline void removeConnector(Connector* c)
{
if (!c->isInKernel())
return;
Body* body0 = c->getBody(Connector::body0);
Body* body1 = c->getBody(Connector::body1);
SimBody* simBody0 = body0 ? body0->getRootSimBody() : NULL;
SimBody* simBody1 = body1 ? body1->getRootSimBody() : NULL;
if (c->isHumanoid())
{
humanoidConnectors.fastRemove(c);
if (simBody0)
simBody0->decrementHumanoidConnectorCount();
if (simBody1)
simBody1->decrementHumanoidConnectorCount();
}
else if (c->isSecondPass())
{
secondPassConnectors.fastRemove(c);
if (simBody0)
simBody0->decrementSecondPassConnectorCount();
if (simBody1)
simBody1->decrementSecondPassConnectorCount();
}
else if (c->isRealTime())
{
realTimeConnectors.fastRemove(c);
if (simBody0)
simBody0->decrementRealTimeConnectorCount();
if (simBody1)
simBody1->decrementRealTimeConnectorCount();
}
else if (c->isJoint())
{
jointConnectors.fastRemove(c);
if (simBody0)
simBody0->decrementJointConnetorCount();
if (simBody1)
simBody1->decrementJointConnetorCount();
}
else if (c->isBuoyancy())
{
buoyancyConnectors.fastRemove(c);
if (simBody0)
simBody0->decrementBuoyancyConnectorCount();
if (simBody1)
simBody1->decrementBuoyancyConnectorCount();
}
else
{
RBXASSERT(c->isContact());
contactConnectors.fastRemove(c);
if (simBody0)
simBody0->decrementContactConnectorCount();
if (simBody1)
simBody1->decrementContactConnectorCount();
ContactConnector* conn = static_cast<ContactConnector*>(c);
PairParams params = conn->getContactPoint();
if (conn->getReordedSimBody(simBody0, simBody1, params))
conn->applyContactPointForSymmetryDetection(simBody0, simBody1, params, -1.0f);
}
if (body0)
removeConnectorFromBody(c, body0);
if (body1)
removeConnectorFromBody(c, body1);
RBXASSERT(simBody0 == NULL || simBody0->validateBodyLists());
RBXASSERT(simBody1 == NULL || simBody1->validateBodyLists());
}
private:
inline void addLeafBody(Body* b)
{
RBXASSERT(!b->isLeafBody());
RBXASSERT(b->connectorUseCount > 0);
leafBodies.fastAppend(b);
RBXASSERT(b->isLeafBody());
}
inline void removeLeafBody(Body* b)
{
RBXASSERT(b->isLeafBody());
leafBodies.fastRemove(b);
RBXASSERT(!b->isLeafBody());
}
inline void removeLeafBodies(Body* b)
{
for (int i = 0; i < b->numChildren(); ++i)
{
Body* child = b->getChild(i);
if (child->isLeafBody())
{
removeLeafBody(child);
}
removeLeafBodies(child);
}
}
inline void removeBodyFromCurrentList(SimBody* simBody)
{
if (simBody->isRealTimeBody())
{
removeLeafBodies(simBody->getBody());
realTimeBodies.fastRemove(simBody);
} else if (simBody->isJointBody())
{
removeLeafBodies(simBody->getBody());
jointBodies.fastRemove(simBody);
} else if (simBody->isContactBody())
{
contactBodies.fastRemove(simBody);
} else if (simBody->isFreeFallBody())
{
freeFallBodies.fastRemove(simBody);
simBody->updateAngMomentum();
} else
return;
simBody->setDt(0.0f);
}
inline void addBodyToNewList(SimBody* simBody)
{
if (!simBody->getBody()->getCanThrottle())
{
if (!simBody->isRealTimeBody())
{
removeBodyFromCurrentList(simBody);
realTimeBodies.fastAppend(simBody);
simBody->setDt(Constants::kernelDt());
addLeafBodies(simBody->getBody());
}
} else if (simBody->getJointConnectorCount() > 0)
{
if (!simBody->isJointBody())
{
removeBodyFromCurrentList(simBody);
jointBodies.fastAppend(simBody);
simBody->setDt(Constants::kernelDt());
addLeafBodies(simBody->getBody());
}
} else if (simBody->getContactConnectorCount() > 0)
{
if (!simBody->isContactBody())
{
removeBodyFromCurrentList(simBody);
contactBodies.fastAppend(simBody);
simBody->setDt(Constants::freeFallDt());
}
} else
{
if (simBody->getConnectorCount() == 0)
{
if (!simBody->isFreeFallBody())
{
removeBodyFromCurrentList(simBody);
freeFallBodies.fastAppend(simBody);
simBody->setDt(Constants::freeFallDt());
simBody->clearSymStateAndAccummulator();
}
} else
{
if (!simBody->isJointBody())
{
removeBodyFromCurrentList(simBody);
jointBodies.fastAppend(simBody);
simBody->setDt(Constants::kernelDt());
addLeafBodies(simBody->getBody());
}
}
}
}
inline void addConnectorToBody(Connector* c, Body* body)
{
body->connectorUseCount++;
SimBody* simBody = body->getRootSimBody();
if (!simBody->isInKernel())
return;
addBodyToNewList(simBody);
// adds leaf bodies if root is already here and it is subject to the spring solver
if (body != body->getRoot() && !body->isLeafBody() &&
(simBody->isJointBody() || simBody->isRealTimeBody()))
addLeafBody(body);
}
inline void removeConnectorFromBody(Connector* c, Body* body)
{
body->connectorUseCount--;
if (body->isLeafBody() && body->connectorUseCount == 0)
removeLeafBody(body);
SimBody* simBody = body->getRootSimBody();
if (!simBody->isInKernel())
return;
addBodyToNewList(simBody);
}
};
} // namespace
+26
View File
@@ -0,0 +1,26 @@
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "rbx/Debug.h"
namespace RBX {
class KernelIndex {
protected:
int kernelIndex;
public:
bool indexInKernel() const {
return (kernelIndex != -1);
}
KernelIndex() : kernelIndex(-1)
{}
~KernelIndex() {
RBXASSERT(!indexInKernel());
}
};
} // namespace
+86
View File
@@ -0,0 +1,86 @@
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "Util/G3DCore.h"
#include "rbx/Declarations.h"
#include "Util/Memory.h"
#include "Util/Math.h"
namespace RBX {
class Body;
class RBXBaseClass Link
{
friend class Body;
protected:
Body* body; // body I'm affilliated with (child)
CoordinateFrame parentCoord;
CoordinateFrame childCoord;
CoordinateFrame childCoordInverse;
CoordinateFrame childInParent;
unsigned int stateIndex;
virtual void computeChildInParent(CoordinateFrame& answer) const = 0;
void dirty();
void setBody(Body* b) {body = b;}
public:
Link();
~Link();
const CoordinateFrame& getChildInParent();
Body* getBody() const {return body;}
void reset(
const CoordinateFrame& parentC,
const CoordinateFrame& childC);
};
class RevoluteLink
: public Link
, public Allocator<RevoluteLink>
{
private:
float jointAngle;
/*override*/ void computeChildInParent(CoordinateFrame& answer) const;
public:
RevoluteLink() : jointAngle(0.0f)
{
}
void setJointAngle(float value) {
jointAngle = value;
dirty();
}
};
class D6Link
: public Link
, public Allocator<D6Link>
{
private:
CoordinateFrame offsetCFrame;
/*override*/ void computeChildInParent(CoordinateFrame& answer) const;
public:
void setJointOffsetCFrame(const CoordinateFrame& value) {
offsetCFrame = value;
RBXASSERT(!Math::hasNanOrInf(value));
dirty();
}
};
} // namespace
+148
View File
@@ -0,0 +1,148 @@
/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "V8Kernel/ContactParams.h"
#include "Util/NormalID.h"
#include "Util/G3DCore.h"
#include "rbx/Debug.h"
namespace RBX {
class Body;
class PairParams {
public:
Vector3 normal;
union {
float length;
float rotation;
};
Vector3 position;
PairParams() {
normal = position = Vector3::zero();
length = 0.0f;
}
bool operator==(const PairParams& other) {
return (length == other.length && position == other.position && normal == other.normal);
}
};
///////////////////////////////////////////////////////////////////
class GeoPair
{
public:
GeoPairType geoPairType;
// note, pair point0 and point1 have the following polarity
// ball ball: radius0 == point0 body
// ball block: ball->point0, block->point1
// point plane: pointBlock->0, planeBlock->1
// edge edge plane: planeBlock->1
// fixed, not allocated here
// This is defining data
Vector3 offset0;
Vector3 offset1;
Body* body0;
Body* body1;
float edgeLength0;
float edgeLength1;
struct {
union {
RBX::NormalId normalID0;
float radius0; };
union {
RBX::NormalId normalID1;
float radiusSum; };
union {
RBX::NormalId planeID;// edge/edge/plane coords - the normal from the plane
int point0ID; };
} pairData;
private:
void computePointPlane(PairParams& _params);
void computeEdgeEdgePlane(PairParams& _params);
void computeEdgeEdgePlane2(PairParams& _params);
void computeEdgeEdge(PairParams& _params);
public:
GeoPair();
////////// Kernel Update
inline void computeLengthNormalPosition(PairParams& _params)
{
switch (geoPairType) {
case (POINT_PLANE_PAIR): computePointPlane(_params); break;
case (EDGE_EDGE_PLANE_PAIR): computeEdgeEdgePlane2(_params); break;
case (EDGE_EDGE_PAIR): computeEdgeEdge(_params); break;
default: RBXASSERT(0);
}
}
///////////////// GeoPair geometric functions
void setPointPlane(const Vector3* _offsetPoint,
const Vector3* _offsetPlane, int _pointID, RBX::NormalId _planeNormalID) {
offset0 = *_offsetPoint;
offset1 = *_offsetPlane;
pairData.point0ID = _pointID; // purely here for the match
pairData.normalID1 = _planeNormalID;
geoPairType = POINT_PLANE_PAIR;
}
void setEdgeEdgePlane(const Vector3* _edge0, const Vector3* _edge1,
RBX::NormalId _normal0, RBX::NormalId _normal1, RBX::NormalId _planeID, float _edgeLength0, float _edgeLength1) {
offset0 = *_edge0;
offset1 = *_edge1;
pairData.normalID0 = _normal0;
pairData.normalID1 = _normal1;
pairData.planeID = _planeID;
edgeLength0 = _edgeLength0;
edgeLength1 = _edgeLength1;
geoPairType = EDGE_EDGE_PLANE_PAIR;
}
void setEdgeEdge(const Vector3* _edge0, const Vector3* _edge1,
RBX::NormalId _normal0, RBX::NormalId _normal1) {
offset0 = *_edge0;
offset1 = *_edge1;
pairData.normalID0 = _normal0;
pairData.normalID1 = _normal1;
geoPairType = EDGE_EDGE_PAIR;
}
bool match(Body* _b0, Body* _b1, GeoPairType _pairType, int param0, int param1) {
if (_pairType == POINT_PLANE_PAIR) {
return ( (_b0 == body0)
&& (_b1 == body1)
&& (param0 == pairData.point0ID)
&& (param1 == pairData.normalID1) );
}
else if (_pairType == EDGE_EDGE_PLANE_PAIR) {
return ( (_b0 == body0)
&& (_b1 == body1)
&& (param0 == pairData.normalID0)
&& (param1 == pairData.normalID1) );
}
else {
RBXASSERT(_pairType == EDGE_EDGE_PAIR);
return ( ( (_b0 == body0)
&& (_b1 == body1)
&& (param0 == pairData.normalID0)
&& (param1 == pairData.normalID1) )
||
( (_b0 == body1)
&& (_b1 == body0)
&& (param0 == pairData.normalID1)
&& (param1 == pairData.normalID0) )
);
}
}
};
} // namespace RBX
+83
View File
@@ -0,0 +1,83 @@
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "V8Kernel/KernelIndex.h"
#include "Util/G3DCore.h"
namespace RBX {
class Body;
class Point
: public KernelIndex
{
friend class KernelData;
friend class Kernel;
private:
int& getKernelIndex() {return kernelIndex;}
int numOwners;
protected:
Body* body;
// constant
Vector3 localPos;
// auxillary variables, computed on every frame
Vector3 worldPos;
// accumulated quantities;
Vector3 force;
// This is private - only created by the kernel
Point(Body* _body = NULL);
virtual ~Point()
{}
public: // all points from same allocator, size of AttachPoint
static bool sameBodyAndOffset(const Point& p0, const Point& p1) {
return ((p0.body == p1.body) && (p0.localPos == p1.localPos));
}
//////////// called by kernel every step
//
// Updates World Position, Clears Accumulator
void step();
// force accumulation
void accumulateForce(const Vector3& _force) {
force += _force;
}
// corresponds to "for each Point, accumulate forces to Body"
void forceToBody();
void setLocalPos(const Vector3& _localPos);
void setWorldPos(const Vector3& _worldPos);
void setBody(Body* _body) {
body = _body;
}
//////////// inquiry
Body* getBody() {
return body;
}
const Vector3& getWorldPos() {
return worldPos;
}
};
} // namespace RBX
+218
View File
@@ -0,0 +1,218 @@
/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "V8Kernel/ContactParams.h"
#include "V8Kernel/ContactConnector.h"
#include "Util/G3DCore.h"
#include "rbx/Debug.h"
namespace RBX {
//////////////////////////////////////////////////////////////////////////////////
//
// BLOCK BLOCK Types
class PolyConnector : public ContactConnector
{
private:
int param0;
int param1; // for matching
protected:
/*implement*/ virtual GeoPairType getConnectorType() const = 0;
PolyConnector(
Body* b0,
Body* b1,
const ContactParams& contactParams,
int param0,
int param1)
: ContactConnector(b0, b1, contactParams)
, param0(param0)
, param1(param1)
{}
public:
static bool match(PolyConnector* p0, PolyConnector* p1) {
return ( (p0->param0 == p1->param0)
&& (p0->param1 == p1->param1)
&& (p0->getConnectorType() == p1->getConnectorType()) );
}
};
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
class FaceVertexConnector : public PolyConnector,
public Allocator<FaceVertexConnector>
{
private:
Plane facePlane;
Vector3 vertexOffset;
/*override*/ GeoPairType getConnectorType() const {return VERTEX_PLANE_CONNECTOR;}
public:
FaceVertexConnector(
Body* b0,
Body* b1,
const ContactParams& contactParams,
const Plane& facePlane,
const Vector3& vertexOffset,
int planeId,
int vertexId)
: PolyConnector(b0, b1, contactParams, planeId, vertexId)
, facePlane(facePlane)
, vertexOffset(vertexOffset)
{}
/*override*/ void updateContactPoint();
};
class FaceEdgeConnector : public PolyConnector,
public Allocator<FaceEdgeConnector>
{
private:
Plane facePlane;
Plane sideFacePlane;
Line faceLine;
Line edgeLine;
/*override*/ GeoPairType getConnectorType() const {return EDGE_EDGE_PLANE_CONNECTOR;}
public:
FaceEdgeConnector(
Body* b0,
Body* b1,
const ContactParams& contactParams,
const Plane& facePlane,
const Plane& sideFacePlane,
Line faceLine,
Line edgeLine,
const int faceId,
const int edgeId)
: PolyConnector(b0, b1, contactParams, faceId, edgeId)
, facePlane(facePlane)
, sideFacePlane(sideFacePlane)
, faceLine(faceLine)
, edgeLine(edgeLine)
{}
/*override*/ void updateContactPoint();
};
class EdgeEdgeConnector : public PolyConnector,
public Allocator<EdgeEdgeConnector>
{
private:
Line edgeLine0;
Line edgeLine1;
/*override*/ GeoPairType getConnectorType() const {return EDGE_EDGE_CONNECTOR;}
public:
EdgeEdgeConnector(
Body* b0,
Body* b1,
const ContactParams& contactParams,
Line edgeLine0,
Line edgeLine1,
int edgeId0,
int edgeId1 )
: PolyConnector(b0, b1, contactParams, edgeId0, edgeId1)
, edgeLine0(edgeLine0)
, edgeLine1(edgeLine1)
{
}
/*override*/ void updateContactPoint();
};
////////////////////////////////////////////////////////////////////////////////
class BallVertexConnector : public PolyConnector,
public Allocator<BallVertexConnector>
{
private:
float radius;
Vector3 offset;
/*override*/ GeoPairType getConnectorType() const {return BALL_VERTEX_CONNECTOR;}
public:
BallVertexConnector(
Body* b0,
Body* b1,
const ContactParams& contactParams,
float radius,
const Vector3& offset,
int vertexId)
: PolyConnector(b0, b1, contactParams, 0, vertexId)
, radius(radius)
, offset(offset)
{}
/*override*/ void updateContactPoint();
};
class BallEdgeConnector : public PolyConnector,
public Allocator<BallEdgeConnector>
{
private:
float radius;
Vector3 offset;
Vector3 normal;
/*override*/ GeoPairType getConnectorType() const {return BALL_EDGE_CONNECTOR;}
public:
BallEdgeConnector(
Body* b0,
Body* b1,
const ContactParams& contactParams,
float radius,
const Vector3& offset,
const Vector3& normal,
int edgeId)
: PolyConnector(b0, b1, contactParams, 0, edgeId)
, radius(radius)
, offset(offset)
, normal(normal)
{}
/*override*/ void updateContactPoint();
};
class BallPlaneConnector : public PolyConnector,
public Allocator<BallPlaneConnector>
{
private:
float radius;
Vector3 offset;
Vector3 normal;
/*override*/ GeoPairType getConnectorType() const {return BALL_PLANE_CONNECTOR;}
public:
BallPlaneConnector(
Body* b0,
Body* b1,
const ContactParams& contactParams,
float radius,
const Vector3& offset,
const Vector3& normal,
int faceId)
: PolyConnector(b0, b1, contactParams, 0, faceId)
, radius(radius)
, offset(offset)
, normal(normal)
{}
/*override*/ void updateContactPoint();
};
} // namespace RBX
+272
View File
@@ -0,0 +1,272 @@
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "Util/G3DCore.h"
#include "Util/PV.h"
#include "Util/Quaternion.h"
#include "Util/Math.h"
#include "Util/Memory.h"
#include "rbx/threadsafe.h"
#include "v8kernel/Constants.h"
#include "Fastlog.h"
namespace RBX {
class Body;
class SimBody
: public Allocator<SimBody>
{
private:
Body* body;
float dt;
bool dirty;
boost::uint64_t uid;
PV pv;
Quaternion qOrientation; // master for simulation
Vector3 angMomentum; // master for simulation
Vector3 moment;
Vector3 momentRecip;
Matrix3 momentRecipWorld;
float massRecip;
float constantForceY;
// accumulators
Vector3 force; // at center of mass, in world coordinates
Vector3 torque; // in world coordinates
Vector3 impulse; // at center of mass, in world coordinates
Vector3 rotationalImpulse; // in world coordinates
// Cache for impulse solver
Vector3 impulseLast;
int freeFallBodyIndex;
int realTimeBodyIndex;
int jointBodyIndex;
int buoyancyBodyIndex;
int contactBodyIndex;
int numOfConnectors; // how many connectors connect this SimBody (assembly)
int numOfHumanoidConnectors;
int numOfSecondPassConnectors;
int numOfRealTimeConnectors;
int numOfJointConnectors;
int numOfBuoyancyConnectors;
int numOfContactConnectors;
// Symmetrical state detection
bool symmetricContact;
bool verticalContact;
Vector3 penetrationTorque; // aggregated from contact normals scaled by penetration depth
Vector3 penetrationForce; // aggregated from contact normals scaled by penetration depth
inline void clearForceAccumulators() {
force = getWorldGravityForce();
torque = Vector3(0.0, 0.0, 0.0);
}
inline void clearImpulseAccumulators() {
impulse = Vector3(0.0, 0.0, 0.0);
rotationalImpulse = Vector3(0.0, 0.0, 0.0);
}
void update();
// All debugging stuff;
static float maxTorqueXX;
static float maxForceXX;
static float maxLinearImpulseXX;
static float maxRotationalImpulseXX;
static float maxDebugTorque();
static float maxDebugForce();
static float maxDebugLinearImpulse();
static float maxDebugRotationalImpulse();
public:
SimBody(Body* body);
~SimBody();
Body* getBody() {return body;}
const Body* getBodyConst() const {return body;}
void setDt(float _dt) {dt = _dt;}
float getDt() const {return dt;}
void setUID( boost::uint64_t _uid ) { uid = _uid; }
boost::uint64_t getUID() const { return uid; }
inline void updateMomentRecipWorld();
inline Vector3 computeRotationVelocityFromMomentum();
inline Vector3 computeRotationVelocityFromMomentumFast();
inline const Matrix3& getInverseInertiaInWorld() const {return momentRecipWorld;}
void step();
void stepVelocity();
void stepPosition();
void stepFreeFall();
void applyImpulse(const Vector3& _impulse, const Vector3& worldPos);
void clearVelocity();
void updateAngMomentum();
void updateFromSolver( const Vector3& newPosition, const Matrix3& newOrientation, const Vector3& newLinearVelocity, const Vector3& newAngularVelocity );
inline void updateIfDirty() { // called before step. Assumes body cofm is clean
if (dirty)
update();
}
inline Vector3 getWorldGravityForce() const { return Vector3(0, constantForceY, 0); }
inline void clearSymStateAndAccummulator() {
symmetricContact = true;
verticalContact = true;
penetrationTorque = Vector3::zero();
penetrationForce = Vector3::zero();
}
inline void makeDirty() {dirty = true;}
bool getDirty() const {return dirty;}
inline const PV& getPV() const {return pv;}
PV getOwnerPV();
static Vector3 computeTorqueFromOffsetForce(const Vector3& _force, const Vector3& cofm, const Vector3& forceLocationWorld) {
Vector3 localPosWorld = forceLocationWorld - cofm;
return localPosWorld.cross(_force);
}
//////////////////////////////////////////////////////////////////////////////
//
// Parallel physics will accumulate forces from different threads - need a mutex for each
//
//
inline void accumulateForceCofm(const Vector3& _force) {
updateIfDirty();
force += _force;
RBXASSERT_SLOW(force.isFinite());
RBXASSERT_SLOW(Math::longestVector3Component(force) < maxDebugForce());
}
inline void accumulateForce(const Vector3& _force, const Vector3& worldPos) {
RBXASSERT_SLOW(Math::longestVector3Component(_force) < maxDebugForce());
updateIfDirty();
force += _force;
torque += computeTorqueFromOffsetForce(_force, pv.position.translation, worldPos);
RBXASSERT_SLOW(force.isFinite());
RBXASSERT_SLOW(torque.isFinite());
}
inline void accumulatePenetrationForce(const Vector3& _force, const Vector3& worldPos) {
penetrationForce += _force;
penetrationTorque += computeTorqueFromOffsetForce(_force, pv.position.translation, worldPos);
}
inline void accumulateTorque(const Vector3& _torque) {
RBXASSERT_SLOW(Math::longestVector3Component(_torque) < maxDebugTorque());
updateIfDirty();
torque += _torque;
RBXASSERT_SLOW(torque.isFinite());
}
inline void accumulateImpulse(const Vector3& _impulse, const Vector3& worldPos) {
RBXASSERT_SLOW(Math::longestVector3Component(_impulse) < maxDebugLinearImpulse());
updateIfDirty();
impulse += _impulse;
Vector3 localPosWorld = worldPos - pv.position.translation;
rotationalImpulse += localPosWorld.cross(_impulse);
RBXASSERT_SLOW(impulse.isFinite());
RBXASSERT_SLOW(rotationalImpulse.isFinite());
}
inline void accumulateImpulseAtBranchCofm(const Vector3& _impulse) {
RBXASSERT_SLOW(Math::longestVector3Component(_impulse) < maxDebugLinearImpulse());
updateIfDirty();
impulse += _impulse;
RBXASSERT_SLOW(impulse.isFinite());
}
inline void accumulateRotationalImpulse(const Vector3& _rotationalImpulse) {
RBXASSERT_SLOW(Math::longestVector3Component(_rotationalImpulse) < maxDebugRotationalImpulse());
updateIfDirty();
rotationalImpulse += _rotationalImpulse;
RBXASSERT_SLOW(rotationalImpulse.isFinite());
}
// End of parallel section
//
//////////////////////////////////////////////////////////////////////////////////////////
inline void resetImpulseAccumulators() {
updateIfDirty();
clearImpulseAccumulators();
}
inline void resetForceAccumulators() {
updateIfDirty();
clearForceAccumulators();
}
inline const Vector3& getForce() const {return force;}
inline const Vector3& getTorque() const {return torque;}
inline const Vector3& getImpulse() const {return impulse;}
inline const Vector3& getRotationallmpulse() const {return rotationalImpulse;}
inline const float& getMassRecip() const {return massRecip;}
inline const Vector3& getImpulseLast() const {return impulseLast;}
inline bool hasExternalForceOrImpulse() const {return force != getWorldGravityForce() || torque != Vector3::zero() ||
impulse != Vector3::zero() || rotationalImpulse != Vector3::zero();}
inline bool updateSymmetricContactState() {
symmetricContact = (penetrationTorque.squaredMagnitude() < Constants::impulseSolverSymStateTorqueBound());
verticalContact = ( ( fabs(penetrationForce.x) < Constants::impulseSolverSymStateForceBound() ) &&
( fabs(penetrationForce.z) < Constants::impulseSolverSymStateForceBound() ) );
return symmetricContact;
}
inline bool isSymmetricContact() const {return symmetricContact;}
inline bool isVerticalContact() const {return verticalContact;}
inline void clearSymmetricContact() {symmetricContact = false;}
inline int& getRealTimeBodyIndex() {return realTimeBodyIndex;}
inline int& getFreeFallBodyIndex() {return freeFallBodyIndex;}
inline int& getJointBodyIndex() {return jointBodyIndex;}
inline int& getBuoyancyBodyIndex() {return buoyancyBodyIndex;}
inline int& getContactBodyIndex() {return contactBodyIndex;}
inline bool isFreeFallBody() const {return freeFallBodyIndex >= 0;}
inline bool isRealTimeBody() const {return realTimeBodyIndex >= 0;}
inline bool isJointBody() const {return jointBodyIndex >= 0; }
inline bool isBuoyancyBody() const {return buoyancyBodyIndex >= 0; }
inline bool isContactBody() const {return contactBodyIndex >= 0;}
inline bool isInKernel() const {return isFreeFallBody() || isRealTimeBody() || isJointBody() || isContactBody() || isBuoyancyBody();}
inline bool validateBodyLists() const {return (freeFallBodyIndex >= 0) + (realTimeBodyIndex >= 0) +
(jointBodyIndex >= 0) + (contactBodyIndex >= 0) <= 1;}
inline const int& getHumanoidConnectorCount() const {return numOfHumanoidConnectors;}
inline const int& getSecondPassConnectorCount() const {return numOfSecondPassConnectors;}
inline const int& getRealTimeConnectorCount() const {return numOfRealTimeConnectors;}
inline const int& getJointConnectorCount() const {return numOfJointConnectors;}
inline const int& getBuoyancyConnectorCount() const {return numOfBuoyancyConnectors;}
inline const int& getContactConnectorCount() const {return numOfContactConnectors;}
inline const int& getConnectorCount() const {return numOfConnectors;}
inline void incrementHumanoidConnectorCount() {++numOfHumanoidConnectors; ++numOfConnectors;}
inline void decrementHumanoidConnectorCount() {--numOfHumanoidConnectors; --numOfConnectors;}
inline void incrementSecondPassConnectorCount() {++numOfSecondPassConnectors; ++numOfConnectors;}
inline void decrementSecondPassConnectorCount() {--numOfSecondPassConnectors; --numOfConnectors;}
inline void incrementRealTimeConnectorCount() {++numOfRealTimeConnectors; ++numOfConnectors;}
inline void decrementRealTimeConnectorCount() {--numOfRealTimeConnectors; --numOfConnectors;}
inline void incrementJointConnetorCount() {++numOfJointConnectors; ++numOfConnectors;}
inline void decrementJointConnetorCount() {--numOfJointConnectors; --numOfConnectors;}
inline void incrementBuoyancyConnectorCount() {++numOfBuoyancyConnectors; ++numOfConnectors;}
inline void decrementBuoyancyConnectorCount() {--numOfBuoyancyConnectors; --numOfConnectors;}
inline void incrementContactConnectorCount() {++numOfContactConnectors; ++numOfConnectors;}
inline void decrementContactConnectorCount() {--numOfContactConnectors; -- numOfConnectors;}
};
} // namespace