1
0
Fork 0
forked from fte/fteqw
fteqw/engine/common/com_phys_bullet.cpp
Spoike 27a59a0cbc LOTS OF CHANGES. was hoping to get revision 5000 perfect, but really that's never going to happen. this has gone on for too long now.
vulkan, wasapi, quake injector features added.
irc, avplug, cef plugins/drivers reworked/updated/added
openal reverb, doppler effects added.
'dir' console command now attempts to view clicked files.
lots of warning fixes, should now only be deprecation warnings for most targets (depending on compiler version anyway...).
SendEntity finally reworked to use flags properly.
effectinfo improved, other smc-targetted fixes.
mapcluster stuff now has support for linux.
.basebone+.baseframe now exist in ssqc.
qcc: -Fqccx supports qccx syntax, including qccx hacks. don't expect these to work in fteqw nor dp though.
qcc: rewrote function call handling to use refs rather than defs. this makes struct passing more efficient and makes the __out keyword usable with fields etc.
qccgui: can cope a little better with non-unicode files. can now represent most quake chars.
qcc: suppressed warnings from *extensions.qc

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5000 fc73d0e0-1445-4013-8a0c-d673dee63da5
2016-07-12 00:40:13 +00:00

1759 lines
58 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.
*/
//can either be built as a separate dll or statically linked in the engine (will still be treated as if dynamically)
//to enable as static, add the file to the makefile/project and edit engine/common/plugin.c to list Plug_$foo_Init
#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 "../../plugins/plugin.h"
#include "../../plugins/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 modplugfuncs_t *modfuncs;
//============================================================================
// 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);
void World_Bullet_Init(void)
{
pCvar_Register("physics_bullet_enable", "1", 0, "Bullet");
pCvar_Register("physics_bullet_maxiterationsperframe", "10", 0, "Bullet");
pCvar_Register("physics_bullet_framerate", "60", 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
modfuncs->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 void World_Bullet_Frame_JointFromEntity(world_t *world, wedict_t *ed)
{
#if 0
dJointID j = 0;
dBodyID b1 = 0;
dBodyID b2 = 0;
int movetype = 0;
int jointtype = 0;
int enemy = 0, aiment = 0;
wedict_t *o;
vec3_t origin, velocity, angles, forward, left, up, movedir;
vec_t CFM, ERP, FMax, Stop, Vel;
VectorClear(origin);
VectorClear(velocity);
VectorClear(angles);
VectorClear(movedir);
movetype = (int)ed->v->movetype;
jointtype = (int)ed->xv->jointtype;
enemy = ed->v->enemy;
aiment = ed->v->aiment;
VectorCopy(ed->v->origin, origin);
VectorCopy(ed->v->velocity, velocity);
VectorCopy(ed->v->angles, angles);
VectorCopy(ed->v->movedir, movedir);
if(movetype == MOVETYPE_PHYSICS)
jointtype = 0; // can't have both
o = (wedict_t*)PROG_TO_EDICT(world->progs, enemy);
if(o->isfree || o->ode.ode_body == 0)
enemy = 0;
o = (wedict_t*)PROG_TO_EDICT(world->progs, aiment);
if(o->isfree || o->ode.ode_body == 0)
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 / (world->ode.ode_step * K + R); // always > 0
ERP = world->ode.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] : dInfinity;
}
else // movedir[0] > 0, movedir[1] == 0 or movedir[0] < 0, movedir[1] >= 0
{
CFM = 0;
ERP = 0;
Vel = 0;
FMax = 0;
Stop = dInfinity;
}
if(jointtype == ed->ode.ode_joint_type && VectorCompare(origin, ed->ode.ode_joint_origin) && VectorCompare(velocity, ed->ode.ode_joint_velocity) && VectorCompare(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
AngleVectorsFLU(angles, forward, left, up);
switch(jointtype)
{
case JOINTTYPE_POINT:
j = dJointCreateBall(world->ode.ode_world, 0);
break;
case JOINTTYPE_HINGE:
j = dJointCreateHinge(world->ode.ode_world, 0);
break;
case JOINTTYPE_SLIDER:
j = dJointCreateSlider(world->ode.ode_world, 0);
break;
case JOINTTYPE_UNIVERSAL:
j = dJointCreateUniversal(world->ode.ode_world, 0);
break;
case JOINTTYPE_HINGE2:
j = dJointCreateHinge2(world->ode.ode_world, 0);
break;
case JOINTTYPE_FIXED:
j = dJointCreateFixed(world->ode.ode_world, 0);
break;
case 0:
default:
// no joint
j = 0;
break;
}
if(ed->ode.ode_joint)
{
//Con_Printf("deleted old joint %i\n", (int) (ed - prog->edicts));
dJointAttach(ed->ode.ode_joint, 0, 0);
dJointDestroy(ed->ode.ode_joint);
}
ed->ode.ode_joint = (void *) j;
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(angles, ed->ode.ode_joint_angles);
VectorCopy(movedir, ed->ode.ode_joint_movedir);
if(j)
{
//Con_Printf("made new joint %i\n", (int) (ed - prog->edicts));
dJointSetData(j, (void *) ed);
if(enemy)
b1 = (dBodyID)((WEDICT_NUM(world->progs, enemy))->ode.ode_body);
if(aiment)
b2 = (dBodyID)((WEDICT_NUM(world->progs, aiment))->ode.ode_body);
dJointAttach(j, b1, b2);
switch(jointtype)
{
case JOINTTYPE_POINT:
dJointSetBallAnchor(j, origin[0], origin[1], origin[2]);
break;
case JOINTTYPE_HINGE:
dJointSetHingeAnchor(j, origin[0], origin[1], origin[2]);
dJointSetHingeAxis(j, forward[0], forward[1], forward[2]);
dJointSetHingeParam(j, dParamFMax, FMax);
dJointSetHingeParam(j, dParamHiStop, Stop);
dJointSetHingeParam(j, dParamLoStop, -Stop);
dJointSetHingeParam(j, dParamStopCFM, CFM);
dJointSetHingeParam(j, dParamStopERP, ERP);
dJointSetHingeParam(j, dParamVel, Vel);
break;
case JOINTTYPE_SLIDER:
dJointSetSliderAxis(j, forward[0], forward[1], forward[2]);
dJointSetSliderParam(j, dParamFMax, FMax);
dJointSetSliderParam(j, dParamHiStop, Stop);
dJointSetSliderParam(j, dParamLoStop, -Stop);
dJointSetSliderParam(j, dParamStopCFM, CFM);
dJointSetSliderParam(j, dParamStopERP, ERP);
dJointSetSliderParam(j, dParamVel, Vel);
break;
case JOINTTYPE_UNIVERSAL:
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:
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:
break;
case 0:
default:
break;
}
#undef SETPARAMS
}
#endif
}
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;
}
AngleVectorsFLU(vec3_origin, mat+0, mat+4, mat+8);
*/
}
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;
modfuncs->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);
modfuncs->VectorAngles(fwd, up, edict->v->angles);
if (edict->v->modelindex)
{
model_t *model = world->Get_CModel(world, edict->v->modelindex);
if (!model || model->type == mod_alias)
;
else
edict->v->angles[PITCH] *= -1;
}
//so it doesn't get rebuilt
VectorCopy(edict->v->origin, edict->ode.ode_origin);
VectorCopy(edict->v->angles, edict->ode.ode_angles);
// 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 (!modfuncs->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;
//continuous collision detection prefers using a sphere within the object. tbh I have no idea what values to specify.
body->setCcdMotionThreshold((geomsize[0]+geomsize[1]+geomsize[2])*(4/3));
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;
}
}
modfuncs->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)
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)
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, pCvar_GetFloat("physics_bullet_maxiterationsperframe")), 1/bound(1, pCvar_GetFloat("physics_bullet_framerate"), 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)
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 (!pCvar_GetFloat("physics_bullet_enable"))
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.Frame = 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 (modfuncs)
modfuncs->UnregisterPhysicsEngine("Bullet");
return 0;
}
static bool World_Bullet_DoInit(void)
{
if (!modfuncs || !modfuncs->RegisterPhysicsEngine)
Con_Printf("Bullet plugin failed: Engine doesn't support physics engine plugins.\n");
else if (!modfuncs->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;
}
extern "C" qintptr_t Plug_Init(qintptr_t *args)
{
CHECKBUILTIN(Mod_GetPluginModelFuncs);
if (BUILTINISVALID(Mod_GetPluginModelFuncs))
{
modfuncs = pMod_GetPluginModelFuncs(sizeof(modplugfuncs_t));
if (modfuncs && modfuncs->version < MODPLUGFUNCS_VERSION)
modfuncs = NULL;
}
Plug_Export("Shutdown", World_Bullet_Shutdown);
return World_Bullet_DoInit();
}