diff --git a/Makefile b/Makefile index 17f0a9dab..77c7e41cf 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ ogre_cpp=ogre framelistener interface overlay bsaarchive avcodec_cpp=avcodec # Bullet cpp files -bullet_cpp=bullet player +bullet_cpp=bullet player scale #### No modifications should be required below this line. #### diff --git a/README.txt b/README.txt index 5f8d1f245..9975204c9 100644 --- a/README.txt +++ b/README.txt @@ -120,6 +120,7 @@ Changelog: 0.5 (WIP) - working on collision detection with Bullet +- working on walk & fall character physics - working on fixing sound issues for windows (running out of sound resources, music playback doesn't good) - new key bindings: diff --git a/bullet/bindings.d b/bullet/bindings.d index 9df829227..65c014430 100644 --- a/bullet/bindings.d +++ b/bullet/bindings.d @@ -48,6 +48,13 @@ void bullet_setPlayerDir(float x, float y, float z); // been applied. void bullet_getPlayerPos(float *x, float *y, float *z); +// Create a box shape. +/* +void bullet_createBoxShape(float minX, float minY, float minZ, + float maxX, float maxY, float maxZ, + float *trans,float *matrix); +*/ + // Create a triangle shape. This is cumulative, all meshes created // with this function are added to the same shape. Since the various // parts of a mesh can be differently transformed and we are putting diff --git a/bullet/cpp_bullet.cpp b/bullet/cpp_bullet.cpp index f33a38f5a..deda89f56 100644 --- a/bullet/cpp_bullet.cpp +++ b/bullet/cpp_bullet.cpp @@ -29,6 +29,12 @@ using namespace std; class CustomOverlappingPairCallback; +enum + { + MASK_PLAYER = 1, + MASK_STATIC = 2 + }; + // System variables btDefaultCollisionConfiguration* g_collisionConfiguration; btCollisionDispatcher *g_dispatcher; @@ -56,6 +62,11 @@ btVector3 g_walkDirection; // added into this, until bullet_getFinalShape() is called. btTriangleIndexVertexArray *g_currentMesh; +// Current compound shape being built. g_compoundShape and +// g_currentMesh are returned together (the mesh inserted into the +// compound) if both are present. +btCompoundShape *g_compoundShape; + // These variables and the class below are used in player collision // detection. The callback is injected into the broadphase and keeps a // continuously updated list of what objects are colliding with the @@ -77,6 +88,9 @@ int g_physMode; // Include the player physics #include "cpp_player.cpp" +// Include the uniform shape scaler +//#include "cpp_scale.cpp" + class CustomOverlappingPairCallback : public btOverlappingPairCallback { public: @@ -105,13 +119,6 @@ public: { if (proxy0->m_clientObject==g_playerObject) g_pairCache->removeOverlappingPairsContainingProxy(proxy0,dispatcher); } - - /* KILLME - btBroadphasePairArray& getOverlappingPairArray() - { return g_pairCache->getOverlappingPairArray(); } - btOverlappingPairCache* getOverlappingPairCache() - { return g_pairCache; } - */ }; extern "C" int32_t bullet_init() @@ -132,8 +139,8 @@ extern "C" int32_t bullet_init() // to create a custom broadphase designed for our purpose. (We // should probably use different ones for interior and exterior // cells in any case.) - btVector3 worldMin(-40000,-40000,-40000); - btVector3 worldMax(40000,40000,40000); + btVector3 worldMin(-20000,-20000,-20000); + btVector3 worldMax(20000,20000,20000); g_broadphase = new btAxisSweep3(worldMin,worldMax); g_dynamicsWorld = @@ -149,20 +156,25 @@ extern "C" int32_t bullet_init() // Create the player collision shape. float width = 50; + /* - // One possible shape is the convex hull around two spheres - float height = 100; + float height = 50; btVector3 spherePositions[2]; btScalar sphereRadii[2]; sphereRadii[0] = width; sphereRadii[1] = width; - spherePositions[0] = btVector3 (0.0, height/2.0, 0.0); - spherePositions[1] = btVector3 (0.0, -height/2.0, 0.0); + spherePositions[0] = btVector3 (0,0,0); + spherePositions[1] = btVector3 (0,0,-height); + + // One possible shape is the convex hull around two spheres g_playerShape = new btMultiSphereShape(btVector3(width/2.0, height/2.0, width/2.0), &spherePositions[0], &sphereRadii[0], 2); - //*/ - //g_playerShape = new btCylinderShape(btVector3(50, 50, 50)); + */ + + // Other posibilities - most are too slow, except the sphere + //g_playerShape = new btCylinderShapeZ(btVector3(width, width, height)); g_playerShape = new btSphereShape(width); + //g_playerShape = new btCapsuleShapeZ(width, height); // Create the collision object g_playerObject = new btCollisionObject (); @@ -183,13 +195,13 @@ extern "C" int32_t bullet_init() g_dynamicsWorld->setInternalTickCallback(playerStepCallback); // Add the character collision object to the world. - g_dynamicsWorld->addCollisionObject(g_playerObject - ,btBroadphaseProxy::DebrisFilter - //,btBroadphaseProxy::StaticFilter - ); + g_dynamicsWorld->addCollisionObject(g_playerObject, + MASK_PLAYER, + MASK_STATIC); - // Make sure this is zero at startup + // Make sure these is zero at startup g_currentMesh = NULL; + g_compoundShape = NULL; // Start out walking g_physMode = PHYS_WALK; @@ -242,6 +254,41 @@ extern "C" void bullet_getPlayerPos(float *x, float *y, float *z) *z = g_playerPosition.getZ(); } +// Create a box shape and add it to the cumulative shape of the +// current object +/* +extern "C" void bullet_createBoxShape(float minX, float minY, float minZ, + float maxX, float maxY, float maxZ, + float *trans,float *matrix) +{ + if(g_compoundShape == NULL) + g_compoundShape = new btCompoundShape; + + // Build a box from the given data. + int x = (maxX-minX)/2; + int y = (maxY-minY)/2; + int z = (maxZ-minZ)/2; + btBoxShape *box = new btBoxShape(btVector3(x,y,z)); + + // Next, create the transformations + btMatrix3x3 mat(matrix[0], matrix[1], matrix[2], + matrix[3], matrix[4], matrix[5], + matrix[6], matrix[7], matrix[8]); + + // In addition to the mesh's world translation, we have to add the + // local translation of the box origin to fit with the given min/max + // values. + x += minX + trans[0]; + y += minY + trans[1]; + z += minZ + trans[2]; + btVector3 trns(x,y,z); + + // Finally, add the shape to the compound + btTransform tr(mat, trns); + g_compoundShape->addChildShape(tr, box); +} +*/ + void* copyBuffer(void *buf, int elemSize, int len) { int size = elemSize * len; @@ -251,41 +298,6 @@ void* copyBuffer(void *buf, int elemSize, int len) return res; } -// Define a cube with coordinates 0,0,0 - 1,1,1. -const float cube_verts[] = - { - 0,0,0, 1,0,0, 0,1,0, - 1,1,0, 0,0,1, 1,0,1, - 0,1,1, 1,1,1 - }; - -// Triangles of the cube. The orientation of each triange doesn't -// matter. -const short cube_tris[] = - { - // bottom side - 0, 1, 2, - 1, 2, 3, - // top side - 4, 5, 6, - 5, 6, 7, - // front side - 0, 4, 5, - 0, 1, 5, - // back side - 2, 3, 7, - 2, 6, 7, - // left side - 0, 2, 4, - 2, 4, 6, - // right side - 1, 3, 5, - 3, 5, 7 - }; - -const int cube_num_verts = 8; -const int cube_num_tris = 12; - // Internal version that does not copy buffers void createTriShape(int32_t numFaces, void *triArray, int32_t numVerts, void *vertArray, @@ -329,6 +341,41 @@ void createTriShape(int32_t numFaces, void *triArray, g_currentMesh->addIndexedMesh(im, PHY_SHORT); } +// Define a cube with coordinates 0,0,0 - 1,1,1. +const float cube_verts[] = + { + 0,0,0, 1,0,0, 0,1,0, + 1,1,0, 0,0,1, 1,0,1, + 0,1,1, 1,1,1 + }; + +// Triangles of the cube. The orientation of each triange doesn't +// matter. +const short cube_tris[] = + { + // bottom side + 0, 1, 2, + 1, 2, 3, + // top side + 4, 5, 6, + 5, 6, 7, + // front side + 0, 4, 5, + 0, 1, 5, + // back side + 2, 3, 7, + 2, 6, 7, + // left side + 0, 2, 4, + 2, 4, 6, + // right side + 1, 3, 5, + 3, 5, 7 + }; + +const int cube_num_verts = 8; +const int cube_num_tris = 12; + // Create a (trimesh) box with the given dimensions. Used for bounding // boxes. TODO: I guess we have to use the NIF-specified bounding box // for this, not our automatically calculated one. @@ -379,14 +426,33 @@ extern "C" void bullet_createTriShape(int32_t numFaces, // so the next call to createTriShape will start a new shape. extern "C" btCollisionShape *bullet_getFinalShape() { - // Return null if no meshes have been inserted - if(g_currentMesh == NULL) return NULL; + btCollisionShape *shape; - // Create the shape from the completed mesh - btBvhTriangleMeshShape *shape = - new btBvhTriangleMeshShape(g_currentMesh, false); + // Create a shape from all the inserted completed meshes + shape = NULL; + if(g_currentMesh != NULL) + shape = new btBvhTriangleMeshShape(g_currentMesh, false); + // Is there a compound shape as well? (usually contains bounding + // boxes) + if(g_compoundShape != NULL) + { + // If both shape types are present, insert the mesh shape into + // the compound. + if(shape != NULL) + { + btTransform tr; + tr.setIdentity(); + g_compoundShape->addChildShape(tr, shape); + } + + // The compound is the final shape + shape = g_compoundShape; + } + + // Clear these for the next NIF g_currentMesh = NULL; + g_compoundShape = NULL; return shape; } @@ -396,14 +462,12 @@ extern "C" void bullet_insertStatic(btCollisionShape *shape, float *quat, float scale) { - // TODO: Good test case for scaled meshes: Aharunartus, some of the - // stairs inside the cavern currently don't collide + // Are we scaled? if(scale != 1.0) - { - cout << "WARNING: Cannot scale collision meshes yet (wanted " - << scale << ")\n"; - return; - } + return; + // Wrap the shape in a class that scales it. TODO: Try this on + // ALL shapes, just to test the slowdown. + //shape = new ScaleShape(shape, scale); btTransform trafo; trafo.setIdentity(); @@ -416,7 +480,7 @@ extern "C" void bullet_insertStatic(btCollisionShape *shape, btCollisionObject *obj = new btCollisionObject(); obj->setCollisionShape(shape); obj->setWorldTransform(trafo); - g_dynamicsWorld->addCollisionObject(obj); + g_dynamicsWorld->addCollisionObject(obj, MASK_STATIC, MASK_PLAYER); } // Move the physics simulation 'delta' seconds forward in time diff --git a/bullet/cpp_player.cpp b/bullet/cpp_player.cpp index df94d1b49..bc45edf6d 100644 --- a/bullet/cpp_player.cpp +++ b/bullet/cpp_player.cpp @@ -34,7 +34,7 @@ bool g_touchingContact; btVector3 g_touchingNormal; btScalar g_currentStepOffset; -float g_stepHeight = 20; +float g_stepHeight = 5; // Returns the reflection direction of a ray going 'direction' hitting // a surface with normal 'normal' @@ -293,6 +293,18 @@ void playerStepCallback(btDynamicsWorld* dynamicsWorld, btScalar timeStep) // simulation. btVector3 walkStep = g_walkDirection * timeStep; + float len = walkStep.length(); + + // In walk mode, it shouldn't matter whether or not we look up or + // down. Rotate the vector back to the horizontal plane. + if(g_physMode == PHYS_WALK) + { + walkStep.setZ(0); + float len2 = walkStep.length(); + if(len2 > 0) + walkStep *= len/len2; + } + // Get the player position g_playerPosition = g_playerObject->getWorldTransform().getOrigin(); diff --git a/input/events.d b/input/events.d index 4d6451ee4..19d1ac088 100644 --- a/input/events.d +++ b/input/events.d @@ -312,6 +312,9 @@ extern(C) int d_frameStarted(float time) if(isPressed(Keys.MoveRight)) moveX += speed; if(isPressed(Keys.MoveForward)) moveZ -= speed; if(isPressed(Keys.MoveBackward)) moveZ += speed; + + // TODO: These should be enabled for floating modes (like swimming + // and levitation) only. if(isPressed(Keys.MoveUp)) moveY += speed; if(isPressed(Keys.MoveDown)) moveY -= speed; diff --git a/ogre/cpp_framelistener.cpp b/ogre/cpp_framelistener.cpp index da73335b0..c8111e920 100644 --- a/ogre/cpp_framelistener.cpp +++ b/ogre/cpp_framelistener.cpp @@ -120,9 +120,15 @@ extern "C" void ogre_screenshot(char* filename) extern "C" void ogre_rotateCamera(float x, float y) { mCamera->yaw(Degree(-x)); + + Quaternion nopitch = mCamera->getOrientation(); + mCamera->pitch(Degree(-y)); - //g_light->setDirection(mCamera->getDirection()); + // Is the camera close to being upside down? + if(mCamera->getUp()[1] <= 0.1) + // If so, undo the last pitch + mCamera->setOrientation(nopitch); } // Get current camera position @@ -155,7 +161,7 @@ extern "C" void ogre_moveCamera(float x, float y, float z) // Transforms Morrowind coordinates to OGRE coordinates. The camera // is not affected by the rotation of the root node, so we must // transform this manually. - mCamera->setPosition(Vector3(x,z,-y)); + mCamera->setPosition(Vector3(x,z+90,-y)); //g_light->setPosition(mCamera->getPosition()); } @@ -181,5 +187,8 @@ extern "C" void ogre_setCameraRotation(float r1, float r2, float r3) // Move camera relative to its own axis set. extern "C" void ogre_moveCameraRel(float x, float y, float z) { - mCamera->moveRelative(Vector3(x,y,z)); + mCamera->moveRelative(Vector3(x,0,z)); + + // up/down movement is always done relative the world axis + mCamera->move(Vector3(0,y,0)); } diff --git a/ogre/cpp_interface.cpp b/ogre/cpp_interface.cpp index 7a46a70b8..0d39751b2 100644 --- a/ogre/cpp_interface.cpp +++ b/ogre/cpp_interface.cpp @@ -182,6 +182,8 @@ extern "C" void ogre_makeScene() // Alter the camera aspect ratio to match the viewport mCamera->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight())); + mCamera->setFOVy(Degree(55)); + // Set default mipmap level (NB some APIs ignore this) TextureManager::getSingleton().setDefaultNumMipmaps(5); diff --git a/ogre/meshloader.d b/ogre/meshloader.d index 09907ce3b..8799de02e 100644 --- a/ogre/meshloader.d +++ b/ogre/meshloader.d @@ -82,7 +82,7 @@ struct MeshLoader base = ogre_getDetachedNode(); // Recursively insert nodes (don't rotate the first node) - insertNode(n, base, true); + insertNode(n, base, 0, true); // Get the final shape, if any shape = bullet_getFinalShape(); @@ -92,10 +92,12 @@ struct MeshLoader private: - void insertNode(Node data, NodePtr parent, bool noRot = false) + void insertNode(Node data, NodePtr parent, + int flags, + bool noRot = false) { - // Skip hidden nodes for now. - if(data.flags & 0x01) return; + // Add the flags to the previous node's flags + flags = data.flags | flags; // Create a scene node, move and rotate it into place. The name // must be unique, however we might have to recognize some special @@ -115,23 +117,29 @@ struct MeshLoader NiNode n = cast(NiNode)data; if(n !is null) // Handle the NiNode, and any children it might have - handleNiNode(n, node); + handleNiNode(n, node, flags); } { NiTriShape n = cast(NiTriShape)data; if(n !is null) // Trishape, with a mesh - handleNiTriShape(n, node); + handleNiTriShape(n, node, flags); } } - void handleNiNode(NiNode data, NodePtr node) + void handleNiNode(NiNode data, NodePtr node, int flags) { // Ignore sound activators and similar objects. NiStringExtraData d = cast(NiStringExtraData) data.extra; - if(d !is null && d.string == "MRK") - return; + if(d !is null) + { + if(d.string == "MRK") + return; + + if(d.string == "NCO") + flags |= 0x800; // Temporary internal marker + } // Handle any effects here @@ -144,16 +152,31 @@ struct MeshLoader // Loop through children foreach(Node n; data.children) - insertNode(n, node); + insertNode(n, node, flags); } - void handleNiTriShape(NiTriShape shape, NodePtr node) + void handleNiTriShape(NiTriShape shape, NodePtr node, int flags) { char[] texture; char[] material; char[] newName = UniqueName(baseName); NiMaterialProperty mp; + bool hidden = (flags & 0x01) != 0; // Not displayed + bool collide = (flags & 0x02) != 0; // Use this mesh for collision + bool bbcollide = (flags & 0x04) != 0; // Use bounding box for + // collision + // Always use mesh collision for now + if(bbcollide) collide = true; + bbcollide = false; + + // Things marked "NCO" should not collide with anything. + if(flags & 0x800) + collide = false; + + // Skip the entire material phase for hidden nodes + if(hidden) goto nomaterial; + // Scan the property list for textures foreach(Property p; shape.properties) { @@ -227,6 +250,8 @@ struct MeshLoader texturePtr); } + nomaterial: + with(shape.data) { //writefln("Number of vertices: ", vertices.length); @@ -251,7 +276,9 @@ struct MeshLoader maxY = -float.infinity, maxZ = -float.infinity; - // Calculate the bounding box. TODO: This is really a hack. + // Calculate the bounding box. TODO: This is really a + // hack. IIRC the bounding box supplied by the NIF could not + // be trusted, but I can't remember why :/ for( int i; i < vertices.length; i+=3 ) { if( vertices[i] < minX ) minX = vertices[i]; @@ -269,20 +296,34 @@ struct MeshLoader float[9] matrix; ogre_getWorldTransform(node, trans.ptr, matrix.ptr); - // Create a bullet collision shape from the trimesh, if there - // are any triangles present. Pass along the world - // transformation as well, since we must transform the trimesh - // data manually. - if(facesPtr != null) - bullet_createTriShape(triangles.length, facesPtr, - vertices.length, vertices.ptr, + // Next we must create the actual OGRE mesh and the collision + // objects, based on the flags we have been given. + assert(!bbcollide); + /* // Bounding box collision currently disabled + if(bbcollide) + // Insert the bounding box into the collision system + bullet_createBoxShape(minX, minY, minZ, maxX, maxY, maxZ, trans.ptr, matrix.ptr); - // Create the ogre mesh, associate it with the node - ogre_createMesh(newName.ptr, vertices.length, vertices.ptr, - normalsPtr, colorsPtr, uvsPtr, triangles.length, facesPtr, - radius, material.ptr, minX, minY, minZ, maxX, maxY, maxZ, - node); + // Create a bullet collision shape from the trimesh. Pass + // along the world transformation as well, since we must + // transform the trimesh data manually. + else*/if(collide) + { + assert(facesPtr !is null, + "cannot create collision shape without a mesh"); + bullet_createTriShape(triangles.length, facesPtr, + vertices.length, vertices.ptr, + trans.ptr, matrix.ptr); + } + + // Create the ogre mesh, associate it with the node. Skip for + // hidden nodes. + if(!hidden) + ogre_createMesh(newName.ptr, vertices.length, vertices.ptr, + normalsPtr, colorsPtr, uvsPtr, triangles.length, + facesPtr, radius, material.ptr, minX, minY, minZ, + maxX, maxY, maxZ, node); } } }