diff --git a/README.txt b/README.txt index cc4989bd5..5f8d1f245 100644 --- a/README.txt +++ b/README.txt @@ -122,6 +122,9 @@ Changelog: - working on collision detection with Bullet - working on fixing sound issues for windows (running out of sound resources, music playback doesn't good) +- new key bindings: + t - toggle physics mode (walking, flying, ghost) + n - nighteye, toggle full ambient light - added build files for CMake (with CMakeD) and Code::Blocks (neither are tested yet) - various minor changes and updates diff --git a/bullet/bindings.d b/bullet/bindings.d index 5bb45f7db..9df829227 100644 --- a/bullet/bindings.d +++ b/bullet/bindings.d @@ -35,6 +35,9 @@ extern(C): // Initialize the dynamic world. Returns non-zero if an error occurs. int bullet_init(); +// Switch to the next physics mode +void bullet_nextMode(); + // Warp the player to a specific location. void bullet_movePlayer(float x, float y, float z); diff --git a/bullet/cpp_bullet.cpp b/bullet/cpp_bullet.cpp index 26ecc440b..af4b66d76 100644 --- a/bullet/cpp_bullet.cpp +++ b/bullet/cpp_bullet.cpp @@ -64,6 +64,16 @@ btTriangleIndexVertexArray *g_currentMesh; btHashedOverlappingPairCache* g_pairCache; CustomOverlappingPairCallback *g_customPairCallback; +// Three physics modes: walking (with gravity and collision), flying +// (collision but no gravity) and ghost mode (fly through walls) +enum + { + PHYS_WALK, + PHYS_FLY, + PHYS_GHOST + }; +int g_physMode; + // Include the player physics #include "cpp_player.cpp" @@ -181,10 +191,34 @@ extern "C" int32_t bullet_init() // Make sure this is zero at startup g_currentMesh = NULL; + // Start out walking + g_physMode = PHYS_WALK; + // Success! return 0; } +// Switch to the next physics mode +extern "C" void bullet_nextMode() +{ + g_physMode++; + if(g_physMode > PHYS_GHOST) + g_physMode = PHYS_WALK; + + switch(g_physMode) + { + case PHYS_WALK: + cout << "Entering walking mode\n"; + break; + case PHYS_FLY: + cout << "Entering flying mode\n"; + break; + case PHYS_GHOST: + cout << "Entering ghost mode\n"; + break; + } +} + // Warp the player to a specific location. We do not bother setting // rotation, since it's completely irrelevant for collision detection. extern "C" void bullet_movePlayer(float x, float y, float z) @@ -285,6 +319,8 @@ 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 if(scale != 1.0) { cout << "WARNING: Cannot scale collision meshes yet (wanted " diff --git a/bullet/cpp_player.cpp b/bullet/cpp_player.cpp index 1b9a04c68..c1982bfc8 100644 --- a/bullet/cpp_player.cpp +++ b/bullet/cpp_player.cpp @@ -232,8 +232,6 @@ bool recoverFromPenetration() g_dynamicsWorld->getDispatchInfo(), g_dispatcher); - g_playerPosition = g_playerObject->getWorldTransform().getOrigin(); - btScalar maxPen = 0.0; for (int i = 0; i < g_pairCache->getNumOverlappingPairs(); i++) { @@ -285,41 +283,56 @@ bool recoverFromPenetration() // main function is responsible for player movement. void playerStepCallback(btDynamicsWorld* dynamicsWorld, btScalar timeStep) { - // Before moving, recover from current penetrations + // The walking direction is set from D code each frame, and the + // final player position is read back from D code after the + // simulation. + btVector3 walkStep = g_walkDirection * timeStep; - int numPenetrationLoops = 0; - g_touchingContact = false; - while (recoverFromPenetration()) - { - numPenetrationLoops++; - g_touchingContact = true; + // Get the player position + g_playerPosition = g_playerObject->getWorldTransform().getOrigin(); - // Make sure we don't stay here indefinitely - if (numPenetrationLoops > 4) - break; + if(g_physMode == PHYS_GHOST) + { + // Ghost mode - just move, no collision + g_playerPosition += walkStep; } + else + { + // Collision detection is active - // Get the player position - btTransform xform; - xform = g_playerObject->getWorldTransform (); - g_playerPosition = xform.getOrigin(); + // Before moving, recover from current penetrations + int numPenetrationLoops = 0; + g_touchingContact = false; + while (recoverFromPenetration()) + { + numPenetrationLoops++; + g_touchingContact = true; - // Next, do the walk. + // Make sure we don't stay here indefinitely + if (numPenetrationLoops > 4) + break; + } - // TODO: Walking direction should be set from D code, and the final - // position should be retrieved back from C++. The walking direction - // always reflects the intentional action of an agent (ie. the - // player or the AI), but the end result comes from collision and - // physics. + // recoverFromPenetration updates g_playerPosition and the + // collision mesh, so they are still in sync at this point - btVector3 walkStep = g_walkDirection * timeStep; + // Next, do the walk. The following functions only updates + // g_playerPosition, they do not move the collision object. - stepUp(); - stepForward(walkStep); - stepDown(timeStep); + if(g_physMode == PHYS_WALK) + { + stepUp(); + stepForward(walkStep); + stepDown(timeStep); + } + else if(g_physMode == PHYS_FLY) + stepForward(walkStep); + else + cout << "WARNING: Unknown physics mode " << g_physMode << "!\n"; + } - // Move the player (but keep rotation) - xform = g_playerObject->getWorldTransform (); + // Move the player collision mesh + btTransform xform = g_playerObject->getWorldTransform (); xform.setOrigin (g_playerPosition); g_playerObject->setWorldTransform (xform); } diff --git a/core/config.d b/core/config.d index 9fef75bd5..de5f5ebd5 100644 --- a/core/config.d +++ b/core/config.d @@ -240,6 +240,8 @@ struct ConfigManager bind(Keys.Fullscreen, KC.F); bind(Keys.ToggleBattleMusic, KC.SPACE); + bind(Keys.PhysMode, KC.T); + bind(Keys.Nighteye, KC.N); bind(Keys.Debug, KC.G); bind(Keys.Pause, KC.PAUSE, KC.P); diff --git a/input/events.d b/input/events.d index 23e227558..4d6451ee4 100644 --- a/input/events.d +++ b/input/events.d @@ -221,6 +221,9 @@ extern(C) void d_handleKey(KC keycode, dchar text = 0) case Keys.Mute: toggleMute(); break; case Keys.Fullscreen: toggleFullscreen(); break; + case Keys.PhysMode: bullet_nextMode(); break; + case Keys.Nighteye: ogre_toggleLight(); break; + case Keys.Debug: ogre_debug(0); break; case Keys.ScreenShot: takeScreenShot(); break; case Keys.Pause: togglePause(); break; diff --git a/input/keys.d b/input/keys.d index 9773f6500..c988472af 100644 --- a/input/keys.d +++ b/input/keys.d @@ -65,6 +65,8 @@ enum Keys // These will not be part of the finished product Fullscreen, ToggleBattleMusic, + PhysMode, // Toggle physics mode between walking, flying and ghost + Nighteye, // Full ambient lighting Debug, // Misc @@ -248,6 +250,8 @@ struct KeyBindings keyToString[Keys.Fullscreen] = "Toggle Fullscreen Mode"; keyToString[Keys.ToggleBattleMusic] = "Toggle Battle Music"; + keyToString[Keys.PhysMode] = "Toggle Physics Mode"; + keyToString[Keys.Nighteye] = "Toggle Nighteye"; keyToString[Keys.Debug] = "OGRE Test Action"; keyToString[Keys.Pause] = "Pause"; diff --git a/nif/controlled.d b/nif/controlled.d index dc96969e9..26a74ed2c 100644 --- a/nif/controlled.d +++ b/nif/controlled.d @@ -49,11 +49,10 @@ abstract class Controlled : Extra // Record with name and controller/extra data link abstract class Named : Controlled { + // Name of this object. This is used to refer to the object from .kf + // files. char[] name; - //Extra extra; - //Controller controller; - override: char[] toString() { @@ -68,23 +67,8 @@ abstract class Named : Controlled //super.read(f); name = nifFile.getString(); debug(verbose) writefln("Name: %s", name); - /* - debug(verbose) writef("Extra "); - getIndex(f); - debug(verbose) writef("Controller "); - getIndex(f); - */ super.read(); } - - /* - void sortOut(Record[] list) - { - super.sortOut(list); - extra = lookup!(Extra)(list); - controller = lookup!(Controller)(list); - } - */ } class NiSequenceStreamHelper : Named {} diff --git a/nif/data.d b/nif/data.d index e5d935850..2701591e2 100644 --- a/nif/data.d +++ b/nif/data.d @@ -147,6 +147,15 @@ class ShapeData : Record if(nifFile.getInt != 0) { if(uvs == 0) nifFile.warn("Empty UV list"); + + // Only the 6 first bits are used as a count, the rest are + // (unknown) flags. + if(uvs > 0b111111) + { + nifFile.warn("UV count contains (unknown) flags"); + uvs = uvs & 0b111111; + } + uvlist = nifFile.getArraySize!(float)(uvs*verts*2); } else if(uvs != 0) nifFile.warn("UV list was unexpectedly missing"); @@ -528,6 +537,7 @@ class NiTriShapeData : ShapeData debug(verbose) writefln("Number of faces: ", tris); if(tris) { + // Must have three times as many vertices as triangles int cnt = nifFile.getIntIs(tris*3); triangles = nifFile.getArraySize!(short)(cnt); } diff --git a/ogre/bindings.d b/ogre/bindings.d index 2b0c0f187..28266fd46 100644 --- a/ogre/bindings.d +++ b/ogre/bindings.d @@ -70,6 +70,9 @@ void ogre_setFog(float rf, float gf, float bf, // Create a simple sky dome int ogre_makeSky(); +// Toggle full ambient lighting on and off +void ogre_toggleLight(); + // Enter main rendering loop void ogre_startRendering(); diff --git a/ogre/cpp_framelistener.cpp b/ogre/cpp_framelistener.cpp index 37feeb9a5..da73335b0 100644 --- a/ogre/cpp_framelistener.cpp +++ b/ogre/cpp_framelistener.cpp @@ -121,6 +121,8 @@ extern "C" void ogre_rotateCamera(float x, float y) { mCamera->yaw(Degree(-x)); mCamera->pitch(Degree(-y)); + + //g_light->setDirection(mCamera->getDirection()); } // Get current camera position @@ -154,6 +156,8 @@ extern "C" void ogre_moveCamera(float x, float y, float z) // is not affected by the rotation of the root node, so we must // transform this manually. mCamera->setPosition(Vector3(x,z,-y)); + + //g_light->setPosition(mCamera->getPosition()); } // Rotate camera using Morrowind rotation specifiers diff --git a/ogre/cpp_interface.cpp b/ogre/cpp_interface.cpp index fb796c012..e7329181b 100644 --- a/ogre/cpp_interface.cpp +++ b/ogre/cpp_interface.cpp @@ -163,7 +163,7 @@ extern "C" void ogre_initWindow() std::cout << "ogre_initWindow finished\n"; } -// Make a scene, set the given ambient light +// Make a scene extern "C" void ogre_makeScene() { // Get the SceneManager, in this case a generic one @@ -198,6 +198,53 @@ extern "C" void ogre_makeScene() SceneNode *rt = mSceneMgr->getRootSceneNode(); root = rt->createChildSceneNode(); root->pitch(Degree(-90)); + + /* + g_light = mSceneMgr->createLight("carry"); + g_light->setDiffuseColour(1,0.7,0.3); + g_light->setAttenuation(2000, 0, 0.008, 0); + */ +} + +/* +// Toggle carryable light +extern "C" void ogre_toggleCarryLight() +{ + if(g_spotOn == 0) + { + g_light->setVisible(true); + g_spotOn = 1; + } + else + { + g_light->setVisible(false); + g_spotOn = 0; + } +} +*/ + +// Toggle ambient light +extern "C" void ogre_toggleLight() +{ + if(g_lightOn == 0) + { + std::cout << "Turning the lights up\n"; + ColourValue half = 0.7*g_ambient + 0.3*ColourValue(1,1,1); + mSceneMgr->setAmbientLight(half); + g_lightOn = 1; + } + else if(g_lightOn == 1) + { + std::cout << "Turning the lights to full\n"; + g_lightOn = 2; + mSceneMgr->setAmbientLight(ColourValue(1,1,1)); + } + else + { + std::cout << "Setting lights to normal\n"; + g_lightOn = 0; + mSceneMgr->setAmbientLight(g_ambient); + } } // Create a sky dome. Currently disabled since we aren't including the @@ -217,6 +264,7 @@ extern "C" Light* ogre_attachLight(char *name, SceneNode* base, // This seems to look reasonably ok. l->setAttenuation(3*radius, 0, 0, 12.0/(radius*radius)); + //l->setAttenuation(5*radius, 0, 0, 8.0/(radius*radius)); // base might be null, sometimes lights don't have meshes if(base) base->attachObject(l); @@ -233,8 +281,8 @@ extern "C" void ogre_toggleFullscreen() extern "C" void ogre_setAmbient(float r, float g, float b, // Ambient light float rs, float gs, float bs) // "Sunlight" { - ColourValue c = ColourValue(r, g, b); - mSceneMgr->setAmbientLight(c); + g_ambient = ColourValue(r, g, b); + mSceneMgr->setAmbientLight(g_ambient); // Create a "sun" that shines light downwards. It doesn't look // completely right, but leave it for now. diff --git a/ogre/cpp_ogre.cpp b/ogre/cpp_ogre.cpp index ac786d786..e408acf06 100644 --- a/ogre/cpp_ogre.cpp +++ b/ogre/cpp_ogre.cpp @@ -42,6 +42,14 @@ SceneManager *mSceneMgr; Camera *mCamera; Viewport *vp; +ColourValue g_ambient; +int g_lightOn = 0; + +/* +int g_spotOn = 0; +Light *g_light; +*/ + OIS::InputManager *mInputManager; OIS::Mouse *mMouse; OIS::Keyboard *mKeyboard; diff --git a/ogre/meshloader.d b/ogre/meshloader.d index e86984aa4..09907ce3b 100644 --- a/ogre/meshloader.d +++ b/ogre/meshloader.d @@ -102,8 +102,8 @@ struct MeshLoader // names later, in order to attach arms and legs on NPCs // etc. Always ignore transformation of the first node? This is a // problem, I don't know when to do this and when not to. Neither - // is always right. Update: It looks like we should always set - // noRot to false in exterior cells. + // is always right. Update: I originally thought noRot should be + // false for exteriors and true for interiors, but this isn't so. NodePtr node = ogre_createNode(UniqueName(data.name).ptr, &data.trafo, parent, cast(int)noRot); diff --git a/openmw.d b/openmw.d index 195ba8d60..ec4c9044e 100644 --- a/openmw.d +++ b/openmw.d @@ -308,9 +308,9 @@ void main(char[][] args) // Doors foreach(ref LiveDoor ls; cd.doors) putObject(ls.m.model, &ls.base.pos, ls.base.scale); - // Activators + // Activators (including beds etc) foreach(ref LiveActivator ls; cd.activators) - putObject(ls.m.model, &ls.base.pos, ls.base.scale); + putObject(ls.m.model, &ls.base.pos, ls.base.scale, true); // Potions foreach(ref LivePotion ls; cd.potions) putObject(ls.m.model, &ls.base.pos, ls.base.scale); diff --git a/openmw.ini.win32 b/openmw.ini.win32 index 2a19c8a35..6ff46858a 100755 --- a/openmw.ini.win32 +++ b/openmw.ini.win32 @@ -31,6 +31,7 @@ Decrease SFX Volume=3 Mute Sound=m Toggle Fullscreen Mode=f Toggle Battle Music=space +Toggle Physics Mode=t OGRE Test Action=g Pause=pause,p Screen Shot=print_screen @@ -41,3 +42,6 @@ Main Volume=0.7 Music Volume=0.5 SFX Volume=0.5 Enable Music=yes + +[Game Files] +GameFile[0]=Morrowind.esm