mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-25 13:21:36 +00:00
974a8074a3
use sdl 2.0.4 audio queuing output when mixerthread is disabled. first attempt at BIH traces. doesn't yet work properly so disabled for now, but does fix some performance blackholes in xonotic. first attempt at a lfield_nearclip value. add VF_SKYROOM_CAMERA for easier skyrooms. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5442 fc73d0e0-1445-4013-8a0c-d673dee63da5
1904 lines
62 KiB
C++
1904 lines
62 KiB
C++
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program 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 GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
//if we're not building as an fte-specific plugin, we must be being built as part of the fte engine itself.
|
|
//(no, we don't want to act as a plugin for ezquake...)
|
|
#ifndef FTEPLUGIN
|
|
#define FTEENGINE
|
|
#define FTEPLUGIN
|
|
#define pCvar_Register Cvar_Get
|
|
#define pCvar_GetNVFDG Cvar_Get2
|
|
#define pCvar_GetFloat(x) Cvar_FindVar(x)->value
|
|
#define pSys_Error Sys_Error
|
|
#define Plug_Init Plug_Bullet_Init
|
|
#pragma comment(lib,"../../plugins/bullet/libs/bullet_dbg.lib")
|
|
#endif
|
|
|
|
#include "../plugin.h"
|
|
#include "../engine.h"
|
|
|
|
#include "pr_common.h"
|
|
#include "com_mesh.h"
|
|
|
|
#ifndef FTEENGINE
|
|
#define BZ_Malloc malloc
|
|
#define BZ_Free free
|
|
#define Z_Free BZ_Free
|
|
//#define vec3_origin vec3_origin_
|
|
//static vec3_t vec3_origin;
|
|
#define VectorCompare VectorCompare_
|
|
static int VectorCompare (const vec3_t v1, const vec3_t v2)
|
|
{
|
|
int i;
|
|
for (i=0 ; i<3 ; i++)
|
|
if (v1[i] != v2[i])
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
#endif
|
|
static rbeplugfuncs_t *rbefuncs;
|
|
|
|
|
|
|
|
//============================================================================
|
|
// physics engine support
|
|
//============================================================================
|
|
|
|
#define DEG2RAD(d) (d * M_PI * (1/180.0f))
|
|
#define RAD2DEG(d) ((d*180) / M_PI)
|
|
|
|
#include "btBulletDynamicsCommon.h"
|
|
|
|
//not sure where these are going. seems to be an issue only on windows.
|
|
#ifndef max
|
|
#define max(a,b) ((a) > (b) ? (a) : (b))
|
|
#endif
|
|
#ifndef min
|
|
#define min(a,b) ((a) < (b) ? (a) : (b))
|
|
#endif
|
|
|
|
static void World_Bullet_RunCmd(world_t *world, rbecommandqueue_t *cmd);
|
|
|
|
static cvar_t *physics_bullet_enable;
|
|
static cvar_t *physics_bullet_maxiterationsperframe;
|
|
static cvar_t *physics_bullet_framerate;
|
|
static cvar_t *pr_meshpitch;
|
|
|
|
void World_Bullet_Init(void)
|
|
{
|
|
physics_bullet_enable = pCvar_GetNVFDG("physics_bullet_enable", "1", 0, "", "Bullet");
|
|
physics_bullet_maxiterationsperframe = pCvar_GetNVFDG("physics_bullet_maxiterationsperframe", "10", 0, "FIXME: should be 1 when CCD is working properly.", "Bullet");
|
|
physics_bullet_framerate = pCvar_GetNVFDG("physics_bullet_framerate", "60", 0, "Bullet physics run at a fixed framerate in order to preserve numerical stability (interpolation is used to smooth out the result). Higher framerates are of course more demanding.", "Bullet");
|
|
pr_meshpitch = pCvar_GetNVFDG("r_meshpitch", "-1", 0, "", "Bullet");
|
|
}
|
|
|
|
void World_Bullet_Shutdown(void)
|
|
{
|
|
}
|
|
|
|
typedef struct bulletcontext_s
|
|
{
|
|
rigidbodyengine_t funcs;
|
|
|
|
bool hasextraobjs;
|
|
// void *ode_space;
|
|
// void *ode_contactgroup;
|
|
// number of constraint solver iterations to use (for dWorldStepFast)
|
|
// int ode_iterations;
|
|
// actual step (server frametime / ode_iterations)
|
|
// vec_t ode_step;
|
|
// max velocity for a 1-unit radius object at current step to prevent
|
|
// missed collisions
|
|
// vec_t ode_movelimit;
|
|
rbecommandqueue_t *cmdqueuehead;
|
|
rbecommandqueue_t *cmdqueuetail;
|
|
|
|
|
|
world_t *gworld;
|
|
|
|
|
|
btBroadphaseInterface *broadphase;
|
|
btDefaultCollisionConfiguration *collisionconfig;
|
|
btCollisionDispatcher *collisiondispatcher;
|
|
btSequentialImpulseConstraintSolver *solver;
|
|
btDiscreteDynamicsWorld *dworld;
|
|
btOverlapFilterCallback *ownerfilter;
|
|
} bulletcontext_t;
|
|
|
|
class QCFilterCallback : public btOverlapFilterCallback
|
|
{
|
|
// return true when pairs need collision
|
|
virtual bool needBroadphaseCollision(btBroadphaseProxy* proxy0,btBroadphaseProxy* proxy1) const
|
|
{
|
|
//dimensions don't collide
|
|
bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0;
|
|
collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask);
|
|
|
|
//owners don't collide (unless one is world, obviouslyish)
|
|
if (collides)
|
|
{
|
|
btRigidBody *b1 = (btRigidBody*)proxy0->m_clientObject;
|
|
btRigidBody *b2 = (btRigidBody*)proxy1->m_clientObject;
|
|
//don't let two qc-controlled entities collide in Bullet, that's the job of quake.
|
|
if (b1->isStaticOrKinematicObject() && b2->isStaticOrKinematicObject())
|
|
return false;
|
|
wedict_t *e1 = (wedict_t*)b1->getUserPointer();
|
|
wedict_t *e2 = (wedict_t*)b2->getUserPointer();
|
|
if ((e1->v->solid == SOLID_TRIGGER && e2->v->solid != SOLID_BSP) ||
|
|
(e2->v->solid == SOLID_TRIGGER && e1->v->solid != SOLID_BSP))
|
|
return false; //triggers only collide with bsp objects.
|
|
if (e1->entnum && e2->entnum)
|
|
collides = e1->v->owner != e2->entnum && e2->v->owner != e1->entnum;
|
|
}
|
|
|
|
return collides;
|
|
}
|
|
};
|
|
|
|
static void QDECL World_Bullet_End(world_t *world)
|
|
{
|
|
struct bulletcontext_s *ctx = (struct bulletcontext_s*)world->rbe;
|
|
world->rbe = NULL;
|
|
delete ctx->dworld;
|
|
delete ctx->solver;
|
|
delete ctx->collisionconfig;
|
|
delete ctx->collisiondispatcher;
|
|
delete ctx->broadphase;
|
|
delete ctx->ownerfilter;
|
|
Z_Free(ctx);
|
|
}
|
|
|
|
static void QDECL World_Bullet_RemoveJointFromEntity(world_t *world, wedict_t *ed)
|
|
{
|
|
ed->rbe.joint_type = 0;
|
|
// if(ed->rbe.joint)
|
|
// dJointDestroy((dJointID)ed->rbe.joint);
|
|
ed->rbe.joint.joint = NULL;
|
|
}
|
|
|
|
static void QDECL World_Bullet_RemoveFromEntity(world_t *world, wedict_t *ed)
|
|
{
|
|
struct bulletcontext_s *ctx = (struct bulletcontext_s*)world->rbe;
|
|
btRigidBody *body;
|
|
btCollisionShape *geom;
|
|
if (!ed->rbe.physics)
|
|
return;
|
|
|
|
// entity is not physics controlled, free any physics data
|
|
ed->rbe.physics = qfalse;
|
|
|
|
body = (btRigidBody*)ed->rbe.body.body;
|
|
ed->rbe.body.body = NULL;
|
|
if (body)
|
|
ctx->dworld->removeRigidBody (body);
|
|
|
|
geom = (btCollisionShape*)ed->rbe.body.geom;
|
|
ed->rbe.body.geom = NULL;
|
|
if (ed->rbe.body.geom)
|
|
delete geom;
|
|
|
|
//FIXME: joints
|
|
rbefuncs->ReleaseCollisionMesh(ed);
|
|
if(ed->rbe.massbuf)
|
|
BZ_Free(ed->rbe.massbuf);
|
|
ed->rbe.massbuf = NULL;
|
|
}
|
|
|
|
static void World_Bullet_Frame_BodyToEntity(world_t *world, wedict_t *ed)
|
|
{
|
|
return;
|
|
|
|
#if 0
|
|
model_t *model;
|
|
const float *avel;
|
|
const float *o;
|
|
const float *r; // for some reason dBodyGetRotation returns a [3][4] matrix
|
|
const float *vel;
|
|
btRigidBody *body = (btRigidBody*)ed->rbe.body.body;
|
|
int movetype;
|
|
float bodymatrix[16];
|
|
float entitymatrix[16];
|
|
vec3_t angles;
|
|
vec3_t avelocity;
|
|
vec3_t forward, left, up;
|
|
vec3_t origin;
|
|
vec3_t spinvelocity;
|
|
vec3_t velocity;
|
|
if (!body)
|
|
return;
|
|
|
|
movetype = (int)ed->v->movetype;
|
|
if (movetype != MOVETYPE_PHYSICS)
|
|
{
|
|
switch((int)ed->xv->jointtype)
|
|
{
|
|
// TODO feed back data from physics
|
|
case JOINTTYPE_POINT:
|
|
break;
|
|
case JOINTTYPE_HINGE:
|
|
break;
|
|
case JOINTTYPE_SLIDER:
|
|
break;
|
|
case JOINTTYPE_UNIVERSAL:
|
|
break;
|
|
case JOINTTYPE_HINGE2:
|
|
break;
|
|
case JOINTTYPE_FIXED:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
// store the physics engine data into the entity
|
|
|
|
btTransform trans;
|
|
body->getMotionState()->getWorldTransform(trans);
|
|
// o = dBodyGetPosition(body);
|
|
// r = dBodyGetRotation(body);
|
|
// vel = dBodyGetLinearVel(body);
|
|
// avel = dBodyGetAngularVel(body);
|
|
// VectorCopy(o, origin);
|
|
// forward[0] = r[0];
|
|
// forward[1] = r[4];
|
|
// forward[2] = r[8];
|
|
// left[0] = r[1];
|
|
// left[1] = r[5];
|
|
// left[2] = r[9];
|
|
// up[0] = r[2];
|
|
// up[1] = r[6];
|
|
// up[2] = r[10];
|
|
vel = body->getLinearVelocity();
|
|
avel = body->getAngularVelocity();
|
|
VectorCopy(vel, velocity);
|
|
VectorCopy(avel, spinvelocity);
|
|
trans.getBasis().getOpenGLSubMatrix(bodymatrix);
|
|
foo Matrix4x4_RM_FromVectors(bodymatrix, forward, left, up, origin);
|
|
foo Matrix4_Multiply(ed->rbe.offsetimatrix, bodymatrix, entitymatrix);
|
|
foo Matrix3x4_RM_ToVectors(entitymatrix, forward, left, up, origin);
|
|
|
|
VectorAngles(forward, up, angles);
|
|
angles[0]*=-1;
|
|
|
|
avelocity[PITCH] = RAD2DEG(spinvelocity[PITCH]);
|
|
avelocity[YAW] = RAD2DEG(spinvelocity[ROLL]);
|
|
avelocity[ROLL] = RAD2DEG(spinvelocity[YAW]);
|
|
|
|
if (ed->v->modelindex)
|
|
{
|
|
model = world->Get_CModel(world, ed->v->modelindex);
|
|
if (!model || model->type == mod_alias)
|
|
{
|
|
angles[PITCH] *= -1;
|
|
avelocity[PITCH] *= -1;
|
|
}
|
|
}
|
|
|
|
VectorCopy(origin, ed->v->origin);
|
|
VectorCopy(velocity, ed->v->velocity);
|
|
//VectorCopy(forward, ed->xv->axis_forward);
|
|
//VectorCopy(left, ed->xv->axis_left);
|
|
//VectorCopy(up, ed->xv->axis_up);
|
|
//VectorCopy(spinvelocity, ed->xv->spinvelocity);
|
|
VectorCopy(angles, ed->v->angles);
|
|
VectorCopy(avelocity, ed->v->avelocity);
|
|
|
|
// values for BodyFromEntity to check if the qc modified anything later
|
|
VectorCopy(origin, ed->rbe.origin);
|
|
VectorCopy(velocity, ed->rbe.velocity);
|
|
VectorCopy(angles, ed->rbe.angles);
|
|
VectorCopy(avelocity, ed->rbe.avelocity);
|
|
// ed->rbe.gravity = (qboolean)dBodyGetGravityMode(body);
|
|
|
|
World_LinkEdict(world, ed, true);
|
|
#endif
|
|
}
|
|
|
|
|
|
static bool NegativeMeshPitch(world_t *world, wedict_t *ent)
|
|
{
|
|
if (ent->v->modelindex)
|
|
{
|
|
model_t *model = world->Get_CModel(world, ent->v->modelindex);
|
|
if (model && (model->type == mod_alias || model->type == mod_halflife))
|
|
return pr_meshpitch->value < 0;
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static btTransform transformFromQuake(world_t *world, wedict_t *ent)
|
|
{
|
|
vec3_t forward, left, up;
|
|
if (NegativeMeshPitch(world, ent))
|
|
{
|
|
vec3_t iangles = {-ent->v->angles[0], ent->v->angles[1], ent->v->angles[2]};
|
|
rbefuncs->AngleVectors(iangles, forward, left, up);
|
|
}
|
|
else
|
|
rbefuncs->AngleVectors(ent->v->angles, forward, left, up);
|
|
VectorNegate(left, left);
|
|
|
|
return btTransform(btMatrix3x3(forward[0], forward[1], forward[2], left[0], left[1], left[2], up[0], up[1], up[2]), btVector3(ent->v->origin[0], ent->v->origin[1], ent->v->origin[2]));
|
|
}
|
|
|
|
static void World_Bullet_Frame_JointFromEntity(world_t *world, wedict_t *ed)
|
|
{
|
|
bulletcontext_t *rbe = (bulletcontext_t*)world->rbe;
|
|
btTypedConstraint *j = NULL;
|
|
btRigidBody *b1 = NULL;
|
|
btRigidBody *b2 = NULL;
|
|
int movetype = 0;
|
|
int jointtype = 0;
|
|
int enemy = 0, aiment = 0;
|
|
wedict_t *e1, *e2;
|
|
// vec_t CFM, ERP, FMax;
|
|
vec_t Stop;
|
|
// vec_t Vel;
|
|
vec3_t forward;
|
|
movetype = (int)ed->v->movetype;
|
|
jointtype = (int)ed->xv->jointtype;
|
|
enemy = ed->v->enemy;
|
|
aiment = ed->v->aiment;
|
|
btVector3 origin(ed->v->origin[0], ed->v->origin[1], ed->v->origin[2]);
|
|
btVector3 velocity(ed->v->velocity[0], ed->v->velocity[1], ed->v->velocity[2]);
|
|
btVector3 movedir(ed->v->movedir[0], ed->v->movedir[1], ed->v->movedir[2]);
|
|
if(movetype == MOVETYPE_PHYSICS)
|
|
jointtype = 0; // can't have both
|
|
|
|
e1 = (wedict_t*)PROG_TO_EDICT(world->progs, enemy);
|
|
b1 = (btRigidBody*)e1->rbe.body.body;
|
|
if(ED_ISFREE(e1) || !b1)
|
|
enemy = 0;
|
|
e2 = (wedict_t*)PROG_TO_EDICT(world->progs, aiment);
|
|
b2 = (btRigidBody*)e2->rbe.body.body;
|
|
if(ED_ISFREE(e2) || !b2)
|
|
aiment = 0;
|
|
// see http://www.ode.org/old_list_archives/2006-January/017614.html
|
|
// we want to set ERP? make it fps independent and work like a spring constant
|
|
// note: if movedir[2] is 0, it becomes ERP = 1, CFM = 1.0 / (H * K)
|
|
if(movedir[0] > 0 && movedir[1] > 0)
|
|
{
|
|
// float K = movedir[0];
|
|
// float D = movedir[1];
|
|
// float R = 2.0 * D * sqrt(K); // we assume D is premultiplied by sqrt(sprungMass)
|
|
// CFM = 1.0 / (rbe->ode_step * K + R); // always > 0
|
|
// ERP = rbe->ode_step * K * CFM;
|
|
// Vel = 0;
|
|
// FMax = 0;
|
|
Stop = movedir[2];
|
|
}
|
|
else if(movedir[1] < 0)
|
|
{
|
|
// CFM = 0;
|
|
// ERP = 0;
|
|
// Vel = movedir[0];
|
|
// FMax = -movedir[1]; // TODO do we need to multiply with world.physics.ode_step?
|
|
Stop = movedir[2] > 0 ? movedir[2] : BT_INFINITY;
|
|
}
|
|
else // movedir[0] > 0, movedir[1] == 0 or movedir[0] < 0, movedir[1] >= 0
|
|
{
|
|
// CFM = 0;
|
|
// ERP = 0;
|
|
// Vel = 0;
|
|
// FMax = 0;
|
|
Stop = BT_INFINITY;
|
|
}
|
|
if(jointtype == ed->rbe.joint_type && VectorCompare(origin, ed->rbe.joint_origin) && VectorCompare(velocity, ed->rbe.joint_velocity) && VectorCompare(ed->v->angles, ed->rbe.joint_angles) && enemy == ed->rbe.joint_enemy && aiment == ed->rbe.joint_aiment && VectorCompare(movedir, ed->rbe.joint_movedir))
|
|
return; // nothing to do
|
|
|
|
if(ed->rbe.joint.joint)
|
|
{
|
|
j = (btTypedConstraint*)ed->rbe.joint.joint;
|
|
rbe->dworld->removeConstraint(j);
|
|
ed->rbe.joint.joint = NULL;
|
|
delete j;
|
|
}
|
|
if (!jointtype)
|
|
return;
|
|
|
|
btVector3 b1org(0,0,0), b2org(0,0,0);
|
|
if(enemy)
|
|
b1org.setValue(e1->v->origin[0], e1->v->origin[1], e1->v->origin[2]);
|
|
if(aiment)
|
|
b2org.setValue(e2->v->origin[0], e2->v->origin[1], e2->v->origin[2]);
|
|
|
|
ed->rbe.joint_type = jointtype;
|
|
ed->rbe.joint_enemy = enemy;
|
|
ed->rbe.joint_aiment = aiment;
|
|
VectorCopy(origin, ed->rbe.joint_origin);
|
|
VectorCopy(velocity, ed->rbe.joint_velocity);
|
|
VectorCopy(ed->v->angles, ed->rbe.joint_angles);
|
|
VectorCopy(movedir, ed->rbe.joint_movedir);
|
|
|
|
rbefuncs->AngleVectors(ed->v->angles, forward, NULL, NULL);
|
|
|
|
//Con_Printf("making new joint %i\n", (int) (ed - prog->edicts));
|
|
switch(jointtype)
|
|
{
|
|
case JOINTTYPE_POINT:
|
|
j = new btPoint2PointConstraint(*b1, *b2, btVector3(b1org - origin), btVector3(b2org - origin));
|
|
break;
|
|
/* case JOINTTYPE_HINGE:
|
|
btHingeConstraint *h = new btHingeConstraint(*b1, *b2, btVector3(b1org - origin), btVector3(b2org - origin), aa, ab, ref);
|
|
j = h;
|
|
if (h)
|
|
{
|
|
h->setLimit(-Stop, Stop, softness, bias, relaxation);
|
|
h->setAxis(btVector3(forward[0], forward[1], forward[2]));
|
|
// h->dJointSetHingeParam(j, dParamFMax, FMax);
|
|
// h->dJointSetHingeParam(j, dParamHiStop, Stop);
|
|
// h->dJointSetHingeParam(j, dParamLoStop, -Stop);
|
|
// h->dJointSetHingeParam(j, dParamStopCFM, CFM);
|
|
// h->dJointSetHingeParam(j, dParamStopERP, ERP);
|
|
|
|
// h->setMotorTarget(vel);
|
|
}
|
|
break;*/
|
|
case JOINTTYPE_SLIDER:
|
|
{
|
|
btTransform jointtransform = transformFromQuake(world, ed);
|
|
btTransform b1transform = transformFromQuake(world, e1).inverseTimes(jointtransform);
|
|
btTransform b2transform = transformFromQuake(world, e2).inverseTimes(jointtransform);
|
|
|
|
btSliderConstraint *s = new btSliderConstraint(*b1, *b2, b1transform, b2transform, false);
|
|
j = s;
|
|
if (s)
|
|
{
|
|
// s->dJointSetSliderAxis(j, forward[0], forward[1], forward[2]);
|
|
// s->dJointSetSliderParam(j, dParamFMax, FMax);
|
|
s->setLowerLinLimit(-Stop);
|
|
s->setUpperLinLimit(Stop);
|
|
s->setLowerAngLimit(0);
|
|
s->setUpperAngLimit(0);
|
|
// s->dJointSetSliderParam(j, dParamHiStop, Stop);
|
|
// s->dJointSetSliderParam(j, dParamLoStop, -Stop);
|
|
// s->dJointSetSliderParam(j, dParamStopCFM, CFM);
|
|
// s->dJointSetSliderParam(j, dParamStopERP, ERP);
|
|
// s->setTargetLinMotorVelocity(vel);
|
|
// s->setPoweredLinMotor(true);
|
|
}
|
|
}
|
|
break;
|
|
/* case JOINTTYPE_UNIVERSAL:
|
|
btGeneric6DofConstraint
|
|
j = dJointCreateUniversal(rbe->ode_world, 0);
|
|
if (j)
|
|
{
|
|
dJointSetUniversalAnchor(j, origin[0], origin[1], origin[2]);
|
|
dJointSetUniversalAxis1(j, forward[0], forward[1], forward[2]);
|
|
dJointSetUniversalAxis2(j, up[0], up[1], up[2]);
|
|
dJointSetUniversalParam(j, dParamFMax, FMax);
|
|
dJointSetUniversalParam(j, dParamHiStop, Stop);
|
|
dJointSetUniversalParam(j, dParamLoStop, -Stop);
|
|
dJointSetUniversalParam(j, dParamStopCFM, CFM);
|
|
dJointSetUniversalParam(j, dParamStopERP, ERP);
|
|
dJointSetUniversalParam(j, dParamVel, Vel);
|
|
dJointSetUniversalParam(j, dParamFMax2, FMax);
|
|
dJointSetUniversalParam(j, dParamHiStop2, Stop);
|
|
dJointSetUniversalParam(j, dParamLoStop2, -Stop);
|
|
dJointSetUniversalParam(j, dParamStopCFM2, CFM);
|
|
dJointSetUniversalParam(j, dParamStopERP2, ERP);
|
|
dJointSetUniversalParam(j, dParamVel2, Vel);
|
|
}
|
|
break;*/
|
|
/* case JOINTTYPE_HINGE2:
|
|
j = dJointCreateHinge2(rbe->ode_world, 0);
|
|
if (j)
|
|
{
|
|
dJointSetHinge2Anchor(j, origin[0], origin[1], origin[2]);
|
|
dJointSetHinge2Axis1(j, forward[0], forward[1], forward[2]);
|
|
dJointSetHinge2Axis2(j, velocity[0], velocity[1], velocity[2]);
|
|
dJointSetHinge2Param(j, dParamFMax, FMax);
|
|
dJointSetHinge2Param(j, dParamHiStop, Stop);
|
|
dJointSetHinge2Param(j, dParamLoStop, -Stop);
|
|
dJointSetHinge2Param(j, dParamStopCFM, CFM);
|
|
dJointSetHinge2Param(j, dParamStopERP, ERP);
|
|
dJointSetHinge2Param(j, dParamVel, Vel);
|
|
dJointSetHinge2Param(j, dParamFMax2, FMax);
|
|
dJointSetHinge2Param(j, dParamHiStop2, Stop);
|
|
dJointSetHinge2Param(j, dParamLoStop2, -Stop);
|
|
dJointSetHinge2Param(j, dParamStopCFM2, CFM);
|
|
dJointSetHinge2Param(j, dParamStopERP2, ERP);
|
|
dJointSetHinge2Param(j, dParamVel2, Vel);
|
|
}
|
|
break;*/
|
|
case JOINTTYPE_FIXED:
|
|
{
|
|
btTransform jointtransform = transformFromQuake(world, ed);
|
|
btTransform b1transform = transformFromQuake(world, e1).inverseTimes(jointtransform);
|
|
btTransform b2transform = transformFromQuake(world, e2).inverseTimes(jointtransform);
|
|
|
|
j = new btFixedConstraint(*b1, *b2, b1transform, b2transform);
|
|
}
|
|
break;
|
|
case 0:
|
|
default:
|
|
j = NULL;
|
|
break;
|
|
}
|
|
|
|
ed->rbe.joint.joint = (void *) j;
|
|
if (j)
|
|
{
|
|
j->setUserConstraintPtr((void *) ed);
|
|
rbe->dworld->addConstraint(j, false);
|
|
}
|
|
}
|
|
|
|
static void MatToTransform(const float *mat, btTransform &tr)
|
|
{
|
|
tr.setBasis(btMatrix3x3(
|
|
mat[0], mat[1], mat[2],
|
|
mat[3], mat[4], mat[5],
|
|
mat[6], mat[7], mat[8]));
|
|
tr.setOrigin(btVector3(mat[9], mat[10], mat[11]));
|
|
}
|
|
static void MatFromTransform(float *mat, const btTransform &tr)
|
|
{
|
|
const btMatrix3x3 &m = tr.getBasis();
|
|
const btVector3 &o = tr.getOrigin();
|
|
const btVector3 &r0 = m.getRow(0);
|
|
const btVector3 &r1 = m.getRow(1);
|
|
const btVector3 &r2 = m.getRow(2);
|
|
mat[0] = r0[0];
|
|
mat[1] = r0[1];
|
|
mat[2] = r0[2];
|
|
mat[3] = r1[0];
|
|
mat[4] = r1[1];
|
|
mat[5] = r1[2];
|
|
mat[6] = r2[0];
|
|
mat[7] = r2[1];
|
|
mat[8] = r2[2];
|
|
mat[9] = o[0];
|
|
mat[10] = o[1];
|
|
mat[11] = o[2];
|
|
}
|
|
static qboolean QDECL World_Bullet_RagMatrixToBody(rbebody_t *bodyptr, float *mat)
|
|
{ //mat is a 4*3 matrix
|
|
btTransform tr;
|
|
btRigidBody *body = (btRigidBody*)bodyptr->body;
|
|
MatToTransform(mat, tr);
|
|
body->setWorldTransform(tr);
|
|
return qtrue;
|
|
}
|
|
static qboolean QDECL World_Bullet_RagCreateBody(world_t *world, rbebody_t *bodyptr, rbebodyinfo_t *bodyinfo, float *mat, wedict_t *ent)
|
|
{
|
|
btRigidBody *body = NULL;
|
|
btCollisionShape *geom = NULL;
|
|
float radius;
|
|
// float length;
|
|
bulletcontext_t *ctx = (bulletcontext_t*)world->rbe;
|
|
// int axisindex;
|
|
ctx->hasextraobjs = true;
|
|
|
|
switch(bodyinfo->geomshape)
|
|
{
|
|
/*
|
|
case GEOMTYPE_TRIMESH:
|
|
// foo Matrix4x4_Identity(ed->rbe.offsetmatrix);
|
|
geom = NULL;
|
|
if (!model)
|
|
{
|
|
Con_Printf("entity %i (classname %s) has no model\n", NUM_FOR_EDICT(world->progs, (edict_t*)ed), PR_GetString(world->progs, ed->v->classname));
|
|
if (ed->rbe.physics)
|
|
World_Bullet_RemoveFromEntity(world, ed);
|
|
return;
|
|
}
|
|
if (!rbefuncs->GenerateCollisionMesh(world, model, ed, geomcenter))
|
|
{
|
|
if (ed->rbe.physics)
|
|
World_Bullet_RemoveFromEntity(world, ed);
|
|
return;
|
|
}
|
|
|
|
// foo Matrix4x4_RM_CreateTranslate(ed->rbe.offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]);
|
|
|
|
{
|
|
btTriangleIndexVertexArray *tiva = new btTriangleIndexVertexArray();
|
|
btIndexedMesh mesh;
|
|
mesh.m_vertexType = PHY_FLOAT;
|
|
mesh.m_indexType = PHY_INTEGER;
|
|
mesh.m_numTriangles = ed->rbe.numtriangles;
|
|
mesh.m_numVertices = ed->rbe.numvertices;
|
|
mesh.m_triangleIndexBase = (const unsigned char*)ed->rbe.element3i;
|
|
mesh.m_triangleIndexStride = sizeof(*ed->rbe.element3i)*3;
|
|
mesh.m_vertexBase = (const unsigned char*)ed->rbe.vertex3f;
|
|
mesh.m_vertexStride = sizeof(*ed->rbe.vertex3f)*3;
|
|
tiva->addIndexedMesh(mesh);
|
|
geom = new btBvhTriangleMeshShape(tiva, true);
|
|
}
|
|
break;
|
|
*/
|
|
default:
|
|
Con_DPrintf("World_Bullet_RagCreateBody: unsupported geomshape %i\n", bodyinfo->geomshape);
|
|
case GEOMTYPE_BOX:
|
|
geom = new btBoxShape(btVector3(bodyinfo->dimensions[0], bodyinfo->dimensions[1], bodyinfo->dimensions[2]) * 0.5);
|
|
break;
|
|
|
|
case GEOMTYPE_SPHERE:
|
|
geom = new btSphereShape(bodyinfo->dimensions[0] * 0.5f);
|
|
break;
|
|
|
|
case GEOMTYPE_CAPSULE:
|
|
// case GEOMTYPE_CAPSULE_X:
|
|
// case GEOMTYPE_CAPSULE_Y:
|
|
case GEOMTYPE_CAPSULE_Z:
|
|
radius = (bodyinfo->dimensions[0]+bodyinfo->dimensions[1]) * 0.5f;
|
|
geom = new btCapsuleShapeZ(radius, bodyinfo->dimensions[2]);
|
|
break;
|
|
|
|
case GEOMTYPE_CYLINDER:
|
|
// case GEOMTYPE_CYLINDER_X:
|
|
// case GEOMTYPE_CYLINDER_Y:
|
|
case GEOMTYPE_CYLINDER_Z:
|
|
radius = (bodyinfo->dimensions[0] + bodyinfo->dimensions[1]) * 0.5;
|
|
geom = new btCylinderShapeZ(btVector3(radius, radius, bodyinfo->dimensions[2])*0.5);
|
|
break;
|
|
}
|
|
bodyptr->geom = geom;
|
|
|
|
//now create the body too
|
|
|
|
btVector3 fallInertia(0, 0, 0);
|
|
((btCollisionShape*)geom)->calculateLocalInertia(bodyinfo->mass, fallInertia);
|
|
btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(bodyinfo->mass, NULL, (btCollisionShape*)geom, fallInertia);
|
|
MatToTransform(mat, fallRigidBodyCI.m_startWorldTransform);
|
|
body = new btRigidBody(fallRigidBodyCI);
|
|
body->setUserPointer(ent);
|
|
bodyptr->body = (void*)body;
|
|
|
|
//motion threshhold should be speed/physicsframerate.
|
|
//FIXME: recalculate...
|
|
body->setCcdMotionThreshold((bodyinfo->dimensions[0]+bodyinfo->dimensions[1]+bodyinfo->dimensions[2])*(4/3));
|
|
//radius should be the body's radius
|
|
body->setCcdSweptSphereRadius((bodyinfo->dimensions[0]+bodyinfo->dimensions[1]+bodyinfo->dimensions[2])*(0.5/3));
|
|
|
|
ctx->dworld->addRigidBody(body, ent->xv->dimension_solid, ent->xv->dimension_hit);
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
static void QDECL World_Bullet_RagMatrixFromJoint(rbejoint_t *joint, rbejointinfo_t *info, float *mat)
|
|
{
|
|
/*
|
|
dVector3 dr3;
|
|
switch(info->type)
|
|
{
|
|
case JOINTTYPE_POINT:
|
|
dJointGetBallAnchor(joint->ode_joint, dr3);
|
|
mat[3] = dr3[0];
|
|
mat[7] = dr3[1];
|
|
mat[11] = dr3[2];
|
|
VectorClear(mat+4);
|
|
VectorClear(mat+8);
|
|
break;
|
|
|
|
case JOINTTYPE_HINGE:
|
|
dJointGetHingeAnchor(joint->ode_joint, dr3);
|
|
mat[3] = dr3[0];
|
|
mat[7] = dr3[1];
|
|
mat[11] = dr3[2];
|
|
|
|
dJointGetHingeAxis(joint->ode_joint, dr3);
|
|
VectorCopy(dr3, mat+4);
|
|
VectorClear(mat+8);
|
|
|
|
CrossProduct(mat+4, mat+8, mat+0);
|
|
return;
|
|
break;
|
|
case JOINTTYPE_HINGE2:
|
|
dJointGetHinge2Anchor(joint->ode_joint, dr3);
|
|
mat[3] = dr3[0];
|
|
mat[7] = dr3[1];
|
|
mat[11] = dr3[2];
|
|
|
|
dJointGetHinge2Axis1(joint->ode_joint, dr3);
|
|
VectorCopy(dr3, mat+4);
|
|
dJointGetHinge2Axis2(joint->ode_joint, dr3);
|
|
VectorCopy(dr3, mat+8);
|
|
break;
|
|
|
|
case JOINTTYPE_SLIDER:
|
|
//no anchor point...
|
|
//get the two bodies and average their origin for a somewhat usable representation of an anchor.
|
|
{
|
|
const dReal *p1, *p2;
|
|
dReal n[3];
|
|
dBodyID b1 = dJointGetBody(joint->ode_joint, 0), b2 = dJointGetBody(joint->ode_joint, 1);
|
|
if (b1)
|
|
p1 = dBodyGetPosition(b1);
|
|
else
|
|
{
|
|
p1 = n;
|
|
VectorClear(n);
|
|
}
|
|
if (b2)
|
|
p2 = dBodyGetPosition(b2);
|
|
else
|
|
p2 = p1;
|
|
dJointGetSliderAxis(joint->ode_joint, dr3 + 0);
|
|
VectorInterpolate(p1, 0.5, p2, dr3);
|
|
mat[3] = dr3[0];
|
|
mat[7] = dr3[1];
|
|
mat[11] = dr3[2];
|
|
|
|
VectorClear(mat+4);
|
|
VectorClear(mat+8);
|
|
}
|
|
break;
|
|
|
|
case JOINTTYPE_UNIVERSAL:
|
|
dJointGetUniversalAnchor(joint->ode_joint, dr3);
|
|
mat[3] = dr3[0];
|
|
mat[7] = dr3[1];
|
|
mat[11] = dr3[2];
|
|
|
|
dJointGetUniversalAxis1(joint->ode_joint, dr3);
|
|
VectorCopy(dr3, mat+4);
|
|
dJointGetUniversalAxis2(joint->ode_joint, dr3);
|
|
VectorCopy(dr3, mat+8);
|
|
|
|
CrossProduct(mat+4, mat+8, mat+0);
|
|
return;
|
|
break;
|
|
}
|
|
rbefuncs->AngleVectors(vec3_origin, mat+0, mat+4, mat+8);
|
|
VectorNegate((mat+4), (mat+4));
|
|
*/
|
|
}
|
|
|
|
static void QDECL World_Bullet_RagMatrixFromBody(world_t *world, rbebody_t *bodyptr, float *mat)
|
|
{
|
|
// bulletcontext_t *ctx = (bulletcontext_t*)world->rbe;
|
|
btRigidBody *body = (btRigidBody*)bodyptr->body;
|
|
MatFromTransform(mat, body->getCenterOfMassTransform());
|
|
}
|
|
static void QDECL World_Bullet_RagEnableJoint(rbejoint_t *joint, qboolean enabled)
|
|
{
|
|
/*
|
|
if (enabled)
|
|
dJointEnable(joint->ode_joint);
|
|
else
|
|
dJointDisable(joint->ode_joint);
|
|
*/
|
|
}
|
|
static void QDECL World_Bullet_RagCreateJoint(world_t *world, rbejoint_t *joint, rbejointinfo_t *info, rbebody_t *body1, rbebody_t *body2, vec3_t aaa2[3])
|
|
{
|
|
/*
|
|
switch(info->type)
|
|
{
|
|
case JOINTTYPE_POINT:
|
|
joint->ode_joint = dJointCreateBall(world->rbe.world, 0);
|
|
break;
|
|
case JOINTTYPE_HINGE:
|
|
joint->ode_joint = dJointCreateHinge(world->rbe.world, 0);
|
|
break;
|
|
case JOINTTYPE_SLIDER:
|
|
joint->ode_joint = dJointCreateSlider(world->rbe.world, 0);
|
|
break;
|
|
case JOINTTYPE_UNIVERSAL:
|
|
joint->ode_joint = dJointCreateUniversal(world->rbe.world, 0);
|
|
break;
|
|
case JOINTTYPE_HINGE2:
|
|
joint->ode_joint = dJointCreateHinge2(world->rbe.world, 0);
|
|
break;
|
|
case JOINTTYPE_FIXED:
|
|
joint->ode_joint = dJointCreateFixed(world->rbe.world, 0);
|
|
break;
|
|
default:
|
|
joint->ode_joint = NULL;
|
|
break;
|
|
}
|
|
if (joint->ode_joint)
|
|
{
|
|
//Con_Printf("made new joint %i\n", (int) (ed - prog->edicts));
|
|
// dJointSetData(joint->ode_joint, NULL);
|
|
dJointAttach(joint->ode_joint, body1?body1->ode_body:NULL, body2?body2->ode_body:NULL);
|
|
|
|
switch(info->type)
|
|
{
|
|
case JOINTTYPE_POINT:
|
|
dJointSetBallAnchor(joint->ode_joint, aaa2[0][0], aaa2[0][1], aaa2[0][2]);
|
|
break;
|
|
case JOINTTYPE_HINGE:
|
|
dJointSetHingeAnchor(joint->ode_joint, aaa2[0][0], aaa2[0][1], aaa2[0][2]);
|
|
dJointSetHingeAxis(joint->ode_joint, aaa2[1][0], aaa2[1][1], aaa2[1][2]);
|
|
dJointSetHingeParam(joint->ode_joint, dParamFMax, info->FMax);
|
|
dJointSetHingeParam(joint->ode_joint, dParamHiStop, info->HiStop);
|
|
dJointSetHingeParam(joint->ode_joint, dParamLoStop, info->LoStop);
|
|
dJointSetHingeParam(joint->ode_joint, dParamStopCFM, info->CFM);
|
|
dJointSetHingeParam(joint->ode_joint, dParamStopERP, info->ERP);
|
|
dJointSetHingeParam(joint->ode_joint, dParamVel, info->Vel);
|
|
break;
|
|
case JOINTTYPE_SLIDER:
|
|
dJointSetSliderAxis(joint->ode_joint, aaa2[1][0], aaa2[1][1], aaa2[1][2]);
|
|
dJointSetSliderParam(joint->ode_joint, dParamFMax, info->FMax);
|
|
dJointSetSliderParam(joint->ode_joint, dParamHiStop, info->HiStop);
|
|
dJointSetSliderParam(joint->ode_joint, dParamLoStop, info->LoStop);
|
|
dJointSetSliderParam(joint->ode_joint, dParamStopCFM, info->CFM);
|
|
dJointSetSliderParam(joint->ode_joint, dParamStopERP, info->ERP);
|
|
dJointSetSliderParam(joint->ode_joint, dParamVel, info->Vel);
|
|
break;
|
|
case JOINTTYPE_UNIVERSAL:
|
|
dJointSetUniversalAnchor(joint->ode_joint, aaa2[0][0], aaa2[0][1], aaa2[0][2]);
|
|
dJointSetUniversalAxis1(joint->ode_joint, aaa2[1][0], aaa2[1][1], aaa2[1][2]);
|
|
dJointSetUniversalAxis2(joint->ode_joint, aaa2[2][0], aaa2[2][1], aaa2[2][2]);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamFMax, info->FMax);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamHiStop, info->HiStop);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamLoStop, info->LoStop);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamStopCFM, info->CFM);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamStopERP, info->ERP);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamVel, info->Vel);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamFMax2, info->FMax2);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamHiStop2, info->HiStop2);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamLoStop2, info->LoStop2);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamStopCFM2, info->CFM2);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamStopERP2, info->ERP2);
|
|
dJointSetUniversalParam(joint->ode_joint, dParamVel2, info->Vel2);
|
|
break;
|
|
case JOINTTYPE_HINGE2:
|
|
dJointSetHinge2Anchor(joint->ode_joint, aaa2[0][0], aaa2[0][1], aaa2[0][2]);
|
|
dJointSetHinge2Axis1(joint->ode_joint, aaa2[1][0], aaa2[1][1], aaa2[1][2]);
|
|
dJointSetHinge2Axis2(joint->ode_joint, aaa2[2][0], aaa2[2][1], aaa2[2][2]);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamFMax, info->FMax);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamHiStop, info->HiStop);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamLoStop, info->LoStop);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamStopCFM, info->CFM);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamStopERP, info->ERP);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamVel, info->Vel);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamFMax2, info->FMax2);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamHiStop2, info->HiStop2);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamLoStop2, info->LoStop2);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamStopCFM2, info->CFM2);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamStopERP2, info->ERP2);
|
|
dJointSetHinge2Param(joint->ode_joint, dParamVel2, info->Vel2);
|
|
break;
|
|
case JOINTTYPE_FIXED:
|
|
dJointSetFixed(joint->ode_joint);
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
static void QDECL World_Bullet_RagDestroyBody(world_t *world, rbebody_t *bodyptr)
|
|
{
|
|
bulletcontext_t *ctx = (bulletcontext_t*)world->rbe;
|
|
btRigidBody *body = (btRigidBody*)bodyptr->body;
|
|
btCollisionShape *geom = (btCollisionShape*)bodyptr->geom;
|
|
|
|
bodyptr->body = NULL;
|
|
bodyptr->geom = NULL;
|
|
|
|
if (body)
|
|
{
|
|
ctx->dworld->removeRigidBody(body);
|
|
delete body;
|
|
}
|
|
if (geom)
|
|
delete geom;
|
|
}
|
|
|
|
static void QDECL World_Bullet_RagDestroyJoint(world_t *world, rbejoint_t *joint)
|
|
{
|
|
/*
|
|
if (joint->ode_joint)
|
|
dJointDestroy(joint->ode_joint);
|
|
joint->ode_joint = NULL;
|
|
*/
|
|
}
|
|
|
|
//bullet gives us a handy way to get/set motion states. we can cheesily update entity fields this way.
|
|
class QCMotionState : public btMotionState
|
|
{
|
|
wedict_t *edict;
|
|
qboolean dirty;
|
|
btTransform trans;
|
|
world_t *world;
|
|
|
|
|
|
public:
|
|
void ReloadMotionState(void)
|
|
{
|
|
vec3_t offset;
|
|
vec3_t axis[3];
|
|
btVector3 org;
|
|
rbefuncs->AngleVectors(edict->v->angles, axis[0], axis[1], axis[2]);
|
|
VectorNegate(axis[1], axis[1]);
|
|
VectorAvg(edict->rbe.mins, edict->rbe.maxs, offset);
|
|
VectorMA(edict->v->origin, offset[0]*1, axis[0], org);
|
|
org[3] = 0;//for sse.
|
|
VectorMA(org, offset[1]*1, axis[1], org);
|
|
VectorMA(org, offset[2]*1, axis[2], org);
|
|
|
|
trans.setBasis(btMatrix3x3(axis[0][0], axis[1][0], axis[2][0],
|
|
axis[0][1], axis[1][1], axis[2][1],
|
|
axis[0][2], axis[1][2], axis[2][2]));
|
|
trans.setOrigin(org);
|
|
}
|
|
QCMotionState(wedict_t *ed, world_t *w)
|
|
{
|
|
dirty = qtrue;
|
|
edict = ed;
|
|
world = w;
|
|
|
|
ReloadMotionState();
|
|
}
|
|
virtual ~QCMotionState()
|
|
{
|
|
}
|
|
|
|
virtual void getWorldTransform(btTransform &worldTrans) const
|
|
{
|
|
worldTrans = trans;
|
|
}
|
|
|
|
virtual void setWorldTransform(const btTransform &worldTrans)
|
|
{
|
|
vec3_t fwd, left, up, offset;
|
|
trans = worldTrans;
|
|
|
|
btVector3 pos = worldTrans.getOrigin();
|
|
VectorCopy(worldTrans.getBasis().getColumn(0), fwd);
|
|
VectorCopy(worldTrans.getBasis().getColumn(1), left);
|
|
VectorCopy(worldTrans.getBasis().getColumn(2), up);
|
|
VectorAvg(edict->rbe.mins, edict->rbe.maxs, offset);
|
|
VectorMA(pos, offset[0]*-1, fwd, pos);
|
|
VectorMA(pos, offset[1]*-1, left, pos);
|
|
VectorMA(pos, offset[2]*-1, up, edict->v->origin);
|
|
|
|
rbefuncs->VectorAngles(fwd, up, edict->v->angles, (qboolean)NegativeMeshPitch(world, edict));
|
|
|
|
const btVector3 &vel = ((btRigidBody*)edict->rbe.body.body)->getLinearVelocity();
|
|
VectorCopy(vel.m_floats, edict->v->velocity);
|
|
|
|
//so it doesn't get rebuilt
|
|
VectorCopy(edict->v->origin, edict->rbe.origin);
|
|
VectorCopy(edict->v->angles, edict->rbe.angles);
|
|
VectorCopy(edict->v->velocity, edict->rbe.velocity);
|
|
|
|
//FIXME: relink the ent into the areagrid
|
|
}
|
|
};
|
|
|
|
static void World_Bullet_Frame_BodyFromEntity(world_t *world, wedict_t *ed)
|
|
{
|
|
bulletcontext_t *ctx = (bulletcontext_t*)world->rbe;
|
|
btRigidBody *body = NULL;
|
|
// btScalar mass;
|
|
float test;
|
|
// void *dataID;
|
|
model_t *model;
|
|
int axisindex;
|
|
int modelindex = 0;
|
|
int movetype = MOVETYPE_NONE;
|
|
int solid = SOLID_NOT;
|
|
int geomtype = GEOMTYPE_SOLID;
|
|
qboolean modified = qfalse;
|
|
vec3_t angles;
|
|
vec3_t avelocity;
|
|
vec3_t entmaxs;
|
|
vec3_t entmins;
|
|
vec3_t forward;
|
|
vec3_t geomcenter;
|
|
vec3_t geomsize;
|
|
vec3_t left;
|
|
vec3_t origin;
|
|
vec3_t spinvelocity;
|
|
vec3_t up;
|
|
vec3_t velocity;
|
|
// vec_t f;
|
|
vec_t length;
|
|
vec_t massval = 1.0f;
|
|
// vec_t movelimit;
|
|
vec_t radius;
|
|
vec_t scale;
|
|
// vec_t spinlimit;
|
|
qboolean gravity;
|
|
|
|
geomtype = (int)ed->xv->geomtype;
|
|
solid = (int)ed->v->solid;
|
|
movetype = (int)ed->v->movetype;
|
|
scale = ed->xv->scale?ed->xv->scale:1;
|
|
modelindex = 0;
|
|
model = NULL;
|
|
|
|
if (!geomtype)
|
|
{
|
|
switch((int)ed->v->solid)
|
|
{
|
|
case SOLID_NOT: geomtype = GEOMTYPE_NONE; break;
|
|
case SOLID_TRIGGER: geomtype = GEOMTYPE_NONE; break;
|
|
case SOLID_BSP: geomtype = GEOMTYPE_TRIMESH; break;
|
|
case SOLID_PHYSICS_TRIMESH: geomtype = GEOMTYPE_TRIMESH; break;
|
|
case SOLID_PHYSICS_BOX: geomtype = GEOMTYPE_BOX; break;
|
|
case SOLID_PHYSICS_SPHERE: geomtype = GEOMTYPE_SPHERE; break;
|
|
case SOLID_PHYSICS_CAPSULE: geomtype = GEOMTYPE_CAPSULE; break;
|
|
case SOLID_PHYSICS_CYLINDER:geomtype = GEOMTYPE_CYLINDER; break;
|
|
default: geomtype = GEOMTYPE_BOX; break;
|
|
}
|
|
}
|
|
|
|
switch(geomtype)
|
|
{
|
|
case GEOMTYPE_TRIMESH:
|
|
modelindex = (int)ed->v->modelindex;
|
|
model = world->Get_CModel(world, modelindex);
|
|
if (!model || model->loadstate != MLS_LOADED)
|
|
{
|
|
model = NULL;
|
|
modelindex = 0;
|
|
}
|
|
if (model)
|
|
{
|
|
VectorScale(model->mins, scale, entmins);
|
|
VectorScale(model->maxs, scale, entmaxs);
|
|
if (ed->xv->mass)
|
|
massval = ed->xv->mass;
|
|
}
|
|
else
|
|
{
|
|
modelindex = 0;
|
|
massval = 1.0f;
|
|
}
|
|
massval = 0; //bullet does not support trisoup moving through the world.
|
|
break;
|
|
case GEOMTYPE_BOX:
|
|
case GEOMTYPE_SPHERE:
|
|
case GEOMTYPE_CAPSULE:
|
|
case GEOMTYPE_CAPSULE_X:
|
|
case GEOMTYPE_CAPSULE_Y:
|
|
case GEOMTYPE_CAPSULE_Z:
|
|
case GEOMTYPE_CYLINDER:
|
|
case GEOMTYPE_CYLINDER_X:
|
|
case GEOMTYPE_CYLINDER_Y:
|
|
case GEOMTYPE_CYLINDER_Z:
|
|
VectorCopy(ed->v->mins, entmins);
|
|
VectorCopy(ed->v->maxs, entmaxs);
|
|
if (ed->xv->mass)
|
|
massval = ed->xv->mass;
|
|
break;
|
|
default:
|
|
// case GEOMTYPE_NONE:
|
|
if (ed->rbe.physics)
|
|
World_Bullet_RemoveFromEntity(world, ed);
|
|
return;
|
|
}
|
|
|
|
VectorSubtract(entmaxs, entmins, geomsize);
|
|
if (DotProduct(geomsize,geomsize) == 0)
|
|
{
|
|
// we don't allow point-size physics objects...
|
|
if (ed->rbe.physics)
|
|
World_Bullet_RemoveFromEntity(world, ed);
|
|
return;
|
|
}
|
|
|
|
// check if we need to create or replace the geom
|
|
if (!ed->rbe.physics
|
|
|| !VectorCompare(ed->rbe.mins, entmins)
|
|
|| !VectorCompare(ed->rbe.maxs, entmaxs)
|
|
|| ed->rbe.modelindex != modelindex)
|
|
{
|
|
btCollisionShape *geom;
|
|
|
|
modified = qtrue;
|
|
World_Bullet_RemoveFromEntity(world, ed);
|
|
ed->rbe.physics = qtrue;
|
|
VectorCopy(entmins, ed->rbe.mins);
|
|
VectorCopy(entmaxs, ed->rbe.maxs);
|
|
ed->rbe.modelindex = modelindex;
|
|
VectorAvg(entmins, entmaxs, geomcenter);
|
|
ed->rbe.movelimit = min(geomsize[0], min(geomsize[1], geomsize[2]));
|
|
|
|
/* memset(ed->rbe.offsetmatrix, 0, sizeof(ed->rbe.offsetmatrix));
|
|
ed->rbe.offsetmatrix[0] = 1;
|
|
ed->rbe.offsetmatrix[5] = 1;
|
|
ed->rbe.offsetmatrix[10] = 1;
|
|
ed->rbe.offsetmatrix[3] = -geomcenter[0];
|
|
ed->rbe.offsetmatrix[7] = -geomcenter[1];
|
|
ed->rbe.offsetmatrix[11] = -geomcenter[2];
|
|
*/
|
|
ed->rbe.mass = massval;
|
|
|
|
switch(geomtype)
|
|
{
|
|
case GEOMTYPE_TRIMESH:
|
|
// foo Matrix4x4_Identity(ed->rbe.offsetmatrix);
|
|
geom = NULL;
|
|
if (!model)
|
|
{
|
|
Con_Printf("entity %i (classname %s) has no model\n", NUM_FOR_EDICT(world->progs, (edict_t*)ed), PR_GetString(world->progs, ed->v->classname));
|
|
if (ed->rbe.physics)
|
|
World_Bullet_RemoveFromEntity(world, ed);
|
|
return;
|
|
}
|
|
if (!rbefuncs->GenerateCollisionMesh(world, model, ed, geomcenter))
|
|
{
|
|
if (ed->rbe.physics)
|
|
World_Bullet_RemoveFromEntity(world, ed);
|
|
return;
|
|
}
|
|
|
|
// foo Matrix4x4_RM_CreateTranslate(ed->rbe.offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]);
|
|
|
|
{
|
|
btTriangleIndexVertexArray *tiva = new btTriangleIndexVertexArray();
|
|
btIndexedMesh mesh;
|
|
mesh.m_vertexType = PHY_FLOAT;
|
|
mesh.m_indexType = PHY_INTEGER;
|
|
mesh.m_numTriangles = ed->rbe.numtriangles;
|
|
mesh.m_numVertices = ed->rbe.numvertices;
|
|
mesh.m_triangleIndexBase = (const unsigned char*)ed->rbe.element3i;
|
|
mesh.m_triangleIndexStride = sizeof(*ed->rbe.element3i)*3;
|
|
mesh.m_vertexBase = (const unsigned char*)ed->rbe.vertex3f;
|
|
mesh.m_vertexStride = sizeof(*ed->rbe.vertex3f)*3;
|
|
tiva->addIndexedMesh(mesh);
|
|
geom = new btBvhTriangleMeshShape(tiva, true);
|
|
}
|
|
break;
|
|
|
|
case GEOMTYPE_BOX:
|
|
geom = new btBoxShape(btVector3(geomsize[0], geomsize[1], geomsize[2]) * 0.5);
|
|
break;
|
|
|
|
case GEOMTYPE_SPHERE:
|
|
geom = new btSphereShape(geomsize[0] * 0.5f);
|
|
break;
|
|
|
|
case GEOMTYPE_CAPSULE:
|
|
case GEOMTYPE_CAPSULE_X:
|
|
case GEOMTYPE_CAPSULE_Y:
|
|
case GEOMTYPE_CAPSULE_Z:
|
|
if (geomtype == GEOMTYPE_CAPSULE)
|
|
{
|
|
// the qc gives us 3 axis radius, the longest axis is the capsule axis
|
|
axisindex = 0;
|
|
if (geomsize[axisindex] < geomsize[1])
|
|
axisindex = 1;
|
|
if (geomsize[axisindex] < geomsize[2])
|
|
axisindex = 2;
|
|
}
|
|
else
|
|
axisindex = geomtype-GEOMTYPE_CAPSULE_X;
|
|
if (axisindex == 0)
|
|
radius = min(geomsize[1], geomsize[2]) * 0.5f;
|
|
else if (axisindex == 1)
|
|
radius = min(geomsize[0], geomsize[2]) * 0.5f;
|
|
else
|
|
radius = min(geomsize[0], geomsize[1]) * 0.5f;
|
|
length = geomsize[axisindex] - radius*2;
|
|
if (length <= 0)
|
|
{
|
|
radius -= (1 - length)*0.5;
|
|
length = 1;
|
|
}
|
|
if (axisindex == 0)
|
|
geom = new btCapsuleShapeX(radius, length);
|
|
else if (axisindex == 1)
|
|
geom = new btCapsuleShape(radius, length);
|
|
else
|
|
geom = new btCapsuleShapeZ(radius, length);
|
|
break;
|
|
|
|
case GEOMTYPE_CYLINDER:
|
|
case GEOMTYPE_CYLINDER_X:
|
|
case GEOMTYPE_CYLINDER_Y:
|
|
case GEOMTYPE_CYLINDER_Z:
|
|
if (geomtype == GEOMTYPE_CYLINDER)
|
|
{
|
|
// the qc gives us 3 axis radius, the longest axis is the capsule axis
|
|
axisindex = 0;
|
|
if (geomsize[axisindex] < geomsize[1])
|
|
axisindex = 1;
|
|
if (geomsize[axisindex] < geomsize[2])
|
|
axisindex = 2;
|
|
}
|
|
else
|
|
axisindex = geomtype-GEOMTYPE_CYLINDER_X;
|
|
if (axisindex == 0)
|
|
geom = new btCylinderShapeX(btVector3(geomsize[0], geomsize[1], geomsize[2])*0.5);
|
|
else if (axisindex == 1)
|
|
geom = new btCylinderShape(btVector3(geomsize[0], geomsize[1], geomsize[2])*0.5);
|
|
else
|
|
geom = new btCylinderShapeZ(btVector3(geomsize[0], geomsize[1], geomsize[2])*0.5);
|
|
break;
|
|
|
|
default:
|
|
// Con_Printf("World_Bullet_BodyFromEntity: unrecognized solid value %i was accepted by filter\n", solid);
|
|
if (ed->rbe.physics)
|
|
World_Bullet_RemoveFromEntity(world, ed);
|
|
return;
|
|
}
|
|
// Matrix3x4_InvertTo4x4_Simple(ed->rbe.offsetmatrix, ed->rbe.offsetimatrix);
|
|
// ed->rbe.massbuf = BZ_Malloc(sizeof(dMass));
|
|
// memcpy(ed->rbe.massbuf, &mass, sizeof(dMass));
|
|
|
|
ed->rbe.body.geom = (void *)geom;
|
|
}
|
|
|
|
//non-moving objects need to be static objects (and thus need 0 mass)
|
|
if (movetype != MOVETYPE_PHYSICS && movetype != MOVETYPE_WALK) //enabling kinematic objects for everything else destroys framerates (!movetype || movetype == MOVETYPE_PUSH)
|
|
massval = 0;
|
|
|
|
//if the mass changes, we'll need to create a new body (but not the shape, so invalidate the current one)
|
|
if (ed->rbe.mass != massval)
|
|
{
|
|
ed->rbe.mass = massval;
|
|
body = (btRigidBody*)ed->rbe.body.body;
|
|
if (body)
|
|
ctx->dworld->removeRigidBody(body);
|
|
ed->rbe.body.body = NULL;
|
|
}
|
|
|
|
// if(ed->rbe.body.geom)
|
|
// dGeomSetData(ed->rbe.body.geom, (void*)ed);
|
|
if (movetype == MOVETYPE_PHYSICS && ed->rbe.mass)
|
|
{
|
|
if (ed->rbe.body.body == NULL)
|
|
{
|
|
// ed->rbe.body.body = (void *)(body = dBodyCreate(world->rbe.world));
|
|
// dGeomSetBody(ed->rbe.body.geom, body);
|
|
// dBodySetData(body, (void*)ed);
|
|
// dBodySetMass(body, (dMass *) ed->rbe.massbuf);
|
|
|
|
btVector3 fallInertia(0, 0, 0);
|
|
((btCollisionShape*)ed->rbe.body.geom)->calculateLocalInertia(ed->rbe.mass, fallInertia);
|
|
btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(ed->rbe.mass, new QCMotionState(ed,world), (btCollisionShape*)ed->rbe.body.geom, fallInertia);
|
|
body = new btRigidBody(fallRigidBodyCI);
|
|
body->setUserPointer(ed);
|
|
// btTransform trans;
|
|
// trans.setFromOpenGLMatrix(ed->rbe.offsetmatrix);
|
|
// body->setCenterOfMassTransform(trans);
|
|
ed->rbe.body.body = (void*)body;
|
|
|
|
//motion threshhold should be speed/physicsframerate.
|
|
//Threshhold enables CCD when the object moves faster than X
|
|
//FIXME: recalculate...
|
|
body->setCcdMotionThreshold((geomsize[0]+geomsize[1]+geomsize[2])*(4/3));
|
|
//radius should be the body's radius, or smaller.
|
|
body->setCcdSweptSphereRadius((geomsize[0]+geomsize[1]+geomsize[2])*(0.5/3));
|
|
|
|
ctx->dworld->addRigidBody(body, ed->xv->dimension_solid, ed->xv->dimension_hit);
|
|
|
|
modified = qtrue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ed->rbe.body.body == NULL)
|
|
{
|
|
btRigidBody::btRigidBodyConstructionInfo rbci(ed->rbe.mass, new QCMotionState(ed,world), (btCollisionShape*)ed->rbe.body.geom, btVector3(0, 0, 0));
|
|
body = new btRigidBody(rbci);
|
|
body->setUserPointer(ed);
|
|
// btTransform trans;
|
|
// trans.setFromOpenGLMatrix(ed->rbe.offsetmatrix);
|
|
// body->setCenterOfMassTransform(trans);
|
|
ed->rbe.body.body = (void*)body;
|
|
if (ed->rbe.mass)
|
|
body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
|
|
else
|
|
body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT);
|
|
ctx->dworld->addRigidBody(body, ed->xv->dimension_solid, ed->xv->dimension_hit);
|
|
|
|
modified = qtrue;
|
|
}
|
|
}
|
|
|
|
body = (btRigidBody*)ed->rbe.body.body;
|
|
|
|
// get current data from entity
|
|
gravity = qtrue;
|
|
VectorCopy(ed->v->origin, origin);
|
|
VectorCopy(ed->v->velocity, velocity);
|
|
VectorCopy(ed->v->angles, angles);
|
|
VectorCopy(ed->v->avelocity, avelocity);
|
|
if (ed == world->edicts || (ed->xv->gravity && ed->xv->gravity <= 0.01))
|
|
gravity = qfalse;
|
|
|
|
// compatibility for legacy entities
|
|
// if (!DotProduct(forward,forward) || solid == SOLID_BSP)
|
|
{
|
|
vec3_t qangles, qavelocity;
|
|
VectorCopy(angles, qangles);
|
|
VectorCopy(avelocity, qavelocity);
|
|
|
|
if (ed->v->modelindex)
|
|
{
|
|
model = world->Get_CModel(world, ed->v->modelindex);
|
|
if (!model || model->type == mod_alias)
|
|
{
|
|
qangles[PITCH] *= -1;
|
|
qavelocity[PITCH] *= -1;
|
|
}
|
|
}
|
|
|
|
rbefuncs->AngleVectors(qangles, forward, left, up);
|
|
VectorNegate(left,left);
|
|
// convert single-axis rotations in avelocity to spinvelocity
|
|
// FIXME: untested math - check signs
|
|
VectorSet(spinvelocity, DEG2RAD(qavelocity[PITCH]), DEG2RAD(qavelocity[ROLL]), DEG2RAD(qavelocity[YAW]));
|
|
}
|
|
|
|
// compatibility for legacy entities
|
|
switch (solid)
|
|
{
|
|
case SOLID_BBOX:
|
|
case SOLID_SLIDEBOX:
|
|
case SOLID_CORPSE:
|
|
VectorSet(forward, 1, 0, 0);
|
|
VectorSet(left, 0, 1, 0);
|
|
VectorSet(up, 0, 0, 1);
|
|
VectorSet(spinvelocity, 0, 0, 0);
|
|
break;
|
|
}
|
|
|
|
|
|
// we must prevent NANs...
|
|
test = DotProduct(origin,origin) + DotProduct(forward,forward) + DotProduct(left,left) + DotProduct(up,up) + DotProduct(velocity,velocity) + DotProduct(spinvelocity,spinvelocity);
|
|
if (IS_NAN(test))
|
|
{
|
|
modified = qtrue;
|
|
//Con_Printf("Fixing NAN values on entity %i : .classname = \"%s\" .origin = '%f %f %f' .velocity = '%f %f %f' .axis_forward = '%f %f %f' .axis_left = '%f %f %f' .axis_up = %f %f %f' .spinvelocity = '%f %f %f'\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.classname)->string), origin[0], origin[1], origin[2], velocity[0], velocity[1], velocity[2], forward[0], forward[1], forward[2], left[0], left[1], left[2], up[0], up[1], up[2], spinvelocity[0], spinvelocity[1], spinvelocity[2]);
|
|
Con_Printf("Fixing NAN values on entity %i : .classname = \"%s\" .origin = '%f %f %f' .velocity = '%f %f %f' .angles = '%f %f %f' .avelocity = '%f %f %f'\n", NUM_FOR_EDICT(world->progs, (edict_t*)ed), PR_GetString(world->progs, ed->v->classname), origin[0], origin[1], origin[2], velocity[0], velocity[1], velocity[2], angles[0], angles[1], angles[2], avelocity[0], avelocity[1], avelocity[2]);
|
|
test = DotProduct(origin,origin);
|
|
if (IS_NAN(test))
|
|
VectorClear(origin);
|
|
test = DotProduct(forward,forward) * DotProduct(left,left) * DotProduct(up,up);
|
|
if (IS_NAN(test))
|
|
{
|
|
VectorSet(angles, 0, 0, 0);
|
|
VectorSet(forward, 1, 0, 0);
|
|
VectorSet(left, 0, 1, 0);
|
|
VectorSet(up, 0, 0, 1);
|
|
}
|
|
test = DotProduct(velocity,velocity);
|
|
if (IS_NAN(test))
|
|
VectorClear(velocity);
|
|
test = DotProduct(spinvelocity,spinvelocity);
|
|
if (IS_NAN(test))
|
|
{
|
|
VectorClear(avelocity);
|
|
VectorClear(spinvelocity);
|
|
}
|
|
}
|
|
|
|
// check if the qc edited any position data
|
|
if (
|
|
0//!VectorCompare(origin, ed->rbe.origin)
|
|
|| !VectorCompare(velocity, ed->rbe.velocity)
|
|
//|| !VectorCompare(angles, ed->rbe.angles)
|
|
|| !VectorCompare(avelocity, ed->rbe.avelocity)
|
|
|| gravity != ed->rbe.gravity)
|
|
modified = qtrue;
|
|
|
|
// store the qc values into the physics engine
|
|
body = (btRigidBody*)ed->rbe.body.body;
|
|
if (modified && body)
|
|
{
|
|
// dVector3 r[3];
|
|
// float entitymatrix[16];
|
|
// float bodymatrix[16];
|
|
|
|
#if 0
|
|
Con_Printf("entity %i got changed by QC\n", (int) (ed - prog->edicts));
|
|
if(!VectorCompare(origin, ed->rbe.origin))
|
|
Con_Printf(" origin: %f %f %f -> %f %f %f\n", ed->rbe.origin[0], ed->rbe.origin[1], ed->rbe.origin[2], origin[0], origin[1], origin[2]);
|
|
if(!VectorCompare(velocity, ed->rbe.velocity))
|
|
Con_Printf(" velocity: %f %f %f -> %f %f %f\n", ed->rbe.velocity[0], ed->rbe.velocity[1], ed->rbe.velocity[2], velocity[0], velocity[1], velocity[2]);
|
|
if(!VectorCompare(angles, ed->rbe.angles))
|
|
Con_Printf(" angles: %f %f %f -> %f %f %f\n", ed->rbe.angles[0], ed->rbe.angles[1], ed->rbe.angles[2], angles[0], angles[1], angles[2]);
|
|
if(!VectorCompare(avelocity, ed->rbe.avelocity))
|
|
Con_Printf(" avelocity: %f %f %f -> %f %f %f\n", ed->rbe.avelocity[0], ed->rbe.avelocity[1], ed->rbe.avelocity[2], avelocity[0], avelocity[1], avelocity[2]);
|
|
if(gravity != ed->rbe.gravity)
|
|
Con_Printf(" gravity: %i -> %i\n", ed->ide.ode_gravity, gravity);
|
|
#endif
|
|
|
|
// values for BodyFromEntity to check if the qc modified anything later
|
|
VectorCopy(origin, ed->rbe.origin);
|
|
VectorCopy(velocity, ed->rbe.velocity);
|
|
VectorCopy(angles, ed->rbe.angles);
|
|
VectorCopy(avelocity, ed->rbe.avelocity);
|
|
ed->rbe.gravity = gravity;
|
|
|
|
// foo Matrix4x4_RM_FromVectors(entitymatrix, forward, left, up, origin);
|
|
// foo Matrix4_Multiply(ed->rbe.offsetmatrix, entitymatrix, bodymatrix);
|
|
// foo Matrix3x4_RM_ToVectors(bodymatrix, forward, left, up, origin);
|
|
|
|
// r[0][0] = forward[0];
|
|
// r[1][0] = forward[1];
|
|
// r[2][0] = forward[2];
|
|
// r[0][1] = left[0];
|
|
// r[1][1] = left[1];
|
|
// r[2][1] = left[2];
|
|
// r[0][2] = up[0];
|
|
// r[1][2] = up[1];
|
|
// r[2][2] = up[2];
|
|
|
|
QCMotionState *ms = (QCMotionState*)body->getMotionState();
|
|
ms->ReloadMotionState();
|
|
body->setMotionState(ms);
|
|
body->setLinearVelocity(btVector3(velocity[0], velocity[1], velocity[2]));
|
|
body->setAngularVelocity(btVector3(spinvelocity[0], spinvelocity[1], spinvelocity[2]));
|
|
// body->setGravity(btVector3(ed->xv->gravitydir[0], ed->xv->gravitydir[1], ed->xv->gravitydir[2]) * ed->xv->gravity);
|
|
|
|
//something changed. make sure it still falls over appropriately
|
|
body->setActivationState(1);
|
|
}
|
|
|
|
/* FIXME: check if we actually need this insanity with bullet (ode sucks).
|
|
if(body)
|
|
{
|
|
// limit movement speed to prevent missed collisions at high speed
|
|
btVector3 ovelocity = body->getLinearVelocity();
|
|
btVector3 ospinvelocity = body->getAngularVelocity();
|
|
movelimit = ed->rbe.movelimit * world->rbe.movelimit;
|
|
test = DotProduct(ovelocity,ovelocity);
|
|
if (test > movelimit*movelimit)
|
|
{
|
|
// scale down linear velocity to the movelimit
|
|
// scale down angular velocity the same amount for consistency
|
|
f = movelimit / sqrt(test);
|
|
VectorScale(ovelocity, f, velocity);
|
|
VectorScale(ospinvelocity, f, spinvelocity);
|
|
body->setLinearVelocity(btVector3(velocity[0], velocity[1], velocity[2]));
|
|
body->setAngularVelocity(btVector3(spinvelocity[0], spinvelocity[1], spinvelocity[2]));
|
|
}
|
|
|
|
// make sure the angular velocity is not exploding
|
|
spinlimit = physics_bullet_spinlimit.value;
|
|
test = DotProduct(ospinvelocity,ospinvelocity);
|
|
if (test > spinlimit)
|
|
{
|
|
body->setAngularVelocity(btVector3(0, 0, 0));
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
#define MAX_CONTACTS 16
|
|
static void VARGS nearCallback (void *data, dGeomID o1, dGeomID o2)
|
|
{
|
|
world_t *world = (world_t *)data;
|
|
dContact contact[MAX_CONTACTS]; // max contacts per collision pair
|
|
dBodyID b1;
|
|
dBodyID b2;
|
|
dJointID c;
|
|
int i;
|
|
int numcontacts;
|
|
|
|
float bouncefactor1 = 0.0f;
|
|
float bouncestop1 = 60.0f / 800.0f;
|
|
float bouncefactor2 = 0.0f;
|
|
float bouncestop2 = 60.0f / 800.0f;
|
|
float erp;
|
|
dVector3 grav;
|
|
wedict_t *ed1, *ed2;
|
|
|
|
if (dGeomIsSpace(o1) || dGeomIsSpace(o2))
|
|
{
|
|
// colliding a space with something
|
|
dSpaceCollide2(o1, o2, data, &nearCallback);
|
|
// Note we do not want to test intersections within a space,
|
|
// only between spaces.
|
|
//if (dGeomIsSpace(o1)) dSpaceCollide(o1, data, &nearCallback);
|
|
//if (dGeomIsSpace(o2)) dSpaceCollide(o2, data, &nearCallback);
|
|
return;
|
|
}
|
|
|
|
b1 = dGeomGetBody(o1);
|
|
b2 = dGeomGetBody(o2);
|
|
|
|
// at least one object has to be using MOVETYPE_PHYSICS or we just don't care
|
|
if (!b1 && !b2)
|
|
return;
|
|
|
|
// exit without doing anything if the two bodies are connected by a joint
|
|
if (b1 && b2 && dAreConnectedExcluding(b1, b2, dJointTypeContact))
|
|
return;
|
|
|
|
ed1 = (wedict_t *) dGeomGetData(o1);
|
|
ed2 = (wedict_t *) dGeomGetData(o2);
|
|
if (ed1 == ed2 && ed1)
|
|
{
|
|
//ragdolls don't make contact with the bbox of the doll entity
|
|
//the origional entity should probably not be solid anyway.
|
|
//these bodies should probably not collide against bboxes of other entities with ragdolls either, but meh.
|
|
if (ed1->rbe.body.body == b1 || ed2->rbe.body == b2)
|
|
return;
|
|
}
|
|
if(!ed1 || ed1->isfree)
|
|
ed1 = world->edicts;
|
|
if(!ed2 || ed2->isfree)
|
|
ed2 = world->edicts;
|
|
|
|
// generate contact points between the two non-space geoms
|
|
numcontacts = dCollide(o1, o2, MAX_CONTACTS, &(contact[0].geom), sizeof(contact[0]));
|
|
if (numcontacts)
|
|
{
|
|
if(ed1 && ed1->v->touch)
|
|
{
|
|
world->Event_Touch(world, ed1, ed2);
|
|
}
|
|
if(ed2 && ed2->v->touch)
|
|
{
|
|
world->Event_Touch(world, ed2, ed1);
|
|
}
|
|
|
|
// if either ent killed itself, don't collide
|
|
if ((ed1&&ed1->isfree) || (ed2&&ed2->isfree))
|
|
return;
|
|
}
|
|
|
|
if(ed1)
|
|
{
|
|
if (ed1->xv->bouncefactor)
|
|
bouncefactor1 = ed1->xv->bouncefactor;
|
|
|
|
if (ed1->xv->bouncestop)
|
|
bouncestop1 = ed1->xv->bouncestop;
|
|
}
|
|
|
|
if(ed2)
|
|
{
|
|
if (ed2->xv->bouncefactor)
|
|
bouncefactor2 = ed2->xv->bouncefactor;
|
|
|
|
if (ed2->xv->bouncestop)
|
|
bouncestop2 = ed2->xv->bouncestop;
|
|
}
|
|
|
|
if ((ed2->entnum&&ed1->v->owner == ed2->entnum) || (ed1->entnum&&ed2->v->owner == ed1->entnum))
|
|
return;
|
|
|
|
// merge bounce factors and bounce stop
|
|
if(bouncefactor2 > 0)
|
|
{
|
|
if(bouncefactor1 > 0)
|
|
{
|
|
// TODO possibly better logic to merge bounce factor data?
|
|
if(bouncestop2 < bouncestop1)
|
|
bouncestop1 = bouncestop2;
|
|
if(bouncefactor2 > bouncefactor1)
|
|
bouncefactor1 = bouncefactor2;
|
|
}
|
|
else
|
|
{
|
|
bouncestop1 = bouncestop2;
|
|
bouncefactor1 = bouncefactor2;
|
|
}
|
|
}
|
|
dWorldGetGravity(world->rbe.world, grav);
|
|
bouncestop1 *= fabs(grav[2]);
|
|
|
|
erp = (DotProduct(ed1->v->velocity, ed1->v->velocity) > DotProduct(ed2->v->velocity, ed2->v->velocity)) ? ed1->xv->erp : ed2->xv->erp;
|
|
|
|
// add these contact points to the simulation
|
|
for (i = 0;i < numcontacts;i++)
|
|
{
|
|
contact[i].surface.mode = (physics_bullet_contact_mu.value != -1 ? dContactApprox1 : 0) |
|
|
(physics_bullet_contact_erp.value != -1 ? dContactSoftERP : 0) |
|
|
(physics_bullet_contact_cfm.value != -1 ? dContactSoftCFM : 0) |
|
|
(bouncefactor1 > 0 ? dContactBounce : 0);
|
|
contact[i].surface.mu = physics_bullet_contact_mu.value;
|
|
if (ed1->xv->friction)
|
|
contact[i].surface.mu *= ed1->xv->friction;
|
|
if (ed2->xv->friction)
|
|
contact[i].surface.mu *= ed2->xv->friction;
|
|
contact[i].surface.mu2 = 0;
|
|
contact[i].surface.soft_erp = physics_bullet_contact_erp.value + erp;
|
|
contact[i].surface.soft_cfm = physics_bullet_contact_cfm.value;
|
|
contact[i].surface.bounce = bouncefactor1;
|
|
contact[i].surface.bounce_vel = bouncestop1;
|
|
c = dJointCreateContact(world->rbe.world, world->rbe.contactgroup, contact + i);
|
|
dJointAttach(c, b1, b2);
|
|
}
|
|
}
|
|
*/
|
|
|
|
static void QDECL World_Bullet_Frame(world_t *world, double frametime, double gravity)
|
|
{
|
|
struct bulletcontext_s *ctx = (struct bulletcontext_s*)world->rbe;
|
|
if (world->rbe_hasphysicsents || ctx->hasextraobjs)
|
|
{
|
|
int iters;
|
|
unsigned int i;
|
|
wedict_t *ed;
|
|
|
|
// world->rbe.iterations = bound(1, physics_bullet_iterationsperframe.ival, 1000);
|
|
// world->rbe.step = frametime / world->rbe.iterations;
|
|
// world->rbe.movelimit = physics_bullet_movelimit.value / world->rbe.step;
|
|
|
|
|
|
// copy physics properties from entities to physics engine
|
|
for (i = 0;i < world->num_edicts;i++)
|
|
{
|
|
ed = (wedict_t*)EDICT_NUM_PB(world->progs, i);
|
|
if (!ED_ISFREE(ed))
|
|
World_Bullet_Frame_BodyFromEntity(world, ed);
|
|
}
|
|
// oh, and it must be called after all bodies were created
|
|
for (i = 0;i < world->num_edicts;i++)
|
|
{
|
|
ed = (wedict_t*)EDICT_NUM_PB(world->progs, i);
|
|
if (!ED_ISFREE(ed))
|
|
World_Bullet_Frame_JointFromEntity(world, ed);
|
|
}
|
|
while(ctx->cmdqueuehead)
|
|
{
|
|
rbecommandqueue_t *cmd = ctx->cmdqueuehead;
|
|
ctx->cmdqueuehead = cmd->next;
|
|
if (!cmd->next)
|
|
ctx->cmdqueuetail = NULL;
|
|
World_Bullet_RunCmd(world, cmd);
|
|
Z_Free(cmd);
|
|
}
|
|
|
|
ctx->dworld->setGravity(btVector3(0, 0, -gravity));
|
|
|
|
iters=physics_bullet_maxiterationsperframe->value;
|
|
if (iters < 0)
|
|
iters = 0;
|
|
ctx->dworld->stepSimulation(frametime, iters, 1/bound(1, physics_bullet_framerate->value, 500));
|
|
|
|
// set the tolerance for closeness of objects
|
|
// dWorldSetContactSurfaceLayer(world->rbe.world, max(0, physics_bullet_contactsurfacelayer.value));
|
|
|
|
// run collisions for the current world state, creating JointGroup
|
|
// dSpaceCollide(world->rbe.space, (void *)world, nearCallback);
|
|
|
|
// run physics (move objects, calculate new velocities)
|
|
// if (physics_bullet_worldquickstep.ival)
|
|
// {
|
|
// dWorldSetQuickStepNumIterations(world->rbe.world, bound(1, physics_bullet_worldquickstep_iterations.ival, 200));
|
|
// dWorldQuickStep(world->rbe.world, world->rbe.step);
|
|
// }
|
|
// else
|
|
// dWorldStep(world->rbe.world, world->rbe.step);
|
|
|
|
// clear the JointGroup now that we're done with it
|
|
// dJointGroupEmpty(world->rbe.contactgroup);
|
|
|
|
if (world->rbe_hasphysicsents)
|
|
{
|
|
// copy physics properties from physics engine to entities
|
|
for (i = 1;i < world->num_edicts;i++)
|
|
{
|
|
ed = (wedict_t*)EDICT_NUM_PB(world->progs, i);
|
|
if (!ED_ISFREE(ed))
|
|
World_Bullet_Frame_BodyToEntity(world, ed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void World_Bullet_RunCmd(world_t *world, rbecommandqueue_t *cmd)
|
|
{
|
|
btRigidBody *body = (btRigidBody*)(cmd->edict->rbe.body.body);
|
|
switch(cmd->command)
|
|
{
|
|
case RBECMD_ENABLE:
|
|
if (body)
|
|
body->setActivationState(1);
|
|
break;
|
|
case RBECMD_DISABLE:
|
|
if (body)
|
|
body->setActivationState(0);
|
|
break;
|
|
case RBECMD_FORCE:
|
|
if (body)
|
|
{
|
|
btVector3 relativepos;
|
|
const btVector3 ¢er = body->getCenterOfMassPosition();
|
|
VectorSubtract(cmd->v2, center, relativepos);
|
|
body->setActivationState(1);
|
|
body->applyImpulse(btVector3(cmd->v1[0], cmd->v1[1], cmd->v1[2]), relativepos);
|
|
}
|
|
break;
|
|
case RBECMD_TORQUE:
|
|
if (cmd->edict->rbe.body.body)
|
|
{
|
|
body->setActivationState(1);
|
|
body->applyTorque(btVector3(cmd->v1[0], cmd->v1[1], cmd->v1[2]));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void QDECL World_Bullet_PushCommand(world_t *world, rbecommandqueue_t *val)
|
|
{
|
|
struct bulletcontext_s *ctx = (struct bulletcontext_s*)world->rbe;
|
|
rbecommandqueue_t *cmd = (rbecommandqueue_t*)BZ_Malloc(sizeof(*cmd));
|
|
world->rbe_hasphysicsents = qtrue; //just in case.
|
|
memcpy(cmd, val, sizeof(*cmd));
|
|
cmd->next = NULL;
|
|
//add on the end of the queue, so that order is preserved.
|
|
if (ctx->cmdqueuehead)
|
|
{
|
|
rbecommandqueue_t *ot = ctx->cmdqueuetail;
|
|
ot->next = ctx->cmdqueuetail = cmd;
|
|
}
|
|
else
|
|
ctx->cmdqueuetail = ctx->cmdqueuehead = cmd;
|
|
}
|
|
|
|
static void QDECL World_Bullet_TraceEntity(world_t *world, wedict_t *ed, vec3_t start, vec3_t end, trace_t *trace)
|
|
{
|
|
struct bulletcontext_s *ctx = (struct bulletcontext_s*)world->rbe;
|
|
btCollisionShape *shape = (btCollisionShape*)ed->rbe.body.geom;
|
|
|
|
//btCollisionAlgorithm
|
|
class myConvexResultCallback : public btCollisionWorld::ConvexResultCallback
|
|
{
|
|
public:
|
|
void *m_impactent;
|
|
btVector3 m_impactpos;
|
|
btVector3 m_impactnorm;
|
|
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
|
|
{
|
|
if (m_closestHitFraction > convexResult.m_hitFraction)
|
|
{
|
|
m_closestHitFraction = convexResult.m_hitFraction;
|
|
m_impactpos = convexResult.m_hitPointLocal;
|
|
m_impactnorm = convexResult.m_hitNormalLocal;
|
|
m_impactent = convexResult.m_hitCollisionObject->getUserPointer();
|
|
}
|
|
return 0;
|
|
}
|
|
} result;
|
|
result.m_impactent = NULL;
|
|
result.m_closestHitFraction = trace->fraction;
|
|
|
|
btTransform from(btMatrix3x3(1, 0, 0, 0, 1, 0, 0, 0, 1), btVector3(start[0], start[1], start[2]));
|
|
btTransform to(btMatrix3x3(1, 0, 0, 0, 1, 0, 0, 0, 1), btVector3(end[0], end[1], end[2]));
|
|
ctx->dworld->convexSweepTest((btConvexShape*)shape, from, to, result, 1);
|
|
|
|
if (result.m_impactent)
|
|
{
|
|
memset(trace, 0, sizeof(*trace));
|
|
trace->fraction = trace->truefraction = result.m_closestHitFraction;
|
|
VectorInterpolate(start, result.m_closestHitFraction, end, trace->endpos);
|
|
// VectorCopy(result.m_impactpos, trace->endpos);
|
|
VectorCopy(result.m_impactnorm, trace->plane.normal);
|
|
trace->ent = result.m_impactent;
|
|
trace->startsolid = qfalse; //FIXME: we don't really know
|
|
}
|
|
}
|
|
|
|
static void QDECL World_Bullet_Start(world_t *world)
|
|
{
|
|
struct bulletcontext_s *ctx;
|
|
if (world->rbe)
|
|
return; //no thanks, we already have one. somehow.
|
|
|
|
if (!physics_bullet_enable->value)
|
|
return;
|
|
|
|
ctx = (struct bulletcontext_s*)BZ_Malloc(sizeof(*ctx));
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
ctx->gworld = world;
|
|
ctx->funcs.End = World_Bullet_End;
|
|
ctx->funcs.RemoveJointFromEntity = World_Bullet_RemoveJointFromEntity;
|
|
ctx->funcs.RemoveFromEntity = World_Bullet_RemoveFromEntity;
|
|
ctx->funcs.RagMatrixToBody = World_Bullet_RagMatrixToBody;
|
|
ctx->funcs.RagCreateBody = World_Bullet_RagCreateBody;
|
|
ctx->funcs.RagMatrixFromJoint = World_Bullet_RagMatrixFromJoint;
|
|
ctx->funcs.RagMatrixFromBody = World_Bullet_RagMatrixFromBody;
|
|
ctx->funcs.RagEnableJoint = World_Bullet_RagEnableJoint;
|
|
ctx->funcs.RagCreateJoint = World_Bullet_RagCreateJoint;
|
|
ctx->funcs.RagDestroyBody = World_Bullet_RagDestroyBody;
|
|
ctx->funcs.RagDestroyJoint = World_Bullet_RagDestroyJoint;
|
|
ctx->funcs.RunFrame = World_Bullet_Frame;
|
|
ctx->funcs.PushCommand = World_Bullet_PushCommand;
|
|
ctx->funcs.Trace = World_Bullet_TraceEntity;
|
|
world->rbe = &ctx->funcs;
|
|
|
|
|
|
ctx->broadphase = new btDbvtBroadphase();
|
|
ctx->collisionconfig = new btDefaultCollisionConfiguration();
|
|
ctx->collisiondispatcher = new btCollisionDispatcher(ctx->collisionconfig);
|
|
ctx->solver = new btSequentialImpulseConstraintSolver;
|
|
ctx->dworld = new btDiscreteDynamicsWorld(ctx->collisiondispatcher, ctx->broadphase, ctx->solver, ctx->collisionconfig);
|
|
|
|
ctx->ownerfilter = new QCFilterCallback();
|
|
ctx->dworld->getPairCache()->setOverlapFilterCallback(ctx->ownerfilter);
|
|
|
|
|
|
|
|
ctx->dworld->setGravity(btVector3(0, -10, 0));
|
|
|
|
/*
|
|
if(physics_bullet_world_erp.value >= 0)
|
|
dWorldSetERP(world->rbe.world, physics_bullet_world_erp.value);
|
|
if(physics_bullet_world_cfm.value >= 0)
|
|
dWorldSetCFM(world->rbe.world, physics_bullet_world_cfm.value);
|
|
if (physics_bullet_world_damping.ival)
|
|
{
|
|
dWorldSetLinearDamping(world->rbe.world, (physics_bullet_world_damping_linear.value >= 0) ? (physics_bullet_world_damping_linear.value * physics_bullet_world_damping.value) : 0);
|
|
dWorldSetLinearDampingThreshold(world->rbe.world, (physics_bullet_world_damping_linear_threshold.value >= 0) ? (physics_bullet_world_damping_linear_threshold.value * physics_bullet_world_damping.value) : 0);
|
|
dWorldSetAngularDamping(world->rbe.world, (physics_bullet_world_damping_angular.value >= 0) ? (physics_bullet_world_damping_angular.value * physics_bullet_world_damping.value) : 0);
|
|
dWorldSetAngularDampingThreshold(world->rbe.world, (physics_bullet_world_damping_angular_threshold.value >= 0) ? (physics_bullet_world_damping_angular_threshold.value * physics_bullet_world_damping.value) : 0);
|
|
}
|
|
else
|
|
{
|
|
dWorldSetLinearDamping(world->rbe.world, 0);
|
|
dWorldSetLinearDampingThreshold(world->rbe.world, 0);
|
|
dWorldSetAngularDamping(world->rbe.world, 0);
|
|
dWorldSetAngularDampingThreshold(world->rbe.world, 0);
|
|
}
|
|
if (physics_bullet_autodisable.ival)
|
|
{
|
|
dWorldSetAutoDisableSteps(world->rbe.world, bound(1, physics_bullet_autodisable_steps.ival, 100));
|
|
dWorldSetAutoDisableTime(world->rbe.world, physics_bullet_autodisable_time.value);
|
|
dWorldSetAutoDisableAverageSamplesCount(world->rbe.world, bound(1, physics_bullet_autodisable_threshold_samples.ival, 100));
|
|
dWorldSetAutoDisableLinearThreshold(world->rbe.world, physics_bullet_autodisable_threshold_linear.value);
|
|
dWorldSetAutoDisableAngularThreshold(world->rbe.world, physics_bullet_autodisable_threshold_angular.value);
|
|
dWorldSetAutoDisableFlag (world->rbe.world, true);
|
|
}
|
|
else
|
|
dWorldSetAutoDisableFlag (world->rbe.world, false);
|
|
*/
|
|
}
|
|
|
|
static qintptr_t QDECL World_Bullet_Shutdown(qintptr_t *args)
|
|
{
|
|
if (rbefuncs)
|
|
rbefuncs->UnregisterPhysicsEngine("Bullet");
|
|
return 0;
|
|
}
|
|
|
|
static bool World_Bullet_DoInit(void)
|
|
{
|
|
if (!rbefuncs || !rbefuncs->RegisterPhysicsEngine)
|
|
{
|
|
rbefuncs = NULL;
|
|
Con_Printf("Bullet plugin failed: Engine doesn't support physics engine plugins.\n");
|
|
}
|
|
else if (!rbefuncs->RegisterPhysicsEngine("Bullet", World_Bullet_Start))
|
|
Con_Printf("Bullet plugin failed: Engine already has a physics plugin active.\n");
|
|
else
|
|
{
|
|
World_Bullet_Init();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#define ARGNAMES ,version
|
|
static BUILTINR(rbeplugfuncs_t*, RBE_GetPluginFuncs, (int version));
|
|
#undef ARGNAMES
|
|
|
|
extern "C" qintptr_t Plug_Init(qintptr_t *args)
|
|
{
|
|
CHECKBUILTIN(RBE_GetPluginFuncs);
|
|
|
|
if (BUILTINISVALID(RBE_GetPluginFuncs))
|
|
{
|
|
rbefuncs = pRBE_GetPluginFuncs(sizeof(rbeplugfuncs_t));
|
|
if (rbefuncs && rbefuncs->version < RBEPLUGFUNCS_VERSION)
|
|
rbefuncs = NULL;
|
|
}
|
|
|
|
Plug_Export("Shutdown", World_Bullet_Shutdown);
|
|
return World_Bullet_DoInit();
|
|
}
|
|
|
|
|