27a59a0cbc
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
1759 lines
58 KiB
C++
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();
|
|
}
|
|
|
|
|