/* ----------------------------------------------------------------------------- GSFramework Copyright 2001-2013 Emmanuel Julien. All Rights Reserved. ----------------------------------------------------------------------------- */ #include "physic_bullet/bullet_world.h" #include "BulletCollision/CollisionDispatch/btGhostObject.h" #include "physic_bullet/bullet_item.h" #include "physic_bullet/bullet_constraint.h" #include "physic_bullet/bullet_debug.h" #include "core/geometry.h" #include "metafile/nml_object.h" #include "log/log.h" using namespace GS; using namespace GS::S3D; //------------------------------------------------------------------------------ PhysicItem *BulletWorld::NewItem() { return new BulletPhysicItem(world); } PhysicConstraint *BulletWorld::NewConstraint() { return new BulletConstraint(world); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ BulletConvex *BulletWorld::LoadConvex(const char *_name) { String name(_name); // Check cache. ListForeachPtr(BulletConvex *, convex, convex_cache) if (convex->name == name) return convex; // Load geometry. AutoPtr g(new Core::Geometry); if (!NML::LoadFromFile(*g, name)) return NULL; // Setup convex. BulletConvex *bullet_convex = new BulletConvex; if (!bullet_convex) __ERR__(__LOG_E__ << "Failed to allocate bullet convex.\n", NULL) Array bt_vtx(g->vtx.GetCount() * 3); btScalar *p_bt_vtx = bt_vtx.c_ptr(); Vector4 gcenter(0, 0, 0); for (uint n = 0; n < g->vtx.GetCount(); ++n) { gcenter += g->vtx[n]; *p_bt_vtx++ = g->vtx[n].x; *p_bt_vtx++ = g->vtx[n].y; *p_bt_vtx++ = g->vtx[n].z; } bullet_convex->name = name; bullet_convex->center = (gcenter / (float)g->vtx.GetCount()); bullet_convex->convex = new btConvexHullShape(bt_vtx.c_ptr(), g->vtx.GetCount(), 3 * sizeof(btScalar)); convex_cache.Add(bullet_convex); return bullet_convex; } BulletMesh *BulletWorld::LoadMesh(const char *_name, const char *_suffix) { String name(_name), suffix(_suffix); // Check cache. ListForeachPtr(BulletMesh *, mesh, mesh_cache) if ((mesh->name == name) && (mesh->suffix == suffix)) { __LOG_V__ << "Reusing cached Bullet btMesh for " << _name << " (suffix: " << _suffix << ").\n"; return mesh; } // Load bullet mesh. AutoPtr g(new Core::Geometry); if (g.IsNull()) return NULL; g->name = name; if (!NML::LoadFromFile(*g, name)) return NULL; if (!g->vtx.GetCount() || !g->pol.GetCount()) __ERR__(__LOG_E__ << "No geometry data in '" << g->name << "' to build collision shape.\n", NULL) BulletMesh *bullet_mesh = new BulletMesh; if (!bullet_mesh) __ERR__(__LOG_E__ << "Failed to allocate bullet mesh.\n", NULL) int triangle_count = g->GetTriangleCount(); bullet_mesh->bt_vtx.Allocate(g->vtx.GetCount() * 3); btScalar *p_bt_vtx = bullet_mesh->bt_vtx; bullet_mesh->bt_idx.Allocate(triangle_count * 3); int *p_bt_idx = bullet_mesh->bt_idx; Vector4 gcenter(0, 0, 0); for (uint n = 0; n < g->vtx.GetCount(); ++n) { gcenter += g->vtx[n]; *p_bt_vtx++ = g->vtx[n].x; *p_bt_vtx++ = g->vtx[n].y; *p_bt_vtx++ = g->vtx[n].z; } bullet_mesh->center = gcenter / (float)g->vtx.GetCount(); // Triangulate geometry on the fly, transfer material indices. bullet_mesh->bt_mat.Allocate(g->material_table.GetCount()); for (uint n = 0; n < g->material_table.GetCount(); ++n) bullet_mesh->bt_mat[n] = g->material_table[n].name; bullet_mesh->bt_id_mat.Allocate(triangle_count); ushort *p_bt_id_mat = bullet_mesh->bt_id_mat; for (uint n = 0; n < g->pol.GetCount(); ++n) for (int p = 1; p < (g->pol[n].vtx_count - 1); ++p) { *p_bt_idx++ = g->pol[n].binding[0]; *p_bt_idx++ = g->pol[n].binding[p]; *p_bt_idx++ = g->pol[n].binding[p + 1]; *p_bt_id_mat++ = g->pol[n].material; } bullet_mesh->name = name; bullet_mesh->suffix = suffix; bullet_mesh->mesh_interface = new btTriangleIndexVertexArray(triangle_count, bullet_mesh->bt_idx, 3 * sizeof(int), g->vtx.GetCount(), bullet_mesh->bt_vtx, 3 * sizeof(btScalar)); bullet_mesh->mesh = new btBvhTriangleMeshShape(bullet_mesh->mesh_interface, true); mesh_cache.Add(bullet_mesh); return bullet_mesh; } void BulletWorld::ClearConvexMeshCache() { convex_cache.Clear(); mesh_cache.Clear(); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool BulletWorld::HasDebugger() const { return debug_draw.IsValid(); } void BulletWorld::CreateDebugger(Renderer *renderer) { debug_draw = renderer ? new BulletDebugDraw(*renderer) : NULL; if (world) world->setDebugDrawer(debug_draw); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ static void bullet_pretick_callback(btDynamicsWorld *world, btScalar timeStep) { BulletWorld *physic_world = (BulletWorld *)world->getWorldUserInfo(); if (physic_world->GetWorldInterface()) physic_world->GetWorldInterface()->PhysicStep(timeStep, true); } static void bullet_posttick_callback(btDynamicsWorld *world, btScalar timeStep) { BulletWorld *physic_world = (BulletWorld *)world->getWorldUserInfo(); if (physic_world->GetWorldInterface()) physic_world->GetWorldInterface()->PhysicStep(timeStep, false); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void BulletWorld::DrawDebug(Renderer &, Camera *c, RasterFont *f, bool xray_first_pass) { if (BulletDebugDraw *dd = (BulletDebugDraw *)world->getDebugDrawer()) { dd->camera = c; dd->raster_font = f; dd->SetDebugMode(btIDebugDraw::DBG_DrawWireframe | btIDebugDraw::DBG_DrawConstraints | btIDebugDraw::DBG_DrawConstraintLimits | btIDebugDraw::DBG_DrawContactPoints); // dd->SetDebugMode(btIDebugDraw::DBG_DrawAabb | btIDebugDraw::DBG_FastWireframe); dd->SetXRayFirstPass(xray_first_pass); world->debugDrawWorld(); dd->Flush(); } } uint BulletWorld::GetCollisionPairCount() { return world->getDispatcher()->getNumManifolds(); } bool BulletWorld::GetCollisionPair(uint n, CollisionPair &pair) { btPersistentManifold *manifold = world->getDispatcher()->getInternalManifoldPointer()[n]; if (!manifold || !manifold->getNumContacts()) // Manifolds are valid as long as the bodies overlap in the broadphase. return false; pair.a = (PhysicItem *)((btRigidBody *)manifold->getBody0())->getUserPointer(); pair.b = (PhysicItem *)((btRigidBody *)manifold->getBody1())->getUserPointer(); pair.contact_count = 0; for (int i = 0; (i < manifold->getNumContacts()) && (i < 4); ++i) { btVector3 p = manifold->getContactPoint(i).getPositionWorldOnB(); pair.contact[i].Set(p.x(), p.y(), p.z()); btVector3 n = manifold->getContactPoint(i).m_normalWorldOnB; pair.normal[i].Set(n.x(), n.y(), n.z()); pair.contact_count++; } return true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool BulletWorld::Create() { collision_config = new btDefaultCollisionConfiguration(); #if __ENABLE_BULLET_MULTITHREAD__ thread_support_collision = new Win32ThreadSupport(Win32ThreadSupport::Win32ThreadConstructionInfo("Bullet Collision", processCollisionTask, createCollisionLocalStoreMemory, __BULLET_THREAD_COUNT__)); dispatcher = new SpuGatheringCollisionDispatcher(thread_support_collision, __BULLET_THREAD_COUNT__, collision_config); #else dispatcher = new btCollisionDispatcher(collision_config); #endif broadphase = new btDbvtBroadphase(); broadphase->getOverlappingPairCache()->setInternalGhostPairCallback(pair_callback = new btGhostPairCallback); solver = new btSequentialImpulseConstraintSolver; world = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collision_config); world->setInternalTickCallback(bullet_pretick_callback, (void *)this, true); world->setInternalTickCallback(bullet_posttick_callback, (void *)this, false); // world->getSolverInfo().m_numIterations = 10; // world->getDispatchInfo().m_enableSPU = true; world->getSolverInfo().m_solverMode = SOLVER_SIMD + SOLVER_USE_WARMSTARTING;// + SOLVER_RANDMIZE_ORDER; // world->getSolverInfo().m_splitImpulse = 1; // world->getSolverInfo().m_splitImpulsePenetrationThreshold = 0.2; world->setDebugDrawer(debug_draw); return true; } void BulletWorld::Delete() { ClearConvexMeshCache(); collision_config = NULL; dispatcher = NULL; broadphase = NULL; solver = NULL; world = NULL; #if __ENABLE_BULLET_MULTITHREAD__ thread_support_collision = NULL; #endif } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void BulletWorld::Step(const GS::Time &dt) { ScopedBenchmark bench(bench_step); #if 1 substep_dt -= dt.toSec(); int limit = 4; while (substep_dt < 0) { world->stepSimulation(GetTimestep(), 0, GetTimestep()); substep_dt += GetTimestep(); if (--limit <= 0) { substep_dt = 0; break; } } #else world->stepSimulation(dt, 12, GetTimestep()); #endif } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool BulletWorld::Raytrace(const Vector4 &s, const Vector4 &d, PhysicTrace &hit, int collision_mask, int shape_mask, float max_distance) { struct ClosestRayResultWithTriangleIndexCallback : public btCollisionWorld::ClosestRayResultCallback { ClosestRayResultWithTriangleIndexCallback(const btVector3 &rayFromWorld, const btVector3 &rayToWorld) : ClosestRayResultCallback(rayFromWorld, rayToWorld) {} int m_TriangleIndex; int m_shapePart; virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult &rayResult, bool normalInWorldSpace) { if (rayResult.m_localShapeInfo) { m_TriangleIndex = rayResult.m_localShapeInfo->m_triangleIndex; m_shapePart = rayResult.m_localShapeInfo->m_shapePart; } else { m_TriangleIndex = -1; m_shapePart = -1; } return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } }; Vector4 e = s + d * (max_distance > 0 ? max_distance : 5000.f); btVector3 from(s.x, s.y, s.z), to(e.x, e.y, e.z); ClosestRayResultWithTriangleIndexCallback trace(from, to); trace.m_collisionFilterGroup = btBroadphaseProxy::AllFilter; trace.m_collisionFilterMask = (short)collision_mask; world->rayTest(from, to, trace); if (!trace.hasHit()) return false; hit.p.Set(trace.m_hitPointWorld.x(), trace.m_hitPointWorld.y(), trace.m_hitPointWorld.z()); hit.n.Set(trace.m_hitNormalWorld.x(), trace.m_hitNormalWorld.y(), trace.m_hitNormalWorld.z()); hit.i = (PhysicItem *)trace.m_collisionObject->getUserPointer(); if (BulletPhysicItem *bi = (BulletPhysicItem *)hit.i) if ((trace.m_shapePart >= 0) && ((uint)trace.m_shapePart < bi->shapes.GetCount())) if (BulletMesh *mesh = bi->shapes[trace.m_shapePart].mesh.c_ptr()) if (uint(trace.m_TriangleIndex) < mesh->bt_id_mat.GetCount()) hit.m = mesh->bt_mat[mesh->bt_id_mat[trace.m_TriangleIndex]]; return true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ static void *bulletAlloc(size_t s) { return MemAllocPhysics::Alloc(s, Alloc::Physics); } static void bulletFree(void *p) { MemAllocPhysics::Delete(p, Alloc::Physics); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ BulletWorld::BulletWorld() { btAlignedAllocSetCustom(bulletAlloc, bulletFree); substep_dt = 0; } BulletWorld::~BulletWorld() { Delete(); } //------------------------------------------------------------------------------