1
0
Fork 0
forked from fte/fteqw
fteqw/plugins/bullet/bulletplug.cpp
Spoike 559144cb05 Updated font code for freecs's benefit.
added cl_iDrive cvar.
tried to solve invalid server address errors.
fixed envmap+screenshot_cubemap commands (they write slightly different cubemaps).
rework server favourites a little, to avoid conflicts with other engines.
fix legacy rtlights on certain maps.
added 'rgbgen entitylighting' for lighting with colormod support.
decompiler should cope with more instructions now. pretty much just switch opcodes that are bugged now.
implemented a couple of joint types into the bullet plugin. still useless though.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5180 fc73d0e0-1445-4013-8a0c-d673dee63da5
2017-12-09 21:22:46 +00:00

1814 lines
60 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_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
static vec3_t vec3_origin;
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>
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");
pr_meshpitch = pCvar_GetNVFDG("r_meshpitch", "-1", 0, "", "Bullet");
}
void World_Bullet_Shutdown(void)
{
}
typedef struct bulletcontext_s
{
rigidbodyengine_t funcs;
qboolean 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->ode.ode_joint_type = 0;
// if(ed->ode.ode_joint)
// dJointDestroy((dJointID)ed->ode.ode_joint);
ed->ode.ode_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->ode.ode_physics)
return;
// entity is not physics controlled, free any physics data
ed->ode.ode_physics = qfalse;
body = (btRigidBody*)ed->ode.ode_body;
ed->ode.ode_body = NULL;
if (body)
ctx->dworld->removeRigidBody (body);
geom = (btCollisionShape*)ed->ode.ode_geom;
ed->ode.ode_geom = NULL;
if (ed->ode.ode_geom)
delete geom;
//FIXME: joints
rbefuncs->ReleaseCollisionMesh(ed);
if(ed->ode.ode_massbuf)
BZ_Free(ed->ode.ode_massbuf);
ed->ode.ode_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->ode.ode_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->ode.ode_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->ode.ode_origin);
VectorCopy(velocity, ed->ode.ode_velocity);
VectorCopy(angles, ed->ode.ode_angles);
VectorCopy(avelocity, ed->ode.ode_avelocity);
// ed->ode.ode_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, 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->ode.ode_body;
if(ED_ISFREE(e1) || !b1)
enemy = 0;
e2 = (wedict_t*)PROG_TO_EDICT(world->progs, aiment);
b2 = (btRigidBody*)e2->ode.ode_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->ode.ode_joint_type && VectorCompare(origin, ed->ode.ode_joint_origin) && VectorCompare(velocity, ed->ode.ode_joint_velocity) && VectorCompare(ed->v->angles, ed->ode.ode_joint_angles) && enemy == ed->ode.ode_joint_enemy && aiment == ed->ode.ode_joint_aiment && VectorCompare(movedir, ed->ode.ode_joint_movedir))
return; // nothing to do
if(ed->ode.ode_joint)
{
j = (btTypedConstraint*)ed->ode.ode_joint;
rbe->dworld->removeConstraint(j);
ed->ode.ode_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->ode.ode_joint_type = jointtype;
ed->ode.ode_joint_enemy = enemy;
ed->ode.ode_joint_aiment = aiment;
VectorCopy(origin, ed->ode.ode_joint_origin);
VectorCopy(velocity, ed->ode.ode_joint_velocity);
VectorCopy(ed->v->angles, ed->ode.ode_joint_angles);
VectorCopy(movedir, ed->ode.ode_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->ode.ode_joint = (void *) j;
if (j)
{
j->setUserConstraintPtr((void *) ed);
rbe->dworld->addConstraint(j, false);
}
}
static qboolean QDECL World_Bullet_RagMatrixToBody(rbebody_t *bodyptr, float *mat)
{
btRigidBody *body;
/*
dVector3 r[3];
r[0][0] = mat[0];
r[0][1] = mat[1];
r[0][2] = mat[2];
r[1][0] = mat[4];
r[1][1] = mat[5];
r[1][2] = mat[6];
r[2][0] = mat[8];
r[2][1] = mat[9];
r[2][2] = mat[10];
dBodySetPosition(bodyptr->ode_body, mat[3], mat[7], mat[11]);
dBodySetRotation(bodyptr->ode_body, r[0]);
dBodySetLinearVel(bodyptr->ode_body, 0, 0, 0);
dBodySetAngularVel(bodyptr->ode_body, 0, 0, 0);
*/
return qtrue;
}
static qboolean QDECL World_Bullet_RagCreateBody(world_t *world, rbebody_t *bodyptr, rbebodyinfo_t *bodyinfo, float *mat, wedict_t *ent)
{
/*
dMass mass;
float radius;
if (!world->ode.ode_space)
return false;
world->ode.hasodeents = true; //I don't like this, but we need the world etc to be solid.
world->ode.hasextraobjs = true;
switch(bodyinfo->geomshape)
{
case GEOMTYPE_CAPSULE:
radius = (bodyinfo->dimensions[0] + bodyinfo->dimensions[1]) * 0.5;
bodyptr->ode_geom = (void *)dCreateCapsule(world->ode.ode_space, radius, bodyinfo->dimensions[2]);
dMassSetCapsuleTotal(&mass, bodyinfo->mass, 3, radius, bodyinfo->dimensions[2]);
//aligned along the geom's local z axis
break;
case GEOMTYPE_SPHERE:
//radius
radius = (bodyinfo->dimensions[0] + bodyinfo->dimensions[1] + bodyinfo->dimensions[2]) / 3;
bodyptr->ode_geom = dCreateSphere(world->ode.ode_space, radius);
dMassSetSphereTotal(&mass, bodyinfo->mass, radius);
//aligned along the geom's local z axis
break;
case GEOMTYPE_CYLINDER:
//radius, length
radius = (bodyinfo->dimensions[0] + bodyinfo->dimensions[1]) * 0.5;
bodyptr->ode_geom = dCreateCylinder(world->ode.ode_space, radius, bodyinfo->dimensions[2]);
dMassSetCylinderTotal(&mass, bodyinfo->mass, 3, radius, bodyinfo->dimensions[2]);
//alignment is irreleevnt, thouse I suppose it might be scaled wierdly.
break;
default:
case GEOMTYPE_BOX:
//diameter
bodyptr->ode_geom = dCreateBox(world->ode.ode_space, bodyinfo->dimensions[0], bodyinfo->dimensions[1], bodyinfo->dimensions[2]);
dMassSetBoxTotal(&mass, bodyinfo->mass, bodyinfo->dimensions[0], bodyinfo->dimensions[1], bodyinfo->dimensions[2]);
//monkey
break;
}
bodyptr->ode_body = dBodyCreate(world->ode.ode_world);
dBodySetMass(bodyptr->ode_body, &mass);
dGeomSetBody(bodyptr->ode_geom, bodyptr->ode_body);
dGeomSetData(bodyptr->ode_geom, (void*)ent);
*/
return World_Bullet_RagMatrixToBody(bodyptr, mat);
}
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)
{
/*
const dReal *o = dBodyGetPosition(bodyptr->ode_body);
const dReal *r = dBodyGetRotation(bodyptr->ode_body);
mat[0] = r[0];
mat[1] = r[1];
mat[2] = r[2];
mat[3] = o[0];
mat[4] = r[4];
mat[5] = r[5];
mat[6] = r[6];
mat[7] = o[1];
mat[8] = r[8];
mat[9] = r[9];
mat[10] = r[10];
mat[11] = o[2];
*/
}
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->ode.ode_world, 0);
break;
case JOINTTYPE_HINGE:
joint->ode_joint = dJointCreateHinge(world->ode.ode_world, 0);
break;
case JOINTTYPE_SLIDER:
joint->ode_joint = dJointCreateSlider(world->ode.ode_world, 0);
break;
case JOINTTYPE_UNIVERSAL:
joint->ode_joint = dJointCreateUniversal(world->ode.ode_world, 0);
break;
case JOINTTYPE_HINGE2:
joint->ode_joint = dJointCreateHinge2(world->ode.ode_world, 0);
break;
case JOINTTYPE_FIXED:
joint->ode_joint = dJointCreateFixed(world->ode.ode_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)
{
/*
if (bodyptr->ode_geom)
dGeomDestroy(bodyptr->ode_geom);
bodyptr->ode_geom = NULL;
if (bodyptr->ode_body)
dBodyDestroy(bodyptr->ode_body);
bodyptr->ode_body = NULL;
*/
}
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->ode.ode_mins, edict->ode.ode_maxs, offset);
VectorMA(edict->v->origin, offset[0]*1, axis[0], org);
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->ode.ode_mins, edict->ode.ode_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->ode.ode_body)->getLinearVelocity();
VectorCopy(vel.m_floats, edict->v->velocity);
//so it doesn't get rebuilt
VectorCopy(edict->v->origin, edict->ode.ode_origin);
VectorCopy(edict->v->angles, edict->ode.ode_angles);
VectorCopy(edict->v->velocity, edict->ode.ode_velocity);
// World_LinkEdict(world, edict, false);
// if(mSceneNode == nullptr)
// return; // silently return before we set a node
// btQuaternion rot = worldTrans.getRotation();
// mSceneNode ->setOrientation(rot.w(), rot.x(), rot.y(), rot.z());
// btVector3 pos = worldTrans.getOrigin();
// mSceneNode ->setPosition(pos.x(), pos.y(), pos.z());
}
};
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->ode.ode_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->ode.ode_physics)
World_Bullet_RemoveFromEntity(world, ed);
return;
}
// check if we need to create or replace the geom
if (!ed->ode.ode_physics
|| !VectorCompare(ed->ode.ode_mins, entmins)
|| !VectorCompare(ed->ode.ode_maxs, entmaxs)
|| ed->ode.ode_modelindex != modelindex)
{
btCollisionShape *geom;
modified = qtrue;
World_Bullet_RemoveFromEntity(world, ed);
ed->ode.ode_physics = qtrue;
VectorCopy(entmins, ed->ode.ode_mins);
VectorCopy(entmaxs, ed->ode.ode_maxs);
ed->ode.ode_modelindex = modelindex;
VectorAvg(entmins, entmaxs, geomcenter);
ed->ode.ode_movelimit = min(geomsize[0], min(geomsize[1], geomsize[2]));
/* memset(ed->ode.ode_offsetmatrix, 0, sizeof(ed->ode.ode_offsetmatrix));
ed->ode.ode_offsetmatrix[0] = 1;
ed->ode.ode_offsetmatrix[5] = 1;
ed->ode.ode_offsetmatrix[10] = 1;
ed->ode.ode_offsetmatrix[3] = -geomcenter[0];
ed->ode.ode_offsetmatrix[7] = -geomcenter[1];
ed->ode.ode_offsetmatrix[11] = -geomcenter[2];
*/
ed->ode.ode_mass = massval;
switch(geomtype)
{
case GEOMTYPE_TRIMESH:
// foo Matrix4x4_Identity(ed->ode.ode_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->ode.ode_physics)
World_Bullet_RemoveFromEntity(world, ed);
return;
}
if (!rbefuncs->GenerateCollisionMesh(world, model, ed, geomcenter))
{
if (ed->ode.ode_physics)
World_Bullet_RemoveFromEntity(world, ed);
return;
}
// foo Matrix4x4_RM_CreateTranslate(ed->ode.ode_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->ode.ode_numtriangles;
mesh.m_numVertices = ed->ode.ode_numvertices;
mesh.m_triangleIndexBase = (const unsigned char*)ed->ode.ode_element3i;
mesh.m_triangleIndexStride = sizeof(*ed->ode.ode_element3i)*3;
mesh.m_vertexBase = (const unsigned char*)ed->ode.ode_vertex3f;
mesh.m_vertexStride = sizeof(*ed->ode.ode_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->ode.ode_physics)
World_Bullet_RemoveFromEntity(world, ed);
return;
}
// Matrix3x4_InvertTo4x4_Simple(ed->ode.ode_offsetmatrix, ed->ode.ode_offsetimatrix);
// ed->ode.ode_massbuf = BZ_Malloc(sizeof(dMass));
// memcpy(ed->ode.ode_massbuf, &mass, sizeof(dMass));
ed->ode.ode_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->ode.ode_mass != massval)
{
ed->ode.ode_mass = massval;
body = (btRigidBody*)ed->ode.ode_body;
if (body)
ctx->dworld->removeRigidBody(body);
ed->ode.ode_body = NULL;
}
// if(ed->ode.ode_geom)
// dGeomSetData(ed->ode.ode_geom, (void*)ed);
if (movetype == MOVETYPE_PHYSICS && ed->ode.ode_mass)
{
if (ed->ode.ode_body == NULL)
{
// ed->ode.ode_body = (void *)(body = dBodyCreate(world->ode.ode_world));
// dGeomSetBody(ed->ode.ode_geom, body);
// dBodySetData(body, (void*)ed);
// dBodySetMass(body, (dMass *) ed->ode.ode_massbuf);
btVector3 fallInertia(0, 0, 0);
((btCollisionShape*)ed->ode.ode_geom)->calculateLocalInertia(ed->ode.ode_mass, fallInertia);
btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(ed->ode.ode_mass, new QCMotionState(ed,world), (btCollisionShape*)ed->ode.ode_geom, fallInertia);
body = new btRigidBody(fallRigidBodyCI);
body->setUserPointer(ed);
// btTransform trans;
// trans.setFromOpenGLMatrix(ed->ode.ode_offsetmatrix);
// body->setCenterOfMassTransform(trans);
ed->ode.ode_body = (void*)body;
//motion threshhold should be speed/physicsframerate.
//FIXME: recalculate...
body->setCcdMotionThreshold((geomsize[0]+geomsize[1]+geomsize[2])*(4/3));
//radius should be the body's radius
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->ode.ode_body == NULL)
{
btRigidBody::btRigidBodyConstructionInfo rbci(ed->ode.ode_mass, new QCMotionState(ed,world), (btCollisionShape*)ed->ode.ode_geom, btVector3(0, 0, 0));
body = new btRigidBody(rbci);
body->setUserPointer(ed);
// btTransform trans;
// trans.setFromOpenGLMatrix(ed->ode.ode_offsetmatrix);
// body->setCenterOfMassTransform(trans);
ed->ode.ode_body = (void*)body;
if (ed->ode.ode_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->ode.ode_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->ode.ode_origin)
|| !VectorCompare(velocity, ed->ode.ode_velocity)
//|| !VectorCompare(angles, ed->ode.ode_angles)
|| !VectorCompare(avelocity, ed->ode.ode_avelocity)
|| gravity != ed->ode.ode_gravity)
modified = qtrue;
// store the qc values into the physics engine
body = (btRigidBody*)ed->ode.ode_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->ode.ode_origin))
Con_Printf(" origin: %f %f %f -> %f %f %f\n", ed->ode.ode_origin[0], ed->ode.ode_origin[1], ed->ode.ode_origin[2], origin[0], origin[1], origin[2]);
if(!VectorCompare(velocity, ed->ode.ode_velocity))
Con_Printf(" velocity: %f %f %f -> %f %f %f\n", ed->ode.ode_velocity[0], ed->ode.ode_velocity[1], ed->ode.ode_velocity[2], velocity[0], velocity[1], velocity[2]);
if(!VectorCompare(angles, ed->ode.ode_angles))
Con_Printf(" angles: %f %f %f -> %f %f %f\n", ed->ode.ode_angles[0], ed->ode.ode_angles[1], ed->ode.ode_angles[2], angles[0], angles[1], angles[2]);
if(!VectorCompare(avelocity, ed->ode.ode_avelocity))
Con_Printf(" avelocity: %f %f %f -> %f %f %f\n", ed->ode.ode_avelocity[0], ed->ode.ode_avelocity[1], ed->ode.ode_avelocity[2], avelocity[0], avelocity[1], avelocity[2]);
if(gravity != ed->ode.ode_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->ode.ode_origin);
VectorCopy(velocity, ed->ode.ode_velocity);
VectorCopy(angles, ed->ode.ode_angles);
VectorCopy(avelocity, ed->ode.ode_avelocity);
ed->ode.ode_gravity = gravity;
// foo Matrix4x4_RM_FromVectors(entitymatrix, forward, left, up, origin);
// foo Matrix4_Multiply(ed->ode.ode_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->ode.ode_movelimit * world->ode.ode_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->ode.ode_body == b1 || ed2->ode.ode_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->ode.ode_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->ode.ode_world, world->ode.ode_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 i;
wedict_t *ed;
// world->ode.ode_iterations = bound(1, physics_bullet_iterationsperframe.ival, 1000);
// world->ode.ode_step = frametime / world->ode.ode_iterations;
// world->ode.ode_movelimit = physics_bullet_movelimit.value / world->ode.ode_step;
// copy physics properties from entities to physics engine
for (i = 0;i < world->num_edicts;i++)
{
ed = (wedict_t*)EDICT_NUM(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(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));
ctx->dworld->stepSimulation(frametime, max(0, physics_bullet_maxiterationsperframe->value), 1/bound(1, physics_bullet_framerate->value, 500));
// set the tolerance for closeness of objects
// dWorldSetContactSurfaceLayer(world->ode.ode_world, max(0, physics_bullet_contactsurfacelayer.value));
// run collisions for the current world state, creating JointGroup
// dSpaceCollide(world->ode.ode_space, (void *)world, nearCallback);
// run physics (move objects, calculate new velocities)
// if (physics_bullet_worldquickstep.ival)
// {
// dWorldSetQuickStepNumIterations(world->ode.ode_world, bound(1, physics_bullet_worldquickstep_iterations.ival, 200));
// dWorldQuickStep(world->ode.ode_world, world->ode.ode_step);
// }
// else
// dWorldStep(world->ode.ode_world, world->ode.ode_step);
// clear the JointGroup now that we're done with it
// dJointGroupEmpty(world->ode.ode_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(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->ode.ode_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)
{
body->setActivationState(1);
body->applyForce(btVector3(cmd->v1[0], cmd->v1[1], cmd->v1[2]), btVector3(cmd->v2[0], cmd->v2[1], cmd->v2[2]));
}
break;
case RBECMD_TORQUE:
if (cmd->edict->ode.ode_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, vec3_t start, vec3_t end, wedict_t *ed)
{
struct bulletcontext_s *ctx = (struct bulletcontext_s*)world->rbe;
btCollisionShape *shape = (btCollisionShape*)ed->ode.ode_geom;
class myConvexResultCallback : public btCollisionWorld::ConvexResultCallback
{
public:
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
{
return 0;
}
} result;
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);
}
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;
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->ode.ode_world, physics_bullet_world_erp.value);
if(physics_bullet_world_cfm.value >= 0)
dWorldSetCFM(world->ode.ode_world, physics_bullet_world_cfm.value);
if (physics_bullet_world_damping.ival)
{
dWorldSetLinearDamping(world->ode.ode_world, (physics_bullet_world_damping_linear.value >= 0) ? (physics_bullet_world_damping_linear.value * physics_bullet_world_damping.value) : 0);
dWorldSetLinearDampingThreshold(world->ode.ode_world, (physics_bullet_world_damping_linear_threshold.value >= 0) ? (physics_bullet_world_damping_linear_threshold.value * physics_bullet_world_damping.value) : 0);
dWorldSetAngularDamping(world->ode.ode_world, (physics_bullet_world_damping_angular.value >= 0) ? (physics_bullet_world_damping_angular.value * physics_bullet_world_damping.value) : 0);
dWorldSetAngularDampingThreshold(world->ode.ode_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->ode.ode_world, 0);
dWorldSetLinearDampingThreshold(world->ode.ode_world, 0);
dWorldSetAngularDamping(world->ode.ode_world, 0);
dWorldSetAngularDampingThreshold(world->ode.ode_world, 0);
}
if (physics_bullet_autodisable.ival)
{
dWorldSetAutoDisableSteps(world->ode.ode_world, bound(1, physics_bullet_autodisable_steps.ival, 100));
dWorldSetAutoDisableTime(world->ode.ode_world, physics_bullet_autodisable_time.value);
dWorldSetAutoDisableAverageSamplesCount(world->ode.ode_world, bound(1, physics_bullet_autodisable_threshold_samples.ival, 100));
dWorldSetAutoDisableLinearThreshold(world->ode.ode_world, physics_bullet_autodisable_threshold_linear.value);
dWorldSetAutoDisableAngularThreshold(world->ode.ode_world, physics_bullet_autodisable_threshold_angular.value);
dWorldSetAutoDisableFlag (world->ode.ode_world, true);
}
else
dWorldSetAutoDisableFlag (world->ode.ode_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();
}