removing last bits of old D code

actorid
Marc Zinnschlag 13 years ago
parent f14dc90b34
commit 6ada405af0

@ -1,66 +0,0 @@
# /
/cache
/later
/openmw.ini.*
/rr.sh
/fontdump
/MyGUI.log
/upm.sh
/raw.txt
/vids
/include
/includes
/.thumbnails
/*.jpg
/*.dll
/*.exe
/*.def
/*.a
/*.map
/*.rsp
/ogre.cfg
/openmw
/bored
/bsatool
/niftool
/esmtool
/bored.highscores
/Ogre.log
/openmw.ini
/openmw.ini.old
/dsss_*
/dsss.last
/objs
/nifobjs
# /bullet/
/bullet/OgreOpcode*
/bullet/demo
/bullet/*.a
# /media_mygui/
/media_mygui/core.skin.orig
/media_mygui/.thumbnails
# /monster/
/monster/*openmw_last
# /mscripts/
/mscripts/draft
# /nif/
/nif/bumpmap
/nif/*.nif
# /ogre/
/ogre/*.nif
/ogre/cs
# /util/
/util/iconv
*.o
*.patch
*.diff
.directory

@ -1,85 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (bindings.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module bullet.bindings;
/*
* This module is the interface between D and the C++ code that
* handles Bullet.
*/
typedef void* BulletShape;
extern(C):
// Initialize the dynamic world. Returns non-zero if an error occurs.
int bullet_init();
// Set physics modes
void bullet_nextMode();
void bullet_walk();
void bullet_fly();
void bullet_ghost();
// Warp the player to a specific location.
void bullet_movePlayer(float x, float y, float z);
// Request that the player moves in this direction
void bullet_setPlayerDir(float x, float y, float z);
// Get the current player position, after physics and collision have
// been applied.
void bullet_getPlayerPos(float *x, float *y, float *z);
// Create a box shape. Used for bounding boxes. The box is a trimesh
// and is hollow (you can walk inside it.)
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
// them all in one shape, we must transform the vertices manually.
void bullet_createTriShape(int numFaces,
void *triArray,
int numVerts,
void *vertArray,
float *trans,float *matrix);
// "Flushes" the meshes created with createTriShape, returning the
// pointer to the final shape object.
BulletShape bullet_getFinalShape();
// Insert a static mesh with the given translation, quaternion
// rotation and scale. The quaternion is assumed to be in Ogre format,
// ie. with the W first.
void bullet_insertStatic(BulletShape shp, float *pos,
float *quat, float scale);
// Move the physics simulation 'delta' seconds forward in time
void bullet_timeStep(float delta);
// Deallocate objects
void bullet_cleanup();

@ -1,34 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (bullet.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module bullet.bullet;
import bullet.bindings;
void initBullet()
{
if(bullet_init())
throw new Exception("Bullet setup failed");
}
void cleanupBullet() { bullet_cleanup(); }

@ -1,502 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (cpp_bullet.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
#include "btBulletDynamicsCommon.h"
#include <iostream>
#include "../util/dbg.h"
using namespace std;
class CustomOverlappingPairCallback;
enum
{
MASK_PLAYER = 1,
MASK_STATIC = 2
};
// System variables
btDefaultCollisionConfiguration* g_collisionConfiguration;
btCollisionDispatcher *g_dispatcher;
//btBroadphaseInterface *g_broadphase;
btAxisSweep3 *g_broadphase;
btSequentialImpulseConstraintSolver* g_solver;
btDynamicsWorld *g_dynamicsWorld;
// Player variables
btCollisionObject* g_playerObject;
btConvexShape *g_playerShape;
// Player position. This is updated automatically by the physics
// system based on g_walkDirection and collisions. It is read by D
// code through bullet_getPlayerPos().
btVector3 g_playerPosition;
// Walking vector - defines direction and speed that the player
// intends to move right now. This is updated from D code each frame
// through bullet_setPlayerDir(), based on player input (and later, AI
// decisions.) The units of the vector are points per second.
btVector3 g_walkDirection;
// The current trimesh shape being built. All new inserted meshes are
// added into this, until bullet_getFinalShape() is called.
btTriangleIndexVertexArray *g_currentMesh;
// 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
// player (in g_pairCache). This list is used in the function called
// recoverFromPenetration().
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"
// Include the uniform shape scaler
#include "cpp_scale.cpp"
class CustomOverlappingPairCallback : public btOverlappingPairCallback
{
public:
virtual btBroadphasePair* addOverlappingPair(btBroadphaseProxy* proxy0,
btBroadphaseProxy* proxy1)
{
if (proxy0->m_clientObject==g_playerObject ||
proxy1->m_clientObject==g_playerObject)
return g_pairCache->addOverlappingPair(proxy0,proxy1);
return 0;
}
virtual void* removeOverlappingPair(btBroadphaseProxy* proxy0,
btBroadphaseProxy* proxy1,
btDispatcher* dispatcher)
{
if (proxy0->m_clientObject==g_playerObject ||
proxy1->m_clientObject==g_playerObject)
return g_pairCache->removeOverlappingPair(proxy0,proxy1,dispatcher);
return 0;
}
void removeOverlappingPairsContainingProxy(btBroadphaseProxy* proxy0,
btDispatcher* dispatcher)
{ if (proxy0->m_clientObject==g_playerObject)
g_pairCache->removeOverlappingPairsContainingProxy(proxy0,dispatcher);
}
};
extern "C" int32_t bullet_init()
{
// ------- SET UP THE WORLD -------
// Set up basic objects
g_collisionConfiguration = new btDefaultCollisionConfiguration();
g_dispatcher = new btCollisionDispatcher(g_collisionConfiguration);
//g_broadphase = new btDbvtBroadphase();
g_solver = new btSequentialImpulseConstraintSolver;
// TODO: Figure out what to do with this. We need the user callback
// function used below (I think), but this is only offered by this
// broadphase implementation (as far as I can see.) Maybe we can
// scan through the cell first and find good values that covers all
// the objects before we set up the dynamic world. Another option is
// 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(-20000,-20000,-20000);
btVector3 worldMax(20000,20000,20000);
g_broadphase = new btAxisSweep3(worldMin,worldMax);
g_dynamicsWorld =
new btDiscreteDynamicsWorld(g_dispatcher,
g_broadphase,
g_solver,
g_collisionConfiguration);
//g_dynamicsWorld->setGravity(btVector3(0,-10,0));
// ------- SET UP THE PLAYER -------
// Create the player collision shape.
float width = 30;
/*
float height = 50;
btVector3 spherePositions[2];
btScalar sphereRadii[2];
sphereRadii[0] = width;
sphereRadii[1] = width;
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);
*/
// 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 ();
g_playerObject->setCollisionShape (g_playerShape);
g_playerObject->setCollisionFlags (btCollisionObject::CF_NO_CONTACT_RESPONSE);
// ------- OTHER STUFF -------
// Create a custom callback to pick out all the objects colliding
// with the player. We use this in the collision recovery phase.
g_pairCache = new btHashedOverlappingPairCache();
g_customPairCallback = new CustomOverlappingPairCallback();
g_broadphase->setOverlappingPairUserCallback(g_customPairCallback);
// Set up the callback that moves the player at the end of each
// simulation step.
g_dynamicsWorld->setInternalTickCallback(playerStepCallback);
// Add the character collision object to the world.
g_dynamicsWorld->addCollisionObject(g_playerObject,
MASK_PLAYER,
MASK_STATIC);
// Make sure these is zero at startup
g_currentMesh = NULL;
// Start out walking
g_physMode = PHYS_WALK;
// Success!
return 0;
}
// Set physics modes
extern "C" void bullet_walk()
{
g_physMode = PHYS_WALK;
cout << "Walk mode\n";
}
extern "C" void bullet_fly()
{
g_physMode = PHYS_FLY;
cout << "Fly mode\n";
}
extern "C" void bullet_ghost()
{
g_physMode = PHYS_GHOST;
cout << "Ghost mode\n";
}
// Switch to the next physics mode
extern "C" void bullet_nextMode()
{
switch(g_physMode)
{
case PHYS_WALK:
bullet_fly();
break;
case PHYS_FLY:
bullet_ghost();
break;
case PHYS_GHOST:
bullet_walk();
break;
}
}
// Warp the player to a specific location. We do not bother setting
// rotation, since it's completely irrelevant for collision detection,
// and doubly so since the collision mesh is a sphere.
extern "C" void bullet_movePlayer(float x, float y, float z)
{
btTransform tr;
tr.setIdentity();
tr.setOrigin(btVector3(x,y,z));
g_playerObject->setWorldTransform(tr);
}
// Request that the player moves in this direction
extern "C" void bullet_setPlayerDir(float x, float y, float z)
{ g_walkDirection.setValue(x,y,z); }
// Get the current player position, after physics and collision have
// been applied.
extern "C" void bullet_getPlayerPos(float *x, float *y, float *z)
{
*x = g_playerPosition.getX();
*y = g_playerPosition.getY();
*z = g_playerPosition.getZ();
}
void* copyBuffer(const void *buf, int elemSize, int len)
{
int size = elemSize * len;
void *res = malloc(size);
memcpy(res, buf, size);
return res;
}
// Internal version that does not copy buffers
void createTriShape(int32_t numFaces, const void *triArray,
int32_t numVerts, const void *vertArray,
const float *trans, const float *matrix)
{
// This struct holds the index and vertex buffers of a single
// trimesh.
btIndexedMesh im;
// Set up the triangles
int numTriangles = numFaces / 3;
im.m_numTriangles = numTriangles;
im.m_triangleIndexStride = 6; // 3 indices * 2 bytes per short
im.m_triangleIndexBase = (unsigned char*)triArray;
// Set up the vertices
im.m_numVertices = numVerts;
im.m_vertexStride = 12; // 4 bytes per float * 3 floats per vertex
im.m_vertexBase = (unsigned char*)vertArray;
// Transform vertex values in vb according to 'trans' and 'matrix'
float *vb = (float*)im.m_vertexBase;
for(int i=0; i<numVerts; i++)
{
float x,y,z;
// Reinventing basic linear algebra for the win!
x = matrix[0]*vb[0]+matrix[1]*vb[1]+matrix[2]*vb[2] + trans[0];
y = matrix[3]*vb[0]+matrix[4]*vb[1]+matrix[5]*vb[2] + trans[1];
z = matrix[6]*vb[0]+matrix[7]*vb[1]+matrix[8]*vb[2] + trans[2];
*(vb++) = x;
*(vb++) = y;
*(vb++) = z;
}
// If no mesh is currently active, create one
if(g_currentMesh == NULL)
g_currentMesh = new btTriangleIndexVertexArray;
// Add the mesh. Nif data stores triangle indices as shorts.
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 should use the NIF-specified bounding box
// for this, not our automatically calculated one.
extern "C" void bullet_createBoxShape(float xmin, float ymin, float zmin,
float xmax, float ymax, float zmax,
float *trans, float *matrix)
{
// Make a copy of the vertex buffer, since we need to change it
float *vbuffer = (float*)copyBuffer(cube_verts, 12, cube_num_verts);
// Calculate the widths
float xwidth = xmax-xmin;
float ywidth = ymax-ymin;
float zwidth = zmax-zmin;
// Transform the cube to (xmin,xmax) etc
float *vb = vbuffer;
for(int i=0; i<cube_num_verts; i++)
{
*vb = (*vb)*xwidth + xmin; vb++;
*vb = (*vb)*ywidth + ymin; vb++;
*vb = (*vb)*zwidth + zmin; vb++;
}
// Insert the trimesh
createTriShape(cube_num_tris*3, cube_tris,
cube_num_verts, vbuffer,
trans, matrix);
}
// Create a triangle shape and insert it into the current index/vertex
// array. If no array is active, create one.
extern "C" void bullet_createTriShape(int32_t numFaces,
void *triArray,
int32_t numVerts,
void *vertArray,
float *trans,
float *matrix)
{
createTriShape(numFaces, copyBuffer(triArray, 2, numFaces),
numVerts, copyBuffer(vertArray, 12, numVerts),
trans, matrix);
}
// Get the shape built up so far, if any. This clears g_currentMesh,
// so the next call to createTriShape will start a new shape.
extern "C" btCollisionShape *bullet_getFinalShape()
{
btCollisionShape *shape;
// Create a shape from all the inserted completed meshes
shape = NULL;
if(g_currentMesh != NULL)
shape = new btBvhTriangleMeshShape(g_currentMesh, false);
// Clear these for the next NIF
g_currentMesh = NULL;
return shape;
}
// Insert a static mesh
extern "C" void bullet_insertStatic(btConcaveShape *shape,
float *pos,
float *quat,
float scale)
{
// FIXME: Scaling does NOT work.
// Are we scaled?
if(scale != 1.0)
{
//cout << "Scaling shape " << shape << " by " << scale << endl;
// Not quite sure how to handle local scaling yet. Our initial
// attempt was to create a wrapper that showed a scale mesh to
// the "outside world" while referencing the original, but I
// suspect it ended up altering the original data. At least it
// doesn't work the way it is now, and only crashes.
// The alternative is to create a new copy of the shape for each
// scaled version we insert. This is wasteful, but might be
// acceptable.
// It's also possible we can achieve this effect by changing
// larger parts of the Bullet library - but I hope I don't have
// to create my own dispatcher and such. Finally, even if the
// transformations given to objects are supposed to be uniform
// in length, maybe we can cheat the system and scale the
// transformation instead. Try it just for kicks, and go through
// the system to see what parts of Bullet it would break.
// In any case, when we find a solution we should apply it to
// all shapes (not just scale!=1.0) to get a better impression
// of any performance and memory overhead.
// Also, as an optimization, it looks like multiple instances of
// the same shape are often inserted with the same scale
// factor. We could easily cache this. The scale-recreation of
// meshes (in necessary) could be done as a separate function,
// and the caching could be done in D code.
}
btTransform trafo;
trafo.setIdentity();
trafo.setOrigin(btVector3(pos[0], pos[1], pos[2]));
// Ogre uses WXYZ quaternions, Bullet uses XYZW.
trafo.setRotation(btQuaternion(quat[1], quat[2], quat[3], quat[0]));
// Create and insert the collision object
btCollisionObject *obj = new btCollisionObject();
obj->setCollisionShape(shape);
obj->setWorldTransform(trafo);
g_dynamicsWorld->addCollisionObject(obj, MASK_STATIC, MASK_PLAYER);
}
// Move the physics simulation 'delta' seconds forward in time
extern "C" void bullet_timeStep(float delta)
{
TRACE("bullet_timeStep");
// TODO: We might experiment with the number of time steps. Remember
// that the function also returns the number of steps performed.
g_dynamicsWorld->stepSimulation(delta,2);
}
// Cleanup in the reverse order of creation/initialization
extern "C" void bullet_cleanup()
{
// Remove the rigidbodies from the dynamics world and delete them
for (int i=g_dynamicsWorld->getNumCollisionObjects()-1; i>=0 ;i--)
{
btCollisionObject* obj = g_dynamicsWorld->getCollisionObjectArray()[i];
btRigidBody* body = btRigidBody::upcast(obj);
if (body && body->getMotionState())
delete body->getMotionState();
g_dynamicsWorld->removeCollisionObject( obj );
delete obj;
}
delete g_dynamicsWorld;
delete g_solver;
delete g_broadphase;
delete g_dispatcher;
delete g_collisionConfiguration;
}

@ -1,381 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
(see additional copyrights for this file below)
This file (cpp_player.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
----
Parts of this file is based on the kinematic character controller
demo included with the Bullet library. The copyright statement for
these parts follow:
Bullet Continuous Collision Detection and Physics Library
Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software. Permission is
granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
// This file handles player-specific physics and collision detection
// TODO: Later we might handle various physics modes, eg. dynamic
// (full physics), player_walk, player_fall, player_swim,
// player_float, player_levitate, player_ghost. These would be
// applicable to any object (through Monster script), allowing the
// physics code to be shared between NPCs, creatures and the player.
// Variables used internally in this file. Once we make per-object
// player collision, these will be member variables.
bool g_touchingContact;
btVector3 g_touchingNormal;
btScalar g_currentStepOffset;
float g_stepHeight = 5;
// Returns the reflection direction of a ray going 'direction' hitting
// a surface with normal 'normal'
btVector3 reflect (const btVector3& direction, const btVector3& normal)
{ return direction - (btScalar(2.0) * direction.dot(normal)) * normal; }
// Returns the portion of 'direction' that is perpendicular to
// 'normal'
btVector3 perpComponent (const btVector3& direction, const btVector3& normal)
{ return direction - normal * direction.dot(normal); }
btManifoldArray manifoldArray;
// Callback used for collision detection sweep tests. It prevents self
// collision and is used in calls to convexSweepTest(). TODO: It might
// be enough to just set the filters on this. If we set the group and
// mask so that we only collide with static objects, self collision
// would never happen. The sweep test function should have had a
// version where you only specify the filters - I might add that
// myself.
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
{
public:
ClosestNotMeConvexResultCallback()
: btCollisionWorld::ClosestConvexResultCallback
(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
{
m_collisionFilterGroup = g_playerObject->
getBroadphaseHandle()->m_collisionFilterGroup;
m_collisionFilterMask = g_playerObject->
getBroadphaseHandle()->m_collisionFilterMask;
}
btScalar addSingleResult(btCollisionWorld::LocalConvexResult&
convexResult, bool normalInWorldSpace)
{
if (convexResult.m_hitCollisionObject == g_playerObject) return 1.0;
return ClosestConvexResultCallback::addSingleResult
(convexResult, normalInWorldSpace);
}
};
// Used to step up small steps and slopes.
void stepUp()
{
// phase 1: up
btVector3 targetPosition = g_playerPosition +
btVector3(0.0, 0.0, g_stepHeight);
btTransform start, end;
start.setIdentity ();
end.setIdentity ();
// FIXME: Handle penetration properly
start.setOrigin (g_playerPosition + btVector3(0.0, 0.1, 0.0));
end.setOrigin (targetPosition);
ClosestNotMeConvexResultCallback callback;
g_dynamicsWorld->convexSweepTest (g_playerShape, start, end, callback);
if (callback.hasHit())
{
// we moved up only a fraction of the step height
g_currentStepOffset = g_stepHeight * callback.m_closestHitFraction;
g_playerPosition.setInterpolate3(g_playerPosition, targetPosition,
callback.m_closestHitFraction);
}
else
{
g_currentStepOffset = g_stepHeight;
g_playerPosition = targetPosition;
}
}
void updateTargetPositionBasedOnCollision (const btVector3& hitNormal,
btVector3 &targetPosition)
{
btVector3 movementDirection = targetPosition - g_playerPosition;
btScalar movementLength = movementDirection.length();
if (movementLength <= SIMD_EPSILON)
return;
// Is this needed?
movementDirection.normalize();
btVector3 reflectDir = reflect(movementDirection, hitNormal);
reflectDir.normalize();
btVector3 perpendicularDir = perpComponent (reflectDir, hitNormal);
targetPosition = g_playerPosition;
targetPosition += perpendicularDir * movementLength;
}
// This covers all normal forward movement and collision, including
// walking sideways when hitting a wall at an angle. It does NOT
// handle walking up slopes and steps, or falling/gravity.
void stepForward(btVector3& walkMove)
{
btVector3 originalDir = walkMove.normalized();
// If no walking direction is given, we still run the function. This
// allows moving forces to push the player around even if she is
// standing still.
if (walkMove.length() < SIMD_EPSILON)
originalDir.setValue(0.f,0.f,0.f);
btTransform start, end;
btVector3 targetPosition = g_playerPosition + walkMove;
start.setIdentity ();
end.setIdentity ();
btScalar fraction = 1.0;
btScalar distance2 = (g_playerPosition-targetPosition).length2();
if (g_touchingContact)
if (originalDir.dot(g_touchingNormal) > btScalar(0.0))
updateTargetPositionBasedOnCollision (g_touchingNormal, targetPosition);
int maxIter = 10;
while (fraction > btScalar(0.01) && maxIter-- > 0)
{
start.setOrigin (g_playerPosition);
end.setOrigin (targetPosition);
ClosestNotMeConvexResultCallback callback;
g_dynamicsWorld->convexSweepTest (g_playerShape, start, end, callback);
fraction -= callback.m_closestHitFraction;
if (callback.hasHit())
{
// We moved only a fraction
btScalar hitDistance = (callback.m_hitPointWorld - g_playerPosition).length();
// If the distance is further than the collision margin,
// move
if (hitDistance > 0.05)
g_playerPosition.setInterpolate3(g_playerPosition, targetPosition,
callback.m_closestHitFraction);
updateTargetPositionBasedOnCollision(callback.m_hitNormalWorld,
targetPosition);
btVector3 currentDir = targetPosition - g_playerPosition;
distance2 = currentDir.length2();
if (distance2 <= SIMD_EPSILON)
break;
currentDir.normalize();
if (currentDir.dot(originalDir) <= btScalar(0.0))
break;
}
else
// we moved the whole way
g_playerPosition = targetPosition;
}
}
void stepDown (btScalar dt)
{
btTransform start, end;
// phase 3: down
btVector3 step_drop = btVector3(0,0,g_currentStepOffset);
btVector3 gravity_drop = btVector3(0,0,g_stepHeight);
btVector3 targetPosition = g_playerPosition - step_drop - gravity_drop;
start.setIdentity ();
end.setIdentity ();
start.setOrigin (g_playerPosition);
end.setOrigin (targetPosition);
ClosestNotMeConvexResultCallback callback;
g_dynamicsWorld->convexSweepTest(g_playerShape, start, end, callback);
if (callback.hasHit())
// we dropped a fraction of the height -> hit floor
g_playerPosition.setInterpolate3(g_playerPosition, targetPosition,
callback.m_closestHitFraction);
else
// we dropped the full height
g_playerPosition = targetPosition;
}
// Check if the player currently collides with anything, and adjust
// its position accordingly. Returns true if collisions were found.
bool recoverFromPenetration()
{
bool penetration = false;
// Update the collision pair cache
g_dispatcher->dispatchAllCollisionPairs(g_pairCache,
g_dynamicsWorld->getDispatchInfo(),
g_dispatcher);
btScalar maxPen = 0.0;
for (int i = 0; i < g_pairCache->getNumOverlappingPairs(); i++)
{
manifoldArray.resize(0);
btBroadphasePair* collisionPair = &g_pairCache->getOverlappingPairArray()[i];
// Get the contact points
if (collisionPair->m_algorithm)
collisionPair->m_algorithm->getAllContactManifolds(manifoldArray);
// And handle them
for (int j=0;j<manifoldArray.size();j++)
{
btPersistentManifold* manifold = manifoldArray[j];
btScalar directionSign = manifold->getBody0() ==
g_playerObject ? btScalar(-1.0) : btScalar(1.0);
for (int p=0;p<manifold->getNumContacts();p++)
{
const btManifoldPoint &pt = manifold->getContactPoint(p);
if (pt.getDistance() < 0.0)
{
// Pick out the maximum penetration normal and store
// it
if (pt.getDistance() < maxPen)
{
maxPen = pt.getDistance();
g_touchingNormal = pt.m_normalWorldOnB * directionSign;//??
}
g_playerPosition += pt.m_normalWorldOnB * directionSign *
pt.getDistance() * btScalar(0.2);
penetration = true;
}
}
}
}
btTransform newTrans = g_playerObject->getWorldTransform();
newTrans.setOrigin(g_playerPosition);
g_playerObject->setWorldTransform(newTrans);
return penetration;
}
// Callback called at the end of each simulation cycle. This is the
// main function is responsible for player movement.
void playerStepCallback(btDynamicsWorld* dynamicsWorld, btScalar timeStep)
{
// 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;
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();
if(g_physMode == PHYS_GHOST)
{
// Ghost mode - just move, no collision
g_playerPosition += walkStep;
}
else
{
// Collision detection is active
// Before moving, recover from current penetrations
int numPenetrationLoops = 0;
g_touchingContact = false;
while (recoverFromPenetration())
{
numPenetrationLoops++;
g_touchingContact = true;
// Make sure we don't stay here indefinitely
if (numPenetrationLoops > 4)
break;
}
// recoverFromPenetration updates g_playerPosition and the
// collision mesh, so they are still in sync at this point
// Next, do the walk. The following functions only updates
// g_playerPosition, they do not move the collision object.
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 collision mesh
btTransform xform = g_playerObject->getWorldTransform ();
xform.setOrigin (g_playerPosition);
g_playerObject->setWorldTransform (xform);
}

@ -1,98 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (cpp_scale.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
// WARNING: This file does NOT work, and it is not used yet.
class ScaleCallback : public btTriangleCallback
{
btTriangleCallback *call;
float factor;
public:
ScaleCallback(btTriangleCallback *c, float f)
{ call = c; factor = f; }
void processTriangle(btVector3 *tri, int partid, int triindex)
{
btVector3 vecs[3];
vecs[0] = tri[0]*factor;
vecs[1] = tri[1]*factor;
vecs[2] = tri[2]*factor;
call->processTriangle(vecs, partid, triindex);
}
};
// This class is used to uniformly scale a triangle mesh by a
// factor. It wraps around an existing shape and does not copy the
// data.
class ScaleShape : public btConcaveShape
{
btConcaveShape* child;
float factor, fact3, facthalf;
public:
ScaleShape(btConcaveShape* ch, float ft)
{
child = ch;
factor = ft;
fact3 = factor*factor*factor;
facthalf = factor*0.5;
}
void calculateLocalInertia(btScalar mass,btVector3& inertia) const
{
btVector3 tmpInertia;
child->calculateLocalInertia(mass,tmpInertia);
inertia = tmpInertia * fact3;
}
const char* getName()const { return "ScaleShape"; }
void getAabb(const btTransform& t,btVector3& aabbMin,btVector3& aabbMax) const
{
child->getAabb(t,aabbMin,aabbMax);
btVector3 aabbCenter = (aabbMax+aabbMin)*0.5;
btVector3 scaledAabbHalfExtends = (aabbMax-aabbMin)*facthalf;
aabbMin = aabbCenter - scaledAabbHalfExtends;
aabbMax = aabbCenter + scaledAabbHalfExtends;
}
void processAllTriangles(btTriangleCallback *callback,const btVector3& aabbMin,const btVector3& aabbMax) const
{
ScaleCallback scb(callback, factor);
child->processAllTriangles(&scb, aabbMin, aabbMax);
}
void setLocalScaling(const btVector3& scaling)
{ child->setLocalScaling(scaling); }
const btVector3& getLocalScaling() const
{ return child->getLocalScaling(); }
int getShapeType() const
{ return TRIANGLE_MESH_SHAPE_PROXYTYPE; }
};

@ -1,437 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (config.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module core.config;
import std.string;
import std.file;
import std.path;
import std.stdio;
import monster.monster;
import monster.util.string;
import core.inifile;
import core.filefinder;
import sound.audio;
import input.keys;
import input.ois;
import ogre.ogre;
ConfigManager config;
/*
* Structure that handles all user adjustable configuration options,
* including things like file paths, plugins, graphics resolution,
* game settings, window positions, etc. It is also responsible for
* reading and writing configuration files, for importing settings
* from Morrowind.ini and for configuring OGRE. It doesn't currently
* DO all of this, but it is supposed to in the future.
*/
struct ConfigManager
{
MonsterObject *mo;
IniWriter iniWriter;
// Mouse sensitivity
float *mouseSensX;
float *mouseSensY;
bool *flipMouseY;
// Ogre configuration
bool showOgreConfig; // The configuration setting
// The actual result, overridable by a command line switch, and also
// set to true if firstRun is true.
bool finalOgreConfig;
// Other settings
bool firstRun;
// Set to true if sound is completely disabled
bool noSound = false;
// Number of current screen shot. Saved upon exit, so that shots
// from separate sessions don't overwrite each other.
int screenShotNum;
// Game files to load (max 255)
char[][] gameFiles;
// Directories
char[] dataDir;
char[] esmDir;
char[] bsaDir;
char[] sndDir;
char[] fontDir;
char[] musDir; // Explore music
char[] musDir2; // Battle music
// Configuration file
char[] confFile = "openmw.ini";
// Cell to load at startup
char[] defaultCell;
// These set the volume to a new value and updates all sounds to
// take notice.
void setMusicVolume(float vol)
{
stack.pushFloat(vol);
mo.call("setMusicVolume");
}
float getMusicVolume()
{ return mo.getFloat("musicVolume"); }
void setSfxVolume(float vol)
{
stack.pushFloat(vol);
mo.call("setSfxVolume");
}
float getSfxVolume()
{ return mo.getFloat("sfxVolume"); }
void setMainVolume(float vol)
{
stack.pushFloat(vol);
mo.call("setMainVolume");
}
float getMainVolume()
{ return mo.getFloat("mainVolume"); }
// Initialize the config manager. Send a 'true' parameter to reset
// all keybindings to the default. A lot of this stuff will be moved
// to script code at some point. In general, all input mechanics and
// distribution of key events should happen in native code, while
// all setup and control should be handled in script code.
void initialize(bool reset = false)
{
// Initialize variables from Monster.
assert(mo !is null);
mouseSensX = mo.getFloatPtr("mouseSensX");
mouseSensY = mo.getFloatPtr("mouseSensY");
flipMouseY = mo.getBoolPtr("flipMouseY");
// Initialize the key binding manager
keyBindings.initKeys();
/* Disable this at the moment. It's a good idea to put
configuration in a central location, but it's useless as long
as Ogre expects to find it's files in the current working
directory. The best permanent solution would be to let the
locations of ogre.cfg and plugins.cfg be determined by
openmw.ini - I will fix that later.
version(Posix)
{
if(!exists(confFile))
confFile = expandTilde("~/.openmw/openmw.ini");
}
*/
readIni(reset);
}
// Read config from morro.ini, if it exists. The reset parameter is
// set to true if we should use default key bindings instead of the
// ones from the config file.
void readIni(bool reset)
{
// Read configuration file, if it exists.
IniReader ini;
ini.readFile(confFile);
screenShotNum = ini.getInt("General", "Screenshots", 0);
float mainVolume = saneVol(ini.getFloat("Sound", "Main Volume", 0.7));
float musicVolume = saneVol(ini.getFloat("Sound", "Music Volume", 0.5));
float sfxVolume = saneVol(ini.getFloat("Sound", "SFX Volume", 0.5));
bool useMusic = ini.getBool("Sound", "Enable Music", true);
lightConst = ini.getInt("LightAttenuation", "UseConstant", 0);
lightConstValue = ini.getFloat("LightAttenuation", "ConstantValue", 0.0);
lightLinear = ini.getInt("LightAttenuation", "UseLinear", 1);
lightLinearMethod = ini.getInt("LightAttenuation", "LinearMethod", 1);
lightLinearValue = ini.getFloat("LightAttenuation", "LinearValue", 3.0);
lightLinearRadiusMult = ini.getFloat("LightAttenuation", "LinearRadiusMult", 1.0);
lightQuadratic = ini.getInt("LightAttenuation", "UseQuadratic", 0);
lightQuadraticMethod = ini.getInt("LightAttenuation", "QuadraticMethod", 2);
lightQuadraticValue = ini.getFloat("LightAttenuation", "QuadraticValue", 16.0);
lightQuadraticRadiusMult = ini.getFloat("LightAttenuation", "QuadraticRadiusMult", 1.0);
lightOutQuadInLin = ini.getInt("LightAttenuation", "OutQuadInLin", 0);
*mouseSensX = ini.getFloat("Controls", "Mouse Sensitivity X", 0.2);
*mouseSensY = ini.getFloat("Controls", "Mouse Sensitivity Y", 0.2);
*flipMouseY = ini.getBool("Controls", "Flip Mouse Y Axis", false);
mo.setFloat("mainVolume", mainVolume);
mo.setFloat("musicVolume", musicVolume);
mo.setFloat("sfxVolume", sfxVolume);
mo.setBool("useMusic", useMusic);
defaultCell = ini.getString("General", "Default Cell", "Assu");
firstRun = ini.getBool("General", "First Run", true);
showOgreConfig = ini.getBool("General", "Show Ogre Config", false);
// This flag determines whether we will actually show the Ogre
// config dialogue. The EITHER of the following are true, the
// config box will be shown:
// - The program is being run for the first time
// - The "Show Ogre Config" option in openmw.ini is set.
// - The -oc option is specified on the command line
// - The file ogre.cfg is missing
finalOgreConfig = showOgreConfig || firstRun ||
!exists("ogre.cfg");
// Set default key bindings first.
with(keyBindings)
{
// Bind some default keys
bind(Keys.MoveLeft, KC.A, KC.LEFT);
bind(Keys.MoveRight, KC.D, KC.RIGHT);
bind(Keys.MoveForward, KC.W, KC.UP);
bind(Keys.MoveBackward, KC.S, KC.DOWN);
bind(Keys.MoveUp, KC.LSHIFT);
bind(Keys.MoveDown, KC.LCONTROL);
bind(Keys.MainVolUp, KC.ADD);
bind(Keys.MainVolDown, KC.SUBTRACT);
bind(Keys.MusVolDown, KC.N1);
bind(Keys.MusVolUp, KC.N2);
bind(Keys.SfxVolDown, KC.N3);
bind(Keys.SfxVolUp, KC.N4);
bind(Keys.Mute, KC.M);
bind(Keys.Fullscreen, KC.F);
bind(Keys.ToggleBattleMusic, KC.SPACE);
bind(Keys.PhysMode, KC.T);
bind(Keys.Nighteye, KC.N);
bind(Keys.ToggleGui, KC.Mouse1);
bind(Keys.Console, KC.F1, KC.GRAVE);
bind(Keys.Debug, KC.G);
bind(Keys.Pause, KC.PAUSE, KC.P);
bind(Keys.ScreenShot, KC.SYSRQ);
bind(Keys.Exit, KC.Q, KC.ESCAPE);
}
// Unless the ini file was missing or we were asked to reset all
// keybindings to default, replace all present bindings with the
// values from the ini.
if(!reset && ini.wasRead)
{
// Read key bindings
for(int i; i<Keys.Length; i++)
{
char[] s = keyToString[i];
if(s.length)
{
char[] iniVal = ini.getString("Bindings", s, "_def");
// Was the setting present in the ini file?
if(iniVal != "_def")
// If so, bind it!
keyBindings.bindComma(cast(Keys)i, iniVal);
}
}
}
// Read data file directory
dataDir = ini.getString("General", "Data Directory", "data/");
// Make sure there's a trailing slash at the end. The forward slash
// / works on all platforms, while the backslash \ does not. This
// isn't super robust, but we will fix a general path handle
// mechanism later (or use an existing one.)
if(dataDir.ends("\\")) dataDir[$-1] = '/';
if(!dataDir.ends("/")) dataDir ~= '/';
bsaDir = dataDir;
esmDir = dataDir;
sndDir = dataDir ~ "Sound/";
fontDir = dataDir ~ "Fonts/";
musDir = dataDir ~ "Music/Explore/";
musDir2 = dataDir ~ "Music/Battle/";
// A maximum of 255 game files are allowed. Search the whole range
// in case some holes developed in the number sequence. This isn't
// a great way of specifying files (it's just a copy of the flawed
// model that Morrowind uses), but it will do for the time being.
FileFinder srch = new FileFinder(esmDir, null, Recurse.No);
for(int i = 0;i < 255;i++)
{
char[] s = ini.getString("Game Files", format("GameFile[%d]",i), null);
if(s != null && srch.has(s))
gameFiles ~= esmDir ~ s;
}
delete srch;
if(gameFiles.length == 0)
{
// No game files set. Look in the esmDir for Morrowind.esm.
// We can add Tribunal.esm, and Bloodmoon.esm as defaults too
// later, when we're out of testing mode.
char[][] baseFiles = ["Morrowind.esm"];
//char[][] baseFiles = ["Morrowind.esm","Tribunal.esm","Bloodmoon.esm"];
srch = new FileFinder(esmDir, "esm", Recurse.No);
foreach(ref s; baseFiles)
{
if(srch.has(s))
{
writefln("Adding game file %s", s);
gameFiles ~= esmDir ~ s;
}
}
delete srch;
}
// FIXME: Must sort gameFiles so that ESMs come first, then ESPs.
// I don't know if this needs to be done by filename, or by the
// actual file type..
// Further sort the two groups by file date (oldest first).
/* Don't bother reading every directory seperately
bsaDir = ini.getString("General", "BSA Directory", "data/");
esmDir = ini.getString("General", "ESM Directory", "data/");
sndDir = ini.getString("General", "SFX Directory", "data/Sound/");
musDir = ini.getString("General", "Explore Music Directory", "data/Music/Explore/");
musDir2 = ini.getString("General", "Battle Music Directory", "data/Music/Battle/");
*/
}
// Create the config file
void writeConfig()
{
//writefln("writeConfig(%s)", confFile);
with(iniWriter)
{
openFile(confFile);
comment("Don't write your own comments in this file, they");
comment("will disappear when the file is rewritten.");
section("General");
writeString("Data Directory", dataDir);
/*
writeString("ESM Directory", esmDir);
writeString("BSA Directory", bsaDir);
writeString("SFX Directory", sndDir);
writeString("Explore Music Directory", musDir);
writeString("Battle Music Directory", musDir2);
*/
writeInt("Screenshots", screenShotNum);
writeString("Default Cell", defaultCell);
// Save the setting as it appeared in the input. The setting
// you specify in the ini is persistent, specifying the -oc
// parameter does not change it.
writeBool("Show Ogre Config", showOgreConfig);
// The next run is never the first run.
writeBool("First Run", false);
section("Controls");
writeFloat("Mouse Sensitivity X", *mouseSensX);
writeFloat("Mouse Sensitivity Y", *mouseSensY);
writeBool("Flip Mouse Y Axis", *flipMouseY);
section("Bindings");
comment("Key bindings. The strings must match exactly.");
foreach(int i, KeyBind b; keyBindings.bindings)
{
char[] s = keyToString[i];
if(s.length)
writeString(s, b.getString());
}
section("Sound");
writeFloat("Main Volume", mo.getFloat("mainVolume"));
writeFloat("Music Volume", mo.getFloat("musicVolume"));
writeFloat("SFX Volume", mo.getFloat("sfxVolume"));
writeBool("Enable Music", mo.getBool("useMusic"));
section("LightAttenuation");
comment("For constant attenuation");
writeInt("UseConstant", lightConst);
writeFloat("ConstantValue", lightConstValue);
comment("For linear attenuation");
writeInt("UseLinear", lightLinear);
writeInt("LinearMethod", lightLinearMethod);
writeFloat("LinearValue", lightLinearValue);
writeFloat("LinearRadiusMult", lightLinearRadiusMult);
comment("For quadratic attenuation");
writeInt("UseQuadratic", lightQuadratic);
writeInt("QuadraticMethod", lightQuadraticMethod);
writeFloat("QuadraticValue", lightQuadraticValue);
writeFloat("QuadraticRadiusMult", lightQuadraticRadiusMult);
comment("For quadratic in exteriors and linear in interiors");
writeInt("OutQuadInLin", lightOutQuadInLin);
section("Game Files");
foreach(int i, ref s; gameFiles)
writeString(format("GameFile[%d]",i), s[esmDir.length..$]);
close();
}
}
// In the future this will import settings from Morrowind.ini, as
// far as this is sensible.
void importIni()
{
/*
IniReader ini;
ini.readFile("../Morrowind.ini");
// Example of sensible options to convert:
tryArchiveFirst = ini.getInt("General", "TryArchiveFirst");
useAudio = ( ini.getInt("General", "Disable Audio") == 0 );
footStepVolume = ini.getFloat("General", "PC Footstep Volume");
subtitles = ini.getInt("General", "Subtitles") == 1;
The plugin list (all esm and esp files) would be handled a bit
differently. In our system they might be a per-user (per
"character") setting, or even per-savegame. It should be safe and
intuitive to try out a new mod without risking your savegame data
or original settings. So these would be handled in a separate
plugin manager.
In any case, the import should be interactive and user-driven, so
there is no use in making it before we have a gui of some sort up
and running.
*/
}
}

@ -1,229 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (filefinder.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module core.filefinder;
import std.file;
import std.string;
import monster.util.string;
import monster.util.aa;
import core.memory;
import std.stdio;
class FileFinderException : Exception
{
this(char[] msg, char[] ext, char[] dir)
{
if(ext.length) super(format("FileFinder for %s files in %s: %s", ext, dir, msg));
else super(format("FileFinder for %s: %s", dir, msg));
}
}
// Do we traverse directories recursively? Default is yes.
enum Recurse { Yes, No }
// The file finder is used to list all files in a directory so we can
// look up files without searching the filesystem each time. It is
// case insensitive on all platforms, and transparently converts to
// the right directory separator character (\ or /). We might extend
// it later with code from other projects.
class FileFinder
{
private:
char[][] files; // Use GC for this, it's not too big and we don't
// have to manage roots pointing to the filenames.
HashTable!(char[], int, ESMRegionAlloc, FilenameHasher) lookup;
char[] dir; // Base directory to search
char[] ext; // Extensions to pick out
void fail(char[] err)
{
throw new FileFinderException(err, ext, dir);
}
// Removes the part of a path that is stored in 'dir'
char[] removeDir(char[] path)
{
//TODO: Should this be case insensitive?
assert(path[0..dir.length] == dir);
return path[dir.length..$];
}
void insert(char[] filename)
{
// Only keep the part of the filename not given in 'dir'.
char[] name = removeDir(filename);
if(!name.iEnds(ext)) return;
// We start counting from 1
uint newVal = files.length+1;
// Insert it, or get the old value if it already exists
uint oldVal = lookup[name, newVal];
if(oldVal != newVal)
fail("Already have " ~ name ~ "\nPreviously inserted as " ~ files[oldVal-1]);
// Store it
files ~= filename;
}
public:
static char[] addSlash(char[] dir)
{
// Add a trailing slash
version(Windows) if(!dir.ends("\\")) dir ~= '\\';
version(Posix) if(!dir.ends("/")) dir ~= '/';
return dir;
}
int length() { return lookup.length; }
this(char[] dir, char[] ext = null, Recurse r = Recurse.Yes)
in
{
if(!dir.length) fail("'dir' can not be empty");
}
out
{
assert(files.length == lookup.length);
}
body
{
// Add a trailing slash
dir = addSlash(dir);
this.dir = dir;
if(ext.length && ext[0] != '.') ext = "." ~ ext;
this.ext = ext;
bool callback(DirEntry* de)
{
if (de.isdir)
{
if(r == Recurse.Yes)
listdir(de.name, &callback);
}
else
insert(de.name);
return true;
}
try listdir(dir, &callback);
catch(FileException e)
fail(e.toString);
}
char[] opIndex(int i) { return files[i-1]; }
int opIndex(char[] file)
{
int i;
// Get value if it exists
if(lookup.inList(file, i))
return i;
return 0;
}
bool has(char[] file)
{
return lookup.inList(file);
}
int opApply(int delegate(ref char[]) del)
{
int res = 0;
foreach(char[] s; files)
{
char[] tmp = removeDir(s);
res = del(tmp);
if(res) break;
}
return res;
}
char[] toString()
{
char[] result;
foreach(char[] s; this)
result ~= s ~ "\n";
return result;
}
}
// Hash functions that does not differentiate between linux and
// windows file names. This means that it is case insensitive, and
// treats '\' and '/' as the same character. Only needed in linux, in
// windows just use CITextHasher.
version(Posix)
struct FilenameHasher
{
static const char conv = 'a'-'A';
static int isEqual(char[] aa, char[] bb)
{
if(aa.length != bb.length) return 0;
foreach(int i, char a; aa)
{
char b = bb[i];
if(a == b)
continue;
// Convert both to lowercase and "/ case"
if(a <= 'Z' && a >= 'A') a += conv;
else if(a == '\\') a = '/';
if(b <= 'Z' && b >= 'A') b += conv;
else if(b == '\\') b = '/';
if(a != b) return 0;
}
// No differences were found
return 1;
}
static uint hash(char[] s)
{
uint hash;
foreach (char c; s)
{
if(c <= 'Z' && c >= 'A') c += conv;
else if(c == '\\') c = '/';
hash = (hash * 37) + c;
}
return hash;
}
}
version(Windows) alias CITextHash FilenameHasher;

@ -1,187 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (defs.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.defs;
public import std.string;
public import monster.util.string;
import monster.monster;
/*
* Types and definitions related to parsing esm and esp files
*/
alias char[4] NAME;
alias char[32] NAME32;
alias char[256] NAME256;
union Color
{
align(1) struct
{
ubyte red, green, blue, alpha;
}
ubyte[4] array;
uint value;
char[] toString() { return format("RGBA:%s", array); }
}
static assert(Color.sizeof==4);
// State of a record struct
enum LoadState
{
Unloaded, // This record is not loaded, it has just been
// referenced.
Loaded, // This record has been loaded by the current file
Previous // The record has been loaded by a previous file
// Finalized - might be the case for some record types, but I
// don't know if this actual state value would be used for
// anything.
}
enum VarType { Unknown, None, Short, Int, Long, Float, String, Ignored }
enum SpellSchool : int
{
Alteration = 0,
Conjuration = 1,
Destruction = 2,
Illusion = 3,
Mysticism = 4,
Restoration = 5,
Length
}
enum Attribute : int
{
Strength = 0,
Intelligence = 1,
Willpower = 2,
Agility = 3,
Speed = 4,
Endurance = 5,
Personality = 6,
Luck = 7,
Length
}
enum SkillEnum : int
{
Block = 0,
Armorer = 1,
MediumArmor = 2,
HeavyArmor = 3,
BluntWeapon = 4,
LongBlade = 5,
Axe = 6,
Spear = 7,
Athletics = 8,
Enchant = 9,
Destruction = 10,
Alteration = 11,
Illusion = 12,
Conjuration = 13,
Mysticism = 14,
Restoration = 15,
Alchemy = 16,
Unarmored = 17,
Security = 18,
Sneak = 19,
Acrobatics = 20,
LightArmor = 21,
ShortBlade = 22,
Marksman = 23,
Mercantile = 24,
Speechcraft = 25,
HandToHand = 26,
Length
}
// Shared between SPEL (Spells), ALCH (Potions) and ENCH (Item
// enchantments) records
align(1) struct ENAMstruct
{
// Magical effect
short effectID; // ID of magic effect
// Which skills/attributes are affected (for restore/drain spells etc.)
byte skill, attribute; // -1 if N/A
// Other spell parameters
int range; // 0 - self, 1 - touch, 2 - target
int area, duration, magnMin, magnMax;
static assert(ENAMstruct.sizeof==24);
}
// Common stuff for all the load* structs
template LoadTT(T)
{
LoadState state;
char[] name, id;
MonsterObject *proto;
static MonsterClass mc;
void makeProto(char[] clsName = null)
{
// Set up a prototype object
if(mc is null)
{
// Use the template type name as the Monster class name if
// none is specified.
if(clsName == "")
{
clsName = typeid(T).toString;
// Remove the module name
int i = clsName.rfind('.');
if(i != -1)
clsName = clsName[i+1..$];
}
// All the game objects are in the 'game' package
clsName = "game." ~ clsName;
mc = vm.load(clsName);
}
proto = mc.createObject();
proto.setString8("id", id);
proto.setString8("name", name);
static if(is(typeof(data.weight) == float))
{
proto.setFloat("weight", data.weight);
proto.setInt("value", data.value);
}
static if(is(typeof(data.enchant)==int))
proto.setInt("enchant", data.enchant);
}
}
template LoadT() { mixin LoadTT!(typeof(*this)); }

@ -1,167 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (esmmain.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.esmmain;
public import esm.records;
import ogre.ogre;
/* This file is the main module for loading from ESM, ESP and ESS
files. It stores all the data in the appropriate data structures
for later referal. TODO: Put this in a class or whatever? Nah, we
definately only need one structure like this at any one
time. However, we have to deal with unloading and reloading it,
even though that should be the exceptional case (change of plugins,
etc), not the rule (loading a savegame should not alter the base
data set, I think, but it's to early do decide.)*/
// Load a set of esm and esp files. For now, we just traverse in the
// order given. Later, we should sort these into 'masters' and
// 'plugins', because esms are always supposed to be loaded
// first. TODO: I'm not sure if I should load all these in one
// function. Do we need to be able to respond to errors in each file?
// Nah, if anything fails, give a general error message, remove the
// file from the list and try again. We have to be able to get a list
// of which files depend upon which, though... this can be done before
// this function is called.
void loadTESFiles(char[][] files)
{
// Set up all the lists to hold our data
initializeLists();
foreach(char[] filename; files)
{
esFile.open(filename, esmRegion);
while(esFile.hasMoreRecs())
{
uint flags;
// Read record header
char[] recName = esFile.getRecName();
esFile.getRecHeader(flags);
if(flags & RecordFlags.Unknown)
esFile.fail(format("UNKNOWN record flags: %xh", flags));
loadRecord(recName);
}
// We have to loop through the lists and check for broken
// references at this point, and if all forward references were
// loaded. There might be other end-of-file things to do also.
endFiles();
}
esFile.close();
// Put all inventory items into one list
items.addList(appas, ItemType.Apparatus);
items.addList(lockpicks, ItemType.Pick);
items.addList(probes, ItemType.Probe);
items.addList(repairs, ItemType.Repair);
items.addList(lights, ItemType.Light);
items.addList(ingreds, ItemType.Ingredient);
items.addList(potions, ItemType.Potion);
items.addList(armors, ItemType.Armor);
items.addList(weapons, ItemType.Weapon);
items.addList(books, ItemType.Book);
items.addList(clothes, ItemType.Clothing);
items.addList(miscItems, ItemType.Misc);
items.addList(itemLists, ItemType.ItemList); // Leveled item lists
// Same with all actors
actors.addList(creatures, ItemType.Creature);
actors.addList(creatureLists, ItemType.CreatureList);
actors.addList(npcs, ItemType.NPC);
// Finally, add everything that might be looked up in a cell into
// one list
cellRefs.addList(items);
cellRefs.addList(actors);
cellRefs.addList(doors, ItemType.Door);
cellRefs.addList(activators, ItemType.Activator);
cellRefs.addList(statics, ItemType.Static);
cellRefs.addList(containers, ItemType.Container);
// Check that all references are resolved
items.endMerge();
actors.endMerge();
cellRefs.endMerge();
// Put all NPC dialogues into the hyperlink list
foreach(char[] id, ref Dialogue dl; dialogues.names)
hyperlinks.add(id, &dl);
// Finally, sort the hyperlink lists
hyperlinks.sort();
}
// Contains the small bits of information that we currently extract
// from savegames.
struct PlayerSaveInfo
{
char[] cellName;
char[] playerName;
Placement pos;
}
// Load a TES savegame file (.ess). Currently VERY limited, reads the
// player's cell name and position
PlayerSaveInfo importSavegame(char[] file)
{
PlayerSaveInfo pi;
esFile.open(file, esmRegion);
scope(exit) esFile.close();
if(esFile.getFileType != FileType.Ess)
throw new TES3FileException(file ~ " is not a savegame");
with(esFile.saveData)
{
pi.cellName = stripz(cell);
pi.playerName = stripz(player);
}
with(esFile)
{
while(hasMoreRecs())
{
if(isNextHRec("REFR"))
{
while(hasMoreSubs())
{
getSubName();
if(retSubName() == "DATA")
readHExact(&pi.pos, pi.pos.sizeof);
else
skipHSub();
}
}
else
skipHRecord();
}
}
return pi;
}

@ -1,783 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (filereader.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.filereader;
private:
import std.stdio;
import std.stream;
import std.string;
import util.regions;
import util.utfconvert;
import monster.util.string;
import core.resource;
import esm.listkeeper;
import esm.defs;
public:
/*
* Exception class for TES3File
*/
class TES3FileException: Exception
{
this(char[] msg) {super("Error reading TES3 file: " ~ msg);}
this() {this("Unknown error");}
}
// Some flags are in use that we don't know. But we don't really know
// any of them.
enum RecordFlags : uint
{
Flag6 = 0x20, // Eg. adventurers_v2.0.esp (only once per file?)
Persistent = 0x400,
Flag13 = 0x1000, // Eg. Astarsis_BR.esm (several times per file?)
Blocked = 0x2000,
Unknown = 0xffffffff - 0x3420
}
enum FileType
{
Unknown,
Esp, Plugin = Esp,
Esm, Master = Esm,
Ess, Savegame = Ess
}
// Special files
enum SpecialFile
{
Other,
Morrowind,
Tribunal,
Bloodmoon
}
enum Version { Unknown, v12, v13 }
// This struct should contain enough data to put a TES3File object
// back into a specific file position and state. We use it to save the
// "position" of objects in a file (eg. a cell), so we can return
// there later and continue where we stopped (eg. when we want to load
// that specific cell.)
struct TES3FileContext
{
char[] filename;
uint leftRec, leftSub;
ulong leftFile;
NAME recName, subName;
FileType type;
Version ver;
ulong filepos;
}
/**
* Instance used to read TES3 files. Since we will only be reading one
* file at a time, we might as well make one global instance.
*/
TES3File esFile;
/**
* This struct reads an Elder Scrolls 3 file (esp, esm or ess)
*
* Makes heavy use of private variables to represent current
* state.
*
* Relevant exceptions are
* TES3FileException - error interpreting file
* StreamFileException - file IO error
*/
struct TES3File
{
private:
BufferedFile file;// Input file
// These are only used by getRecHeader and getSubHeader for
// asserting the file's integrity.
ulong leftFile; // Number of unread bytes in file
uint leftRec; // Number of unread bytes in record
// This is used by sub-record readers for integrity checking.
uint leftSub; // Number of bytes in subrecord
// Name of current record and current sub-record.
NAME recName, subName;
char[] filename; // Filename
FileType type; // File type
Version ver; // File format version
char[] author; // File author (max 32 bytes (with null?))
char[] desc; // Description (max 256 bytes (ditto?))
uint records; // Number of records in the file (doesn't seem to be right?)
SpecialFile spf; // Is this a file we have to treat in a special way?
struct _mast
{
char[] name; // File name of an esm master for this file
ulong size; // The master file's size in bytes (used for
// version control)
}
// List of esm masters for this file. For savegames this list also
// contains all plugins.
_mast masters[];
// TES3.HEDR, file header struct
align(1) struct HEDRstruct
{
union
{
float ver; // File format version, 1.2 and 1.3 supported.
uint verHex; // 1.2 = 0x3f99999a, 1.3 = 0x3fa66666
}
int type; // 0=esp, 1=esm, 32=ess
NAME32 author; // Author's name
NAME256 desc; // File description blurb
uint records; // Number of records in file (?)
}
static assert(HEDRstruct.sizeof == 300);
// Which memory region to use for allocations.
RegionManager region;
public:
// A struct found in the headers of savegame files. Contains quick
// information to get us going, like the cell name and the player
// name.
struct _saveData
{
float[6] unknown;
char[64] cell; // Cell name
float unk2; // Unknown value
char[32] player; // Player name
}
static assert(_saveData.sizeof == 124);
_saveData saveData;
// Get file information
char[] getFilename() { return filename; }
ulong getFileSize() { return file.size; }
ulong getPosition() { return file.position; }
SpecialFile getSpecial() { return spf; }
char[] retSubName() { return subName; }
bool isVer12() { return ver == Version.v12;}
bool isVer13() { return ver == Version.v13;}
FileType getFileType() { return type; }
_mast[] getMasters() { return masters; }
uint getRecords() { return records; }
char[] getAuthor() { return author; }
RegionManager getRegion() { return region; }
// Store the current file state (position, file name, version, debug
// info). The info should be enough to get us back on track for
// reading from a file, without having to reread the header or any
// previous records.
void getContext(ref TES3FileContext c)
{
c.filename = filename;
c.leftFile = leftFile;
c.leftRec = leftRec;
c.leftSub = leftSub;
c.recName[] = recName;
c.subName[] = subName;
c.type = type;
c.ver = ver;
c.filepos = file.position;
}
// Opens the file if it is not already opened. A region manager has
// to be specified.
void restoreContext(TES3FileContext c, RegionManager r)
{
if(filename != c.filename)
openFile(c.filename, r);
file.seekSet(cast(long)c.filepos);
// File is now open, copy state information
filename = c.filename;
leftFile = c.leftFile;
leftRec = c.leftRec;
leftSub = c.leftSub;
recName[] = c.recName;
subName[] = c.subName;
type = c.type;
ver = c.ver;
}
// Open a new file and assign a region
private void openFile(char[] filename, RegionManager r)
{
close();
debug writefln("Opening file");
if(file is null) file = new BufferedFile(new File());
file.open(filename);
region = r;
}
void open(char[] filename, RegionManager r)
{
uint flags;
debug writefln("openFile(%s, %s)", filename, r);
openFile(filename, r);
if(iEnds(filename, "Morrowind.esm")) spf = SpecialFile.Morrowind;
else if(iEnds(filename, "Tribunal.esm")) spf = SpecialFile.Tribunal;
else if(iEnds(filename, "Bloodmoon.esm")) spf = SpecialFile.Bloodmoon;
else spf = SpecialFile.Other;
debug writefln("Reading header");
// Do NOT .dup this filename, since it is referenced outside the
// GC's reach and might be deleted.
this.filename = filename;
leftFile = file.size;
// First things first
if(getRecName() != "TES3")
fail("Not a valid Morrowind file");
// Record header
getRecHeader(flags);
if(flags)
writefln("WARNING: Header flags are non-zero");
// Read and analyse the header data
HEDRstruct hedr;
readHNExact(&hedr, hedr.sizeof, "HEDR");
// The float hedr.ver signifies the file format version. It can
// take on these two values:
// 0x3f99999a = 1.2
// 0x3fa66666 = 1.3
if( hedr.verHex == 0x3f99999a )
ver = Version.v12;
else if( hedr.verHex == 0x3fa66666 )
ver = Version.v13;
else
{
ver = Version.Unknown;
writefln("WARNING: Unknown version: ", hedr.ver);
writefln(" Hex: %X h", *(cast(uint*)&hedr.ver));
}
switch(hedr.type)
{
case 0: type = FileType.Esp; break;
case 1: type = FileType.Esm; break;
case 32: type = FileType.Ess; break;
default:
type = FileType.Unknown;
writefln("WARNING: Unknown file type: ", hedr.type);
}
author = region.copy(stripz(hedr.author));
desc = region.copy(stripz(hedr.desc));
records = hedr.records;
masters = null;
// Reads a MAST and a DATA fields
while(isNextSub("MAST"))
{
_mast ma;
// MAST entry - master file name
ma.name = getHString();
// DATA entry - master file size
ma.size = getHNUlong("DATA");
// Add to the master list!
masters ~= ma;
}
if(type == FileType.Savegame)
{
// Savegame-related data
// Cell name, player name and player position
readHNExact(&saveData, 124, "GMDT");
// Contains eg. 0xff0000, 0xff00, 0xff, 0x0, 0x20. No idea.
getSubNameIs("SCRD");
skipHSubSize(20);
// Screenshot. Fits with 128x128x4 bytes
getSubNameIs("SCRS");
skipHSubSize(65536);
}
}
// Close the file. We do not clear any object data at this point.
void close()
{
debug writefln("close()");
if(file !is null)
file.close();
leftFile = leftRec = leftSub = 0;
debug writefln("Clearing strings");
recName[] = '\0';
subName[] = '\0';
// This tells restoreContext() that we have to reopen the file
filename = null;
debug writefln("exit close()");
}
/*
* Error reporting
*/
void fail(char[] msg)
{
throw new TES3FileException
(msg ~ "\nFile: " ~ filename ~ "\nRecord name: " ~ recName
~ "\nSubrecord name: " ~ subName);
}
/************************************************************************
*
* Highest level readers, reads a name and looks it up in the given
* list.
*
************************************************************************/
// This should be more than big enough for references.
private char lookupBuffer[200];
// Get a temporary string. This is faster and more memory efficient
// that the other string functions (because it is allocation free),
// but the returned string is only valid until tmpHString() is
// called again.
char[] tmpHString()
{
getSubHeader();
assert(leftSub <= lookupBuffer.length, "lookupBuffer wasn't large enough");
// Use this to test the difference in memory consumption.
return getString(lookupBuffer[0..leftSub]);
}
// These are used for file lookups
MeshIndex getMesh()
{ getSubNameIs("MODL"); return resources.lookupMesh(tmpHString()); }
SoundIndex getSound()
{ getSubNameIs("FNAM"); return resources.lookupSound(tmpHString()); }
IconIndex getIcon(char[] s = "ITEX")
{ getSubNameIs(s); return resources.lookupIcon(tmpHString()); }
TextureIndex getTexture()
{ getSubNameIs("DATA"); return resources.lookupTexture(tmpHString()); }
// The getO* functions read optional records. If they are not
// present, return null.
MeshIndex getOMesh()
{ return isNextSub("MODL") ? resources.lookupMesh(tmpHString()) : MeshIndex.init; }
/*
SoundIndex getOSound()
{ return isNextSub("FNAM") ? resources.lookupSound(tmpHString()) : SoundIndex.init; }
*/
IconIndex getOIcon()
{ return isNextSub("ITEX") ? resources.lookupIcon(tmpHString()) : IconIndex.init; }
TextureIndex getOTexture(char[] s="TNAM")
{ return isNextSub(s) ? resources.lookupTexture(tmpHString()) : TextureIndex.init; }
// Reference with name s
template getHNPtr(Type)
{
Type* getHNPtr(char[] s, ListKeeper list)
{ getSubNameIs(s); return cast(Type*) list.lookup(tmpHString()); }
}
// Reference, only get header
template getHPtr(Type)
{
Type* getHPtr(ListKeeper list)
{ return cast(Type*) list.lookup(tmpHString()); }
}
// Optional reference with name s
template getHNOPtr(Type)
{
Type* getHNOPtr(char[] s, ListKeeper list)
{ return isNextSub(s) ? cast(Type*)list.lookup(tmpHString()) : null; }
}
/************************************************************************
*
* Somewhat high level reading methods. Knows about headers and
* leftFile/leftRec/leftSub.
*
************************************************************************/
// "Automatic" versions. Sets and returns recName and subName and
// updates leftFile/leftRec.
char[] getRecName()
{
if(!hasMoreRecs())
fail("No more records, getRecName() failed");
getName(recName);
leftFile-= 4;
return recName;
}
// This is specially optimized for LoadINFO
bool isEmptyOrGetName()
{
if(leftRec)
{
file.readBlock(subName.ptr, 4);
leftRec -= 4;
return false;
}
return true;
}
// I've tried to optimize this slightly, since it gets called a LOT.
void getSubName()
{
if(leftRec <= 0)
fail("No more sub-records, getSubName() failed");
// Don't bother with error checking, we will catch an EOF upon
// reading the subrecord data anyway.
file.readBlock(subName.ptr, 4);
leftRec -= 4;
}
// We often expect a certain subrecord type, this makes it easy to
// check.
void getSubNameIs(char[] s)
{
getSubName();
if( subName != s )
fail("Expected subrecord "~s~" but got "~subName);
}
// Checks if the next sub-record is called s. If it is, run
// getSubName, if not, return false.
bool isNextSub(char[] s)
{
if(!leftRec) return false;
getName(subName);
if(subName != s)
{
file.seekCur(-4);
return false;
}
leftRec -= 4;
//getSubName();
return true;
}
// Same as isNextSub, only it works on records instead of
// sub-records. It also loads the record header.
bool isNextHRec(char[] s)
{
if(!leftFile) return false;
getName(recName);
if(recName != s)
{
file.seekCur(-4);
return false;
}
leftFile -= 4;
uint flags;
getRecHeader(flags);
return true;
}
bool hasMoreSubs() { return leftRec > 0; }
bool hasMoreRecs() { return leftFile > 0; }
// Remaining size of current record
uint getRecLeft() { return leftRec; }
// Size of current sub record
uint getSubSize() { return leftSub; }
// Skip the rest of this record. Assumes the name and header have
// already been read
void skipRecord()
{
file.seekCur(leftRec);
leftRec = 0;
}
// Skip an entire record
void skipHRecord()
{
if(!leftFile) return;
uint flags;
getRecName();
getRecHeader(flags);
skipRecord();
}
// Skip current sub record and return size
uint skipHSub()
{
getSubHeader();
file.seekCur(leftSub);
return leftSub;
}
// Skip sub record and check it's size
void skipHSubSize(uint size)
{
getSubHeader();
if(leftSub != size)
fail(format("Size mismatch: got %d, wanted %d", leftSub, size));
file.seekCur(leftSub);
}
// Check the name and size before skipping
void skipHNSub(char[] name, uint size)
{
getSubNameIs(name);
skipHSubSize(size);
}
// These read an entire sub-record, including the header. They also
// adjust and check leftSub and leftRecord variables through calling
// getSubHeader().
void readHExact(void * p, uint size)
{
getSubHeader();
if(leftSub != size)
fail(format("Size mismatch: got %d, wanted %d", leftSub, size));
readExact(p, leftSub);
}
template TgetHType(T)
{ T TgetHType() { T t; readHExact(&t, t.sizeof); return t;} }
// To make these easier to use (and to further distinguish them from
// the above "raw" versions), these return their value instead of
// using an ref argument.
alias TgetHType!(uint) getHUint;
alias TgetHType!(int) getHInt;
alias TgetHType!(float) getHFloat;
alias TgetHType!(ulong) getHUlong;
alias TgetHType!(byte) getHByte;
// Reads a string sub-record, including header
char[] getHString()
{
getSubHeader();
// Hack to make MultiMark.esp load. Zero-length strings do not
// occur in any of the official mods, but MultiMark makes use of
// them. For some reason, they break the rules, and contain a
// byte (value 0) even if the header says there is no data. If
// Morrowind accepts it, so should we.
if(leftSub == 0)
{
// Skip the following zero byte
leftRec--;
assert(file.getc() == 0);
// TODO: Report this by setting a flag or something?
return null;
}
return getString(region.getString(leftSub));
}
// Other quick aliases (this is starting to get messy)
// Get string sub record string with name s
char[] getHNString(char[] s)
{ getSubNameIs(s); return getHString(); }
// Get optional sub record string with name s
char[] getHNOString(char[] s)
{ return isNextSub(s) ? getHString() : null; }
template TgetHNType(T)
{ T TgetHNType(char[] s) { T t; readHNExact(&t, t.sizeof, s); return t;} }
template TgetHNOType(T)
{
T TgetHNOType(char[] s, T def)
{
if(isNextSub(s))
{
T t;
readHExact(&t, t.sizeof);
return t;
}
else return def;
}
}
alias TgetHNType!(uint) getHNUint;
alias TgetHNType!(int) getHNInt;
alias TgetHNType!(float) getHNFloat;
alias TgetHNType!(ulong) getHNUlong;
alias TgetHNType!(byte) getHNByte;
alias TgetHNType!(short) getHNShort;
alias TgetHNType!(byte) getHNByte;
alias TgetHNOType!(float) getHNOFloat;
alias TgetHNOType!(int) getHNOInt;
alias TgetHNOType!(byte) getHNOByte;
void readHNExact(void* p, uint size, char[] s)
{ getSubNameIs(s); readHExact(p,size); }
// Record header
// This updates the leftFile variable BEYOND the data that follows
// the header, ie beyond the entire record. You are supposed to use
// the leftRec variable when reading record data.
void getRecHeader(out uint flags)
{
// General error checking
if(leftFile < 12)
fail("End of file while reading record header");
if(leftRec)
fail(format("Previous record contains %d unread bytes", leftRec));
getUint(leftRec);
getUint(flags);// This header entry is always zero
assert(flags == 0);
getUint(flags);
leftFile -= 12;
// Check that sizes add up
if(leftFile < leftRec)
fail(format(leftFile, " bytes left in file, but next record contains ",
leftRec," bytes"));
// Adjust number of bytes left in file
leftFile -= leftRec;
}
// Sub-record head
// This updates leftRec beyond the current sub-record as
// well. leftSub contains size of current sub-record.
void getSubHeader()
{
if(leftRec < 4)
fail("End of record while reading sub-record header");
if(file.readBlock(&leftSub, 4) != 4)
fail("getSubHeader could not read header length");
leftRec -= 4;
// Adjust number of record bytes left
leftRec -= leftSub;
// Check that sizes add up
if(leftRec < 0)
fail(format(leftRec+leftSub,
" bytes left in record, but next sub-record contains ",
leftSub," bytes"));
}
void getSubHeaderIs(uint size)
{
getSubHeader();
if(leftSub != size)
fail(format("Expected header size to be ", size, ", not ", leftSub));
}
/*************************************************************************
*
* Low level reading methods
*
*************************************************************************/
/// Raw data of any size
void readExact(void *buf, uint size)
{
assert(size != 0);
file.readExact(buf,size);
}
// One byte
void getByte(out byte b) { file.read(b); }
void getUByte(out ubyte b) { file.read(b); }
// Two bytes
void getUShort(out ushort s) { file.read(s); }
// Four bytes
void getUint(out uint u) { file.read(u); }
void getInt(out int i) { file.read(i); }
void getFloat(out float f) { file.read(f); }
// Eight bytes
void getUlong(out ulong l) { file.read(l); }
// Get a record or subrecord name, four bytes
void getName(NAME name)
{
file.readBlock(name.ptr, 4);
/*
if(file.readBlock(name.ptr, 4) != 4)
fail("getName() could not find more data");
*/
}
// Fill buffer of predefined length. If actual string is shorter
// (ie. null terminated), the buffer length is set
// accordingly. Chopped string is returned. All strings pass through
// this function, so any character encoding conversions should
// happen here.
char[] getString(char[] str)
{
if(str.length != file.readBlock(str.ptr,str.length))
fail("getString() could not find enough data in stream");
str = stripz(str);
makeUTF8(str); // TODO: A hack. Will replace non-utf characters
// with question marks. This is neither a very
// desirable result nor a very optimized
// implementation of it.
return str;
}
// Use this to allocate and read strings of predefined length
char[] getString(int l)
{
char[] str = region.getString(l);
return getString(str);
}
}

@ -1,47 +0,0 @@
module esm.imports;
/* This is a file that imports common modules used by the load*.d
record loaders. It is really a cut down version of what used to be
the start of records.d.
This file MUST NOT import records.d - directly or indirectly -
because that will trigger a nice three page long list of template
forwarding errors from the compiler.
What happens is that when DMD/GDC compiles one of the load* files,
it is forced to read records.d first (since it is an imported
module) - but then it sees a template that referes to a struct in
the current load* file, before that struct is defined. Curriously
enough, DMD has no problems when you specify all the source files
on the command line simultaneously. This trick doesn't work with
GDC though, and DSSS doesn't use it either.
This file was created to work around this compiler bug.
*/
public
{
import esm.defs;
import esm.filereader;
import esm.listkeeper;
import core.resource;
import core.memory;
import util.regions;
import monster.util.aa;
import std.stdio;
import std.string;
alias RegionBuffer!(ENAMstruct) EffectList;
// Records that are cross referenced often
import esm.loadscpt;
import esm.loadsoun;
import esm.loadspel;
import esm.loadench;
import monster.monster;
}

@ -1,330 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (listkeeper.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.listkeeper;
import monster.util.aa;
import core.memory;
import esm.filereader;
import esm.defs;
import std.stdio;
// Item types, used in the lookup table for inventory items, creature
// lists and leveled lists. We also use it for all types of references
// that can exist in cells.
enum ItemType
{
// Items
None = 0, Potion, Apparatus, Armor, Weapon, Book, Clothing,
Light, Ingredient, Pick, Probe, Repair, Misc, ItemList,
// Used for creature lists
Creature, CreatureList, NPC,
// Other cell references
Door, Activator, Static, Container//, SoundGen
}
abstract class ListKeeper
{
int listIndex;
new(uint size)
{
return esmRegion.allocate(size).ptr;
}
delete(void *p) { assert(0); }
this()
{
// Store our index for later use
listIndex = recordLists.length;
// Add the class to the global list
recordLists ~= this;
}
// Load a record from a master or plugin file
void load();
// Looks up a reference. If it does not exist it is assumed to be a
// forward reference within a file, and is inserted.
void* lookup(char[] s);
// Tell the loader that current file has ended, so it can do things
// like check that all referenced objects have been loaded.
void endFile();
// Number of inserted elements
uint length();
void addToList(ref ItemBaseList l, ItemType t) { assert(0); }
}
ListKeeper recordLists[];
// Keep the list of Type structures for records where the first
// subrecord is an id string called NAME. This id is used for
// lookup. Although almost all lookups match in case, there are a few
// sounds that don't, so we treat these id lookups as generally case
// insensitive. This hasn't posed any problems so far.
class ListID(Type) : ListKeeper
{
HashTable!(char[], Type, ESMRegionAlloc, CITextHash) names;
this(uint size)
{
names = names.init;
if(size) names.rehash(size);
}
// Reads the id for this header. Override if the id is not simply
// getHNString("NAME")
char[] getID()
{
return esFile.getHNString("NAME");
}
// Load a record from a master of plugin file
void load()
{
assert(esFile.getFileType == FileType.Esm ||
esFile.getFileType == FileType.Esp);
// Get the identifier of this record
char[] id = getID();
// Get pointer to a new or existing object.
Type *p;
if(names.insertEdit(id, p))
// A new item was inserted
{
p.state = LoadState.Unloaded;
p.id = id;
p.load();
p.state = LoadState.Loaded;
}
else
// Item already existed, either from a previous file or as a
// forward reference from this file. Load on top of it. The
// LoadState tells the struct whether it contains loaded data.
{
/*
if(p.state == LoadState.Loaded)
// Make a special case for this, perhaps, or just ignore it.
writefln("WARNING: Duplicate record in file %s: '%s'",
esFile.getFilename(), id);
*/
assert(icmp(p.id, id) == 0);
p.load();
p.state = LoadState.Loaded;
}
}
// Looks up a reference. If it does not exist it is assumed to be a
// forward reference within a file, and is inserted.
void* lookup(char[] id)
{
if(!id.length) return null; // Empty reference
Type *p = names.lookup(id);
// Is the value in the list?
if(!p)
// No, assume it is a forward reference.
{
// Since the lookup name is stored in an internal buffer in
// esFile, we have to copy it.
id = esmRegion.copy(id);
// To avoid copying the string on every lookup, we have to
// insert in a separate step. But a double lookup isn't
// really THAT expensive. Besides, my tests show that this
// is used in less than 10% of the cases.
names.insertEdit(id, p);
p.id = id;
p.state = LoadState.Unloaded;
}
return cast(void*)p;
}
// Check that all referenced objects are actually loaded.
void endFile()
in
{
// We can skip this in release builds
names.validate();
}
body
{
foreach(char[] id, ref Type t; names)
// Current file is now counted as done
if(t.state == LoadState.Loaded) t.state = LoadState.Previous;
else if(t.state == LoadState.Unloaded)
//writefln("WARNING: Unloaded reference " ~ id);
esFile.fail("Unloaded reference " ~ id);
}
// Number of inserted elements
uint length() {return names.length;}
// Add the names in this list to an ItemList
void addToList(ref ItemBaseList l, ItemType t)
{
foreach(char[] id, ref Type s; names)
l.insert(id, &s, t);
}
}
// A pointer to an item
struct ItemBase
{
ItemType type;
void *p;
}
struct ItemBaseList
{
HashTable!(char[],ItemBase,ESMRegionAlloc) list;
void addList(ItemBaseList l)
{
foreach(char[] id, ItemBase b; l.list)
insert(id, b.p, b.type);
}
void addList(ListKeeper source, ItemType type)
{
source.addToList(*this, type);
}
void insert(char[] id, void* p, ItemType type)
{
ItemBase *b;
if(!list.insertEdit(id, b))
{
//writefln("Replacing item ", id);
if(b.type != ItemType.None)
esFile.fail("Replaced valid item: " ~ id);
}
//else writefln("Inserting new item ", id);
b.type = type;
b.p = p;
}
// Called at the end to check that all referenced items have been resolved
void endMerge()
{
foreach(char[] id, ref ItemBase t; list)
// Current file is now counted as done
if(t.type == ItemType.None)
// TODO: Don't use esFile.fail for this
esFile.fail("ItemBaseList: Unresolved forward reference: " ~ id);
}
// Look up an item, return a pointer to the ItemBase representing
// it. If it does not exist, it is inserted.
ItemBase *lookup(char[] id)
{
if(!id.length) return null; // Empty reference
ItemBase *b = list.lookup(id);
// Is the value in the list?
if(!b)
// No, assume it is a forward reference.
{
// Since the lookup name is stored in an internal buffer in
// esFile, we have to copy it.
id = esmRegion.copy(id);
// To avoid copying the string on every lookup, we have to
// insert in a separate step. But a double lookup isn't
// really THAT expensive.
list.insertEdit(id, b);
b.p = null;
b.type = ItemType.None;
}
return b;
}
}
// An item. Contains a reference to an ItemBase, which again is a
// reference to an item. The ItemBase might change after we have
// looked it up (for forward references), so we have to use a pointer.
struct Item
{
ItemBase *i;
void* getPtr(ItemType type)
{
if(i != null && i.type == type) return i.p;
return null;
}
T* getType(T, ItemType Type)()
{
return cast(T*)getPtr(Type);
}
}
struct ItemList
{
private:
ItemBaseList list;
public:
void addList(ItemList l)
{ list.addList(l.list); }
void addList(ListKeeper source, ItemType type)
{ list.addList(source, type); }
Item lookup(char[] id)
{
Item i;
i.i = list.lookup(id);
return i;
}
void endMerge()
{ list.endMerge(); }
void endFile()
in { list.list.validate(); }
body {}
void rehash(uint size)
{ list.list.rehash(size); }
uint length() { return list.list.length(); }
}
// Aggregate lists, made by concatinating several other lists.
ItemList items; // All inventory items, including leveled item lists
ItemList actors; // All actors, ie. NPCs, creatures and leveled lists
ItemList cellRefs; // All things that are referenced from cells

@ -1,189 +0,0 @@
/*
This file contains some leftovers which have not yet been ported to
C++.
*/
align(1) struct AMBIStruct
{
Color ambient, sunlight, fog;
float fogDensity;
static assert(AMBIStruct.sizeof == 16);
}
int max(int x, int y)
{ return x>=y?x:y; }
struct ExtCellHash
{
// This is a pretty good hash, gives no collisions for all of
// Morrowind.esm when the table size is 2048, and it gives very few
// collisions overall. Not that it matters that much.
static uint hash(uint val)
{
uint res = cast(ushort)val;
res += *(cast(ushort*)&val+1)*41;
return res;
}
static bool isEqual(uint a, uint b) { return a==b; }
}
class CellList : ListKeeper
{
// Again, these are here to avoid DMD template bugs
alias _aaNode!(char[], InteriorCell) _unused1;
alias _aaNode!(uint, ExteriorCell) _unused2;
HashTable!(char[], InteriorCell, ESMRegionAlloc) in_cells;
HashTable!(uint, ExteriorCell, ESMRegionAlloc, ExtCellHash) ex_cells;
// Store the maximum x or y coordinate (in absolute value). This is
// used in the landscape pregen process.
int maxXY;
this()
{
in_cells = in_cells.init;
in_cells.rehash(1600);
ex_cells = ex_cells.init;
ex_cells.rehash(1800);
}
align(1) struct DATAstruct
{
CellFlags flags;
int gridX, gridY;
static assert(DATAstruct.sizeof==12);
}
DATAstruct data;
// Look up an interior cell, throws an error if not found (might
// change later)
InteriorCell *getInt(char[] s)
{
return in_cells.getPtr(s);
}
// Exterior cell, same as above
ExteriorCell *getExt(int x, int y)
{
return ex_cells.getPtr(compound(x,y));
}
// Check whether we have a given exterior cell
bool hasExt(int x, int y)
{
return ex_cells.inList(compound(x,y));
}
void *lookup(char[] s)
{ assert(0); }
void endFile()
out
{
in_cells.validate();
ex_cells.validate();
}
body
{
foreach(id, ref c; in_cells)
{
if(c.state == LoadState.Loaded) c.state = LoadState.Previous;
// We never forward reference cells!
assert(c.state != LoadState.Unloaded);
}
foreach(id, ref c; ex_cells)
{
if(c.state == LoadState.Loaded) c.state = LoadState.Previous;
// We never forward reference cells!
assert(c.state != LoadState.Unloaded);
}
}
uint length() { return numInt() + numExt(); }
uint numInt() { return in_cells.length; }
uint numExt() { return ex_cells.length; }
// Turn an exterior cell grid position into a unique number
static uint compound(int gridX, int gridY)
{
return cast(ushort)gridX + ((cast(ushort)gridY)<<16);
}
static void decompound(uint val, out int gridX, out int gridY)
{
gridX = cast(short)(val&0xffff);
gridY = cast(int)(val&0xffff0000) >> 16;
}
void load()
{with(esFile){
char[] id = getHNString("NAME");
// Just ignore this, don't know what it does. I assume it
// deletes the cell, but we can't handle that yet.
if(isNextSub("DELE")) getHInt();
readHNExact(&data, data.sizeof, "DATA");
if(data.flags & CellFlags.Interior)
{
InteriorCell *p;
if(in_cells.insertEdit(id, p))
// New item was inserted
{
p.state = LoadState.Unloaded;
p.id = id;
p.flags = data.flags;
p.load();
p.state = LoadState.Loaded;
}
else
// Overloading an existing cell
{
if(p.state != LoadState.Previous)
fail("Duplicate interior cell " ~ id);
assert(id == p.id);
p.load();
p.state = LoadState.Loaded;
}
}
else // Exterior cell
{
uint key = compound(data.gridX, data.gridY);
ExteriorCell *p;
if(ex_cells.insertEdit(key, p))
// New cell
{
p.state = LoadState.Unloaded;
p.name = id;
p.flags = data.flags;
p.gridX = data.gridX;
p.gridY = data.gridY;
p.load();
p.state = LoadState.Loaded;
int mx = max(abs(p.gridX), abs(p.gridY));
maxXY = max(maxXY, mx);
}
else
{
if(p.state != LoadState.Previous)
fail(format("Duplicate exterior cell %d %d",
data.gridX, data.gridY));
assert(p.gridX == data.gridX);
assert(p.gridY == data.gridY);
p.load();
p.state = LoadState.Loaded;
}
}
}}
}
CellList cells;

@ -1,289 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loaddial.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loaddial;
import esm.imports;
import esm.loadinfo;
/*
* Dialogue topic and journal entries. The acutal data is contained in
* the following INFO records.
*/
// Keep a list of possible hyper links. This list is used when parsing
// text and finding references to topic names. Eg. if a character says
// "Would you like to join the mages guild?", we must be able to pick
// out the phrase "join the mages guild?" and make a hyperlink of
// it. Each link is indexed by their first word. The structure
// contains the rest of the phrase, so the phrase above would be
// indexed by "join" and contain the string "the mages guild?", for
// quick comparison with the text are currently parsing. It also
// contains a pointer to the corresponding dialogue struct. The lists
// are sorted by descending string length, in order to match the
// longest possible term first.
struct Hyperlink
{
Dialogue *ptr;
char[] rest;
// Returns a < b if a.length > b.length.
int opCmp(Hyperlink *h) {return h.rest.length - rest.length;}
}
alias RegionBuffer!(Hyperlink) HyperlinkArray;
// This is much nicer now that we use our own AA.
struct HyperlinkList
{
// Make a case insensitive hash table of Hyperlink arrays.
HashTable!(char[], HyperlinkArray, ESMRegionAlloc, CITextHash) list;
void add(char[] topic, Dialogue* ptr)
{
// Only add dialogues
if(ptr.type != Dialogue.Type.Topic) return;
Hyperlink l;
l.ptr = ptr;
l.rest = topic;
// Use the first word as the index
topic = nextWord(l.rest);
// Insert a new array, or get an already existing one
HyperlinkArray ha = list.get(topic,
// Create a new array
delegate void (ref HyperlinkArray a)
{ a = esmRegion.getBuffer!(Hyperlink)(0,1); }
);
// Finally, add it to the list
ha ~= l;
}
Hyperlink[] getList(char[] word)
{
HyperlinkArray p;
if(list.inList(word, p)) return p.array();
return null;
}
void rehash(uint size)
{
list.rehash(size);
}
// We wouldn't need this if we only dealt with one file, since the
// topics are already sorted in Morrowind.esm. However, other files
// might add items out of order later, so we have to sort it. To
// understand why this is needed, consider the following example:
//
// Morrowind.esm contains the topic 'join us'. When ever the text
// ".. join us blahblah ..." is encountered, this match is
// found. However, if a plugin adds the topic 'join us today', we
// have to place this _before_ 'join us' in the list, or else it
// will never be matched.
void sort()
{
foreach(char[] s, HyperlinkArray l; list)
{
l.array().sort;
/*
writefln("%s: ", s, l.length);
foreach(Hyperlink h; l.array())
writefln(" %s (%s)", h.rest, h.ptr.id);
*/
}
}
}
// List of dialogue hyperlinks
HyperlinkList hyperlinks;
struct Dialogue
{
enum Type
{
Topic = 0,
Voice = 1,
Greeting = 2,
Persuasion = 3,
Journal = 4,
Deleted = -1
}
//Type type;
DialogueType type;
DialInfoList infoList;
char[] id; // This is the 'dialogue topic' that the user actually
// sees.
LoadState state;
void load()
{with(esFile){
getSubNameIs("DATA");
getSubHeader();
int si = getSubSize();
if(si == 1)
{
byte b;
getByte(b);
DialogueType t = cast(DialogueType)b;
// Meet the new type, same as the old type
if(t != this.type && state == LoadState.Previous)
fail("Type changed in dialogue " ~ id);
this.type = t;
}
else if(si == 4)
{
// These are just markers, their values are not used.
int i;
getInt(i);
//writefln("In file %s:", getFilename());
//writefln(" WARNING: DIAL.DATA was size 4 and contains: ", i);
i = getHNInt("DELE");
//writefln(" DELE contains ", i);
this.type = Type.Deleted;
}
else fail("Unknown sub record size " ~ toString(si));
infoList.state = state;
while(isNextHRec("INFO"))
infoList.load(this.type);
//skipRecord();
}}
}
typedef Dialogue.Type DialogueType;
/+
// I don't remember when I commented out this code or what state
// it is in. Probably highly experimental.
// --------------
// Loop through the info blocks in this dialogue, and update the
// master as necessary.
// TODO: Note that the dialogue system in Morrowind isn't very
// robust. If several mods insert dialogues at exactly the same
// place, the mods loaded last might overwrite the previous mods,
// completely removing the previous entry even if the two entries
// do not have the same id. This is because a change also
// overwrites the previous and the next entry, in order to update
// their "previous" and "next" fields. Furthermore, we might put
// ourselves in a situation where the forward and backward chains
// do not match, or in a situation where we update a deleted
// info. For now I do nothing about it, but I will have to develop
// a "conflict manager" later on. It SHOULD be possible to merge
// these info lists automatically in most cases, but it
// complicates the code.
// Whoa, seems we have a case study already with just tribunal and
// bloodmoon loaded! See comments below.
foreach(char[] id, ref DialInfoLoad m; mod.infoList)
{
// Remove the response if it is marked as deleted.
if(m.deleted)
{
if((id in master.infoList) == null)
writefln("Cannot delete info %s, does not exist", id);
else master.infoList.remove(id);
}
else
// Just plain copy it in.
master.infoList[id] = m;
}
}
// Here we have to fix inconsistencies. A good case example is the
// dialogue "Apelles Matius" in trib/blood. Trib creates a
// dialogue of a few items, bloodmoon adds another. But since the
// two are independent, the list in bloodmoon does not change the
// one in trib but rather creates a new one. In other words, we
// will have to deal with the possibility of several "independent"
// lists within each topic. We can do this by looking for several
// start points (ie. infos with prev="") and just latch them onto
// each other. I'm not sure it gives the correct result,
// though. For example, which list comes first would be rather
// arbitrarily decided by the order we traverse the infoList AA. I
// will just have to assume that they truly are "independent".
// There still seems to be a problem though. Bloodmoon overwrites
// some stuff added by Tribunal, see "Boots of the Apostle" for an
// example. Looks like the editor handles it just fine... We need
// to make sure that all the items in our AA are put into the
// list, and in the right place too. We obviously cannot fully
// trust the 'next' and 'prev' fields, but they are the only
// guidance we have. Deal with it later!
// At this point we assume "master" to contain the final dialogue
// list, so at this point we can set it in stone.
infoList.length = master.infoList.length;
// Find the first entry
DialInfoLoad* starts[]; // starting points for linked lists
DialInfoLoad *current;
foreach(char[] id, ref DialInfoLoad l; master.infoList)
if(l.prev == "") starts ~= &l;
foreach(int num, ref DialInfo m; infoList)
{
if(current == null)
{
if(starts.length == 0)
{
writefln("Error: No starting points!");
infoList.length = num;
break;
}
// Pick the next starting point
current = starts[0];
starts = starts[1..$];
}
m.copy(*current, this);
if((*current).next == "")
current = null;
else
{
current = (*current).next in master.infoList;
if(current == null)
{
writefln("Error in dialouge info lookup!");
break;
}
}
}
if(infoList.length != master.infoList.length)
writefln("Dialogue list lengths do not match, %d != %d",
infoList.length, master.infoList.length);
}
}
+/

@ -1,219 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (records.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.records;
public
{
import monster.util.aa;
import util.regions;
import core.memory;
import core.resource;
import esm.filereader;
import esm.defs;
import esm.listkeeper;
import std.stdio; // Remove later
}
public import
esm.loadacti, esm.loaddoor, esm.loadglob, esm.loadscpt, esm.loadsoun, esm.loadgmst,
esm.loadfact, esm.loadstat, esm.loadspel, esm.loadalch, esm.loadappa, esm.loadarmo,
esm.loadbody, esm.loadench, esm.loadbook, esm.loadbsgn, esm.loadltex, esm.loadmgef,
esm.loadweap, esm.loadlocks,esm.loadcell, esm.loadregn, esm.loadligh, esm.loadskil,
esm.loadsndg, esm.loadrace, esm.loadmisc, esm.loadclot, esm.loadingr, esm.loadclas,
esm.loadcont, esm.loadcrea, esm.loadnpc, esm.loaddial, esm.loadinfo, esm.loadsscr,
esm.loadlevlist;
void loadRecord(char[] recName)
{
switch(recName)
{
case "ACTI": activators.load(); break;
case "DOOR": doors.load(); break;
case "GLOB": globals.load(); break;
case "SCPT": scripts.load(); break;
case "SOUN": sounds.load(); break;
case "GMST": gameSettings.load(); break;
case "FACT": factions.load(); break;
case "STAT": statics.load(); break;
case "SPEL": spells.load(); break;
case "ALCH": potions.load(); break;
case "APPA": appas.load(); break;
case "ARMO": armors.load(); break;
case "BODY": bodyParts.load(); break;
case "ENCH": enchants.load(); break;
case "BOOK": books.load(); break;
case "BSGN": birthSigns.load(); break;
case "LTEX": landTextures.load(); break;
case "MGEF": effects.load(); break;
case "WEAP": weapons.load(); break;
case "REPA": repairs.load(); break;
case "LOCK": lockpicks.load(); break;
case "PROB": probes.load(); break;
case "CELL": cells.load(); break;
case "REGN": regions.load(); break;
case "LIGH": lights.load(); break;
case "SKIL": skills.load(); break;
case "SNDG": soundGens.load(); break;
case "RACE": races.load(); break;
case "MISC": miscItems.load(); break;
case "CLOT": clothes.load(); break;
case "INGR": ingreds.load(); break;
case "CLAS": classes.load(); break;
case "CONT": containers.load(); break;
case "CREA": creatures.load(); break;
case "LEVI": itemLists.load(); break;
case "LEVC": creatureLists.load(); break;
case "NPC_": npcs.load(); break;
case "DIAL": dialogues.load(); break;
case "SSCR": startScripts.load(); break;
/*
// Tribunal / Bloodmoon only
case "SSCR": loadSSCR.load(); break;
*/
// For save games:
// case "NPCC": loadNPCC;
// case "CNTC": loadCNTC;
// case "CREC": loadCREC;
// These should never be looked up
case "TES3":
case "INFO":
case "LAND":
case "PGRD":
esFile.fail("Misplaced record " ~ recName);
default:
esFile.fail("Unknown record type " ~ recName);
}
//*/
}
// Um, this has to be in this file for some reason.
ListID!(Dialogue) dialogues;
struct ItemT
{
Item item;
ItemBase *i;
T* getType(T, ItemType Type)()
{
return item.getType!(T, Type)();
}
alias getType!(Potion, ItemType.Potion) getPotion;
alias getType!(Apparatus, ItemType.Apparatus) getApparatus;
alias getType!(Armor, ItemType.Armor) getArmor;
alias getType!(Weapon, ItemType.Weapon) getWeapon;
alias getType!(Book, ItemType.Book) getBook;
alias getType!(Clothing, ItemType.Clothing) getClothing;
alias getType!(Light, ItemType.Light) getLight;
alias getType!(Ingredient, ItemType.Ingredient) getIngredient;
alias getType!(Tool, ItemType.Pick) getPick;
alias getType!(Tool, ItemType.Probe) getProbe;
alias getType!(Tool, ItemType.Repair) getRepair;
alias getType!(Misc, ItemType.Misc) getMisc;
alias getType!(LeveledItems, ItemType.ItemList) getItemList;
alias getType!(Creature, ItemType.Creature) getCreature;
alias getType!(LeveledCreatures, ItemType.CreatureList) getCreatureList;
alias getType!(NPC, ItemType.NPC) getNPC;
alias getType!(Door, ItemType.Door) getDoor;
alias getType!(Activator, ItemType.Activator) getActivator;
alias getType!(Static, ItemType.Static) getStatic;
alias getType!(Container, ItemType.Container) getContainer;
static ItemT opCall(Item it)
{
ItemT itm;
itm.item = it;
itm.i = it.i;
return itm;
}
}
void endFiles()
{
foreach(ListKeeper l; recordLists)
l.endFile();
items.endFile();
}
void initializeLists()
{
recordLists = null;
// Initialize all the lists here. The sizes have been chosen big
// enough to hold the main ESM files and a large number of mods
// without rehashing.
activators = new ListID!(Activator)(1400);
doors = new ListID!(Door)(300);
globals = new ListID!(Global)(300);
scripts = new ScriptList(1800);
sounds = new ListID!(Sound)(1000);
gameSettings = new ListID!(GameSetting)(1600);
factions = new ListID!(Faction)(30);
statics = new ListID!(Static)(4000);
spells = new ListID!(Spell)(1300);
potions = new ListID!(Potion)(300);
appas = new ListID!(Apparatus)(30);
armors = new ListID!(Armor)(500);
bodyParts = new ListID!(BodyPart)(2300);
enchants = new ListID!(Enchantment)(1000);
books = new ListID!(Book)(700);
birthSigns = new ListID!(BirthSign)(30);
landTextures = new LandTextureList;
effects = new MagicEffectList;
weapons = new ListID!(Weapon)(700);
lockpicks = new ListID!(Tool)(10);
probes = new ListID!(Tool)(10);
repairs = new ListID!(Tool)(10);
cells = new CellList;
regions = new ListID!(Region)(20);
lights = new ListID!(Light)(1000);
skills = new SkillList;
soundGens = new ListID!(SoundGenerator)(500);
races = new ListID!(Race)(100);
miscItems = new ListID!(Misc)(700);
clothes = new ListID!(Clothing)(700);
ingreds = new ListID!(Ingredient)(200);
classes = new ListID!(Class)(100);
containers = new ListID!(Container)(1200);
creatures = new ListID!(Creature)(800);
itemLists = new ListID!(LeveledItems)(600);
creatureLists = new ListID!(LeveledCreatures)(400);
npcs = new ListID!(NPC)(3500);
dialogues = new ListID!(Dialogue)(3000);
startScripts.init();
hyperlinks.rehash(1600);
items.rehash(5500);
actors.rehash(5000);
cellRefs.rehash(17000);
}

@ -1,377 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (esmtool.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esmtool;
import std.stdio;
import core.memory;
import esm.esmmain;
import monster.util.string;
import mscripts.setup;
import std.gc;
import gcstats;
// Not used, but we have to link it in along with the C++ stuff.
import input.events;
void poolSize()
{
GCStats gc;
getStats(gc);
writefln("Pool size: ", comma(gc.poolsize));
writefln("Used size: ", comma(gc.usedsize));
}
//alias int[Dialogue*] TopicList;
void main(char[][] args)
{
char[][] files;
bool raw;
bool scptList; // List scripts
bool scptShow; // Show a script
char[] scptName; // Script to show
bool ciList; // List interior cells
bool ceList; // List exterior cells that have names
bool weList; // List weapons
bool gmst; // List game settings
bool numbers; // List how many there are of each record type
foreach(char[] a; args[1..$])
if(a == "-r") raw = true;
else if(a == "-sl") scptList = true;
else if(a == "-s") scptShow = true;
else if(scptShow && scptName == "") scptName = a;
else if(a == "-g") gmst = true;
else if(a == "-cil") ciList = true;
else if(a == "-cel") ceList = true;
else if(a == "-wl") weList = true;
else if(a == "-n") numbers = true;
else if(a.begins("-")) writefln("Ignoring unknown option %s", a);
else files ~= a;
int help(char[] msg)
{
writefln("%s", msg);
writefln("Syntax: %s [options] esm-file [esm-file ... ]", args[0]);
writefln(" Options:");
writefln(" -r Display all records in raw format");
writefln(" -n List the number of each record type");
writefln(" -sl List scripts");
writefln(" -g List game settings (GMST)");
writefln(" -s name Show given script");
writefln(" -cil List interior cells");
writefln(" -cel List exterior cells with names");
writefln(" -wl List weapons");
return 1;
}
if(files.length == 0) return help("No input files given");
if(scptShow && scptName == "") return help("No script name given");
initializeMemoryRegions();
initMonsterScripts();
if(raw)
{
foreach(int fileNum, char[] filename; files)
{
try
{
esFile.open(filename, esmRegion);
printRaw();
}
catch(Exception e)
{
try {writefln(e);}
catch {}
writefln("Error on file %s", filename);
}
catch
{
writefln("Error: Unkown failure on file %s", filename);
}
}
return;
}
// Disable resource lookups.
resources.dummy = true;
try loadTESFiles(files);
catch(Exception e)
{
writefln(e);
}
catch { writefln("Error: Unkown failure"); }
// List weapons
if(weList) foreach(n, m; weapons.names)
{
alias Weapon.Type WT;
switch(m.data.type)
{
case WT.ShortBladeOneHand: writef("Short Sword"); break;
case WT.LongBladeOneHand: writef("Long Sword, One-Handed"); break;
case WT.LongBladeTwoHand: writef("Long Sword, Two-Handed"); break;
case WT.BluntOneHand: writef("Blunt, One-Handed"); break;
case WT.BluntTwoClose: writef("Blunt, Two-Handed"); break;
case WT.BluntTwoWide: writef("Blunt, Two-Handed Wide"); break;
case WT.SpearTwoWide: writef("Spear, Two-Handed"); break;
case WT.AxeOneHand: writef("Axe, One-Handed"); break;
case WT.AxeTwoHand: writef("Axe, Two-Handed"); break;
case WT.MarksmanBow: writef("Bow"); break;
case WT.MarksmanCrossbow: writef("Crossbow"); break;
case WT.MarksmanThrown: writef("Thrown weapon"); break;
case WT.Arrow: writef("Arrow"); break;
case WT.Bolt: writef("Bolt"); break;
default: assert(0);
}
writefln(" id '%s': name '%s'", n, m.name);
if(m.data.flags & Weapon.Flags.Magical)
writefln("Magical");
if(m.data.flags & Weapon.Flags.Silver)
writefln("Silver");
writefln("Weight: ", m.data.weight);
writefln("Value: ", m.data.value);
writefln("Health: ", m.data.health);
writefln("Speed: ", m.data.speed);
writefln("Reach: ", m.data.reach);
writefln("Enchantment points: ", m.data.enchant);
writefln("Combat: ", m.data.chop, m.data.slash, m.data.thrust);
if(m.enchant) writefln("Has enchantment '%s'", m.enchant.id);
if(m.script) writefln("Has script '%s'", m.script.id);
writefln();
}
if(numbers)
{
writefln("Activators: ", activators.length);
writefln("Doors: ", doors.length);
writefln("Globals: ", globals.length);
writefln("Sounds: ", sounds.length);
writefln("Game Settings: ", gameSettings.length);
writefln("Factions: ", factions.length);
writefln("Statics: ", statics.length);
writefln("Spells: ", spells.length);
writefln("Potions: ", potions.length);
writefln("Apparatus: ", appas.length);
writefln("Armors: ", armors.length);
writefln("Body parts: ", bodyParts.length);
writefln("Enchantments: ", enchants.length);
writefln("Books: ", books.length);
writefln("Birth signs: ", birthSigns.length);
writefln("Land texture files: ", landTextures.length);
writefln("Weapons: ", weapons.length);
writefln("Lockpicks: ", lockpicks.length);
writefln("Probes: ", probes.length);
writefln("Repairs: ", repairs.length);
writefln("Cells: ", cells.length);
writefln(" Interior: ", cells.numInt);
writefln(" Exterior: ", cells.numExt);
writefln("Regions: ", regions.length);
writefln("Lights: ", lights.length);
writefln("Skills: ", skills.length);
writefln("Sound generators: ", soundGens.length);
writefln("Races: ", races.length);
writefln("Misc items: ", miscItems.length);
writefln("Cloths: ", clothes.length);
writefln("Ingredients: ", ingreds.length);
writefln("Classes: ", classes.length);
writefln("Containers: ", containers.length);
writefln("Creatures: ", creatures.length);
writefln("Leveled item lists: ", itemLists.length);
writefln("Leveled creature lists: ", creatureLists.length);
writefln("NPCs: ", npcs.length);
writefln("Scripts: ", scripts.length);
writefln("Dialogues: ", dialogues.length);
writefln("Hyperlinks: ", hyperlinks.list.length);
writefln("Start scripts: ", startScripts.length);
writefln("\nTotal items: ", items.length);
writefln("Total actors: ", actors.length);
writefln("Total cell placable items: ", cellRefs.length);
}
if(gmst)
{
foreach(a, b; gameSettings.names)
{
writef(a, " (");
if(b.type == VarType.Int) writefln("int) = ", b.i);
else if(b.type == VarType.Float) writefln("float) = ", b.f);
else if(b.type == VarType.String) writefln("string) = '%s'", b.str);
else writefln("no value)", cast(int)b.type);
}
}
if(scptList) foreach(a, b; scripts.names) writefln(a);
if(ciList)
foreach(a, b; cells.in_cells)
writefln(a);
if(ceList)
foreach(uint i, c; .cells.ex_cells)
{
int x, y;
CellList.decompound(i, x, y);
if(c.name.length)
writefln("%s,%s: %s", x, y, c.name);
}
if(scptShow)
{
Script *p = scripts.names.lookup(scptName);
if(p)
writefln("Script '%s', text is:\n-------\n%s\n-------", p.id, p.scriptText);
else writefln("Script '%s' not found", scptName);
writefln();
}
writefln(esmRegion);
poolSize();
}
// Quick function that simply iterates through an ES file and prints
// out all the records and subrecords. Some of this code is really old
// (about 2004-2005)
void printRaw()
{
with(esFile)
{
// Variable length integer (this only works for unsigned ints!)
ulong getHVUint()
{
ulong l;
getSubHeader();
if( (getSubSize != 4) &&
(getSubSize != 2) &&
(getSubSize != 8) )
fail(format("Unknown integer size: ", getSubSize));
readExact(&l, getSubSize);
return l;
}
writefln("Filename: ", getFilename);
writef("Filetype: ");
switch(getFileType())
{
case FileType.Plugin: writefln("Plugin"); break;
case FileType.Master: writefln("Master"); break;
case FileType.Savegame: writefln("Savegame"); break;
case FileType.Unknown: writefln("Unknown"); break;
default: assert(0);
}
writef("Version: ");
if(isVer12()) writefln("1.2");
else if(isVer13()) writefln("1.3");
else writefln("Unknown");
writefln("Records: ", getRecords);
writefln("Master files:");
for(int i; i<getMasters.length; i++)
writefln(" %s", getMasters[i].name, ", ", getMasters[i].size, " bytes");
writefln("Author: %s", getAuthor);
//writefln("Description: %s", desc);
writefln("Total file size: %d\n", getFileSize);
writefln("List of records:");
while(hasMoreRecs())
{
uint flags;
// Read record header
char[] recName = getRecName();
getRecHeader(flags);
if(flags)
{
writef("Flags: ");
if(flags & RecordFlags.Persistent) writef("Persistent ");
if(flags & RecordFlags.Blocked) writef("Blocked ");
if(flags & RecordFlags.Flag6) writef("Flag6 ");
if(flags & RecordFlags.Flag13) writef("Flag13 ");
writefln();
if(flags & RecordFlags.Unknown)
writefln("UNKNOWN flags are set: %xh", flags);
}
// Process sub record
writef("%s %d bytes", recName, getRecLeft());
writefln();
while(hasMoreSubs())
{
getSubName();
char[] subName = retSubName();
writef(" %s = ", subName);
// Process header
if(subName == "NAME" || subName == "STRV" ||
subName == "FNAM" || subName == "MODL" ||
subName == "SCRI" || subName == "RGNN" ||
subName == "BNAM" || subName == "ONAM" ||
subName == "INAM" || subName == "SCVR" ||
subName == "RNAM" || subName == "DNAM" ||
subName == "ANAM")
//subName == "SCTX") // For script text
//getHString();
{
writefln("'%s'", getHString());
}
else if(subName == "FLTV" || subName == "XSCL")
{
float f = getHFloat();
writefln("f=", f, " i=", *(cast(int*)&f));
}
else if(subName == "INTV" /*|| subName == "NAM0"*/ || subName == "FRMR")
writefln(getHVUint());
else
{
int left = skipHSub();
writefln(left, " bytes");
}
}
writefln();
}
}
}

@ -1,336 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (events.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module input.events;
import std.stdio;
import std.string;
import sound.audio;
import core.config;
import scene.soundlist;
import scene.player;
import bullet.bindings;
import monster.monster;
import monster.vm.dbg;
import ogre.bindings;
import gui.bindings;
import gui.gui;
import input.keys;
import input.ois;
// Debug output
//debug=printMouse; // Mouse button events
//debug=printMouseMove; // Mouse movement events
//debug=printKeys; // Keypress events
// TODO: Jukebox controls and other state-related data will later be
// handled entirely in script code, as will some of the key bindings.
// Pause?
bool pause = false;
int *guiMode;
const float volDiff = 0.05;
void musVolume(bool increase)
{
float diff = -volDiff;
if(increase) diff = -diff;
config.setMusicVolume(diff + config.getMusicVolume);
writefln(increase?"Increasing":"Decreasing", " music volume to ", config.getMusicVolume);
}
void sfxVolume(bool increase)
{
float diff = -volDiff;
if(increase) diff = -diff;
config.setSfxVolume(diff + config.getSfxVolume);
writefln(increase?"Increasing":"Decreasing", " sound effect volume to ", config.getSfxVolume);
}
void mainVolume(bool increase)
{
float diff = -volDiff;
if(increase) diff = -diff;
config.setMainVolume(diff + config.getMainVolume);
writefln(increase?"Increasing":"Decreasing", " main volume to ", config.getMainVolume);
}
void updateMouseSensitivity()
{
effMX = *config.mouseSensX;
effMY = *config.mouseSensY;
if(*config.flipMouseY) effMY = -effMY;
}
void togglePause()
{
pause = !pause;
if(pause) writefln("Pause");
else writefln("Pause off");
}
extern(C) void d_handleMouseButton(MouseState *state, int button)
{
debug(printMouse)
writefln("handleMouseButton %s: Abs(%s, %s, %s)", button,
state.X.abs, state.Y.abs, state.Z.abs);
// For the moment, just treat mouse clicks as normal key presses.
d_handleKey(cast(KC) (KC.Mouse0 + button));
}
// Handle a keyboard event through key bindings. Direct movement
// (eg. arrow keys) is not handled here, see d_frameStarted() below.
extern(C) void d_handleKey(KC keycode, dchar text = 0)
{
// Do some preprocessing on the data to account for OIS
// shortcommings.
// Some keys (especially international keys) have no key code but
// return a character instead.
if(keycode == 0)
{
// If no character is given, just drop this event since OIS did
// not manage to give us any useful data at all.
if(text == 0) return;
keycode = KC.CharOnly;
}
// Debug output
debug(printKeys)
{
char[] str;
if(keycode >= 0 && keycode < keysymToString.length)
str = keysymToString[keycode];
else str = "OUT_OF_RANGE";
writefln("Key %s, text '%s', name '%s'", keycode, text, str);
}
// Look up the key binding. We have to send both the keycode and the
// text.
Keys k = keyBindings.findMatch(keycode, text);
// These are handled even if we are in gui mode:
if(k)
switch(k)
{
case Keys.ToggleGui: gui_toggleGui(); return;
case Keys.Console: gui_toggleConsole(); return;
case Keys.ScreenShot: takeScreenShot(); return;
default:
}
if(*guiMode) return;
if(k)
switch(k)
{
case Keys.ToggleBattleMusic:
Music.toggle();
return;
case Keys.MainVolUp: mainVolume(true); return;
case Keys.MainVolDown: mainVolume(false); return;
case Keys.MusVolUp: musVolume(true); return;
case Keys.MusVolDown: musVolume(false); return;
case Keys.SfxVolUp: sfxVolume(true); return;
case Keys.SfxVolDown: sfxVolume(false); return;
case Keys.Mute: Music.toggleMute(); return;
case Keys.Fullscreen: toggleFullscreen(); return;
case Keys.PhysMode: bullet_nextMode(); return;
case Keys.Nighteye: ogre_toggleLight(); return;
case Keys.Debug: return;
case Keys.Pause: togglePause(); return;
case Keys.Exit: exitProgram(); return;
default:
assert(k >= 0 && k < keyToString.length);
writefln("WARNING: Event %s has no effect", keyToString[k]);
}
return;
}
// Refresh rate for sfx placements, in seconds.
const float sndRefresh = 0.17;
// Refresh rate for music fadeing, seconds.
const float musRefresh = 0.05;
// Walking / floating speed, in points per second.
float speed = 300;
float sndCumTime = 0;
float musCumTime = 0;
// Move the player according to playerData.position
void movePlayer()
{
// Move the player into place. TODO: This isn't really input-related
// at all, and should be moved.
with(*playerData.position)
{
ogre_moveCamera(position[0], position[1], position[2]);
ogre_setCameraRotation(rotation[0], rotation[1], rotation[2]);
bullet_movePlayer(position[0], position[1], position[2]);
}
}
void initializeInput()
{
movePlayer();
// TODO/FIXME: This should have been in config, but DMD's module
// system is on the brink of collapsing, and it won't compile if I
// put another import in core.config. I should probably check the
// bug list and report it.
updateMouseSensitivity();
// Get a pointer to the 'guiMode' flag in cpp_ogre.cpp
guiMode = gui_getGuiModePtr();
}
extern(C) int ois_isPressed(int keysym);
// Check if a key is currently down
bool isPressed(Keys key)
{
KeyBind *b = &keyBindings.bindings[key];
foreach(i; b.syms)
if(i != 0 && ois_isPressed(i)) return true;
return false;
}
// Enable superman mode, ie. flight and super-speed. Only used for
// debugging the terrain mode.
extern(C) void d_terr_superman()
{
bullet_fly();
speed = 8000;
with(*playerData.position)
{
position[0] = 20000;
position[1] = -70000;
position[2] = 30000;
}
movePlayer();
}
extern(C) int d_frameStarted(float time)
{
if(doExit) return 0;
dbg.trace("d_frameStarted");
scope(exit) dbg.untrace();
// Run the Monster scheduler
vm.frame(time);
musCumTime += time;
if(musCumTime > musRefresh)
{
Music.updateBuffers();
musCumTime -= musRefresh;
}
// The rest is ignored in pause or GUI mode
if(pause || *guiMode > 0) return 1;
// Check if the movement keys are pressed
float moveX = 0, moveY = 0, moveZ = 0;
float x, y, z, ox, oy, oz;
if(isPressed(Keys.MoveLeft)) moveX -= speed;
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) and disabled for everything else.
if(isPressed(Keys.MoveUp)) moveY += speed;
if(isPressed(Keys.MoveDown)) moveY -= speed;
// This isn't very elegant, but it's simple and it works.
// Get the current coordinates
ogre_getCameraPos(&ox, &oy, &oz);
// Move camera using relative coordinates. TODO: We won't really
// need to move the camera here (since it's moved below anyway), we
// only want the transformation from camera space to world
// space. This can likely be done more efficiently.
ogre_moveCameraRel(moveX, moveY, moveZ);
// Get the result
ogre_getCameraPos(&x, &y, &z);
// The result is the real movement direction, in world coordinates
moveX = x-ox;
moveY = y-oy;
moveZ = z-oz;
// Tell Bullet that this is where we want to go
bullet_setPlayerDir(moveX, moveY, moveZ);
// Perform a Bullet time step
bullet_timeStep(time);
// Get the final (actual) player position and update the camera
bullet_getPlayerPos(&x, &y, &z);
ogre_moveCamera(x,y,z);
// Store it in the player object
playerData.position.position[0] = x;
playerData.position.position[1] = y;
playerData.position.position[2] = z;
if(!config.noSound)
{
// Tell the sound scene that the player has moved
sndCumTime += time;
if(sndCumTime > sndRefresh)
{
float fx, fy, fz;
float ux, uy, uz;
ogre_getCameraOrientation(&fx, &fy, &fz, &ux, &uy, &uz);
soundScene.update(x,y,z,fx,fy,fz,ux,uy,uz);
sndCumTime -= sndRefresh;
}
}
return 1;
}
bool collides = false;

@ -1,428 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (ois.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module input.ois;
// Mouse buttons
enum MB : int
{
Button0 = 0,
Left = Button0,
Button1 = 1,
Right = Button1,
Button2 = 2,
Middle = Button2,
Button3 = 3,
Button4 = 4,
Button5 = 5,
Button6 = 6,
Button7 = 7,
LastMouse
}
// Keyboard scan codes
enum KC : int
{
UNASSIGNED = 0x00,
ESCAPE = 0x01,
N1 = 0x02,
N2 = 0x03,
N3 = 0x04,
N4 = 0x05,
N5 = 0x06,
N6 = 0x07,
N7 = 0x08,
N8 = 0x09,
N9 = 0x0A,
N0 = 0x0B,
MINUS = 0x0C, // - on main keyboard
EQUALS = 0x0D,
BACK = 0x0E, // backspace
TAB = 0x0F,
Q = 0x10,
W = 0x11,
E = 0x12,
R = 0x13,
T = 0x14,
Y = 0x15,
U = 0x16,
I = 0x17,
O = 0x18,
P = 0x19,
LBRACKET = 0x1A,
RBRACKET = 0x1B,
RETURN = 0x1C, // Enter on main keyboard
LCONTROL = 0x1D,
A = 0x1E,
S = 0x1F,
D = 0x20,
F = 0x21,
G = 0x22,
H = 0x23,
J = 0x24,
K = 0x25,
L = 0x26,
SEMICOLON = 0x27,
APOSTROPHE = 0x28,
GRAVE = 0x29, // accent
LSHIFT = 0x2A,
BACKSLASH = 0x2B,
Z = 0x2C,
X = 0x2D,
C = 0x2E,
V = 0x2F,
B = 0x30,
N = 0x31,
M = 0x32,
COMMA = 0x33,
PERIOD = 0x34, // . on main keyboard
SLASH = 0x35, // / on main keyboard
RSHIFT = 0x36,
MULTIPLY = 0x37, // * on numeric keypad
LMENU = 0x38, // left Alt
SPACE = 0x39,
CAPITAL = 0x3A,
F1 = 0x3B,
F2 = 0x3C,
F3 = 0x3D,
F4 = 0x3E,
F5 = 0x3F,
F6 = 0x40,
F7 = 0x41,
F8 = 0x42,
F9 = 0x43,
F10 = 0x44,
NUMLOCK = 0x45,
SCROLL = 0x46, // Scroll Lock
NUMPAD7 = 0x47,
NUMPAD8 = 0x48,
NUMPAD9 = 0x49,
SUBTRACT = 0x4A, // - on numeric keypad
NUMPAD4 = 0x4B,
NUMPAD5 = 0x4C,
NUMPAD6 = 0x4D,
ADD = 0x4E, // + on numeric keypad
NUMPAD1 = 0x4F,
NUMPAD2 = 0x50,
NUMPAD3 = 0x51,
NUMPAD0 = 0x52,
DECIMAL = 0x53, // . on numeric keypad
OEM_102 = 0x56, // < > | on UK/Germany keyboards
F11 = 0x57,
F12 = 0x58,
F13 = 0x64, // (NEC PC98)
F14 = 0x65, // (NEC PC98)
F15 = 0x66, // (NEC PC98)
KANA = 0x70, // (Japanese keyboard)
ABNT_C1 = 0x73, // / ? on Portugese (Brazilian) keyboards
CONVERT = 0x79, // (Japanese keyboard)
NOCONVERT = 0x7B, // (Japanese keyboard)
YEN = 0x7D, // (Japanese keyboard)
ABNT_C2 = 0x7E, // Numpad . on Portugese (Brazilian) keyboards
NUMPADEQUALS= 0x8D, // = on numeric keypad (NEC PC98)
PREVTRACK = 0x90, // Previous Track (CIRCUMFLEX on Japanese keyboard)
AT = 0x91, // (NEC PC98)
COLON = 0x92, // (NEC PC98)
UNDERLINE = 0x93, // (NEC PC98)
KANJI = 0x94, // (Japanese keyboard)
STOP = 0x95, // (NEC PC98)
AX = 0x96, // (Japan AX)
UNLABELED = 0x97, // (J3100)
NEXTTRACK = 0x99, // Next Track
NUMPADENTER = 0x9C, // Enter on numeric keypad
RCONTROL = 0x9D,
MUTE = 0xA0, // Mute
CALCULATOR = 0xA1, // Calculator
PLAYPAUSE = 0xA2, // Play / Pause
MEDIASTOP = 0xA4, // Media Stop
VOLUMEDOWN = 0xAE, // Volume -
VOLUMEUP = 0xB0, // Volume +
WEBHOME = 0xB2, // Web home
NUMPADCOMMA = 0xB3, // , on numeric keypad (NEC PC98)
DIVIDE = 0xB5, // / on numeric keypad
SYSRQ = 0xB7, // Also called print screen
RMENU = 0xB8, // right Alt
PAUSE = 0xC5, // Pause
HOME = 0xC7, // Home on arrow keypad
UP = 0xC8, // UpArrow on arrow keypad
PGUP = 0xC9, // PgUp on arrow keypad
LEFT = 0xCB, // LeftArrow on arrow keypad
RIGHT = 0xCD, // RightArrow on arrow keypad
END = 0xCF, // End on arrow keypad
DOWN = 0xD0, // DownArrow on arrow keypad
PGDOWN = 0xD1, // PgDn on arrow keypad
INSERT = 0xD2, // Insert on arrow keypad
DELETE = 0xD3, // Delete on arrow keypad
LWIN = 0xDB, // Left Windows key
RWIN = 0xDC, // Right Windows key
APPS = 0xDD, // AppMenu key
POWER = 0xDE, // System Power
SLEEP = 0xDF, // System Sleep
WAKE = 0xE3, // System Wake
WEBSEARCH = 0xE5, // Web Search
WEBFAVORITES= 0xE6, // Web Favorites
WEBREFRESH = 0xE7, // Web Refresh
WEBSTOP = 0xE8, // Web Stop
WEBFORWARD = 0xE9, // Web Forward
WEBBACK = 0xEA, // Web Back
MYCOMPUTER = 0xEB, // My Computer
MAIL = 0xEC, // Mail
MEDIASELECT = 0xED, // Media Select
CharOnly = 0xFF, // Set when the keysym is 0 but the
// character is set. This happens with many
// international characters or reassigned
// characters.
Mouse0 = 0x100, // Mouse button events can be handled as
Mouse1 = 0x101, // keypresses too.
Mouse2 = 0x102,
Mouse3 = 0x103,
Mouse4 = 0x104,
Mouse5 = 0x105,
Mouse6 = 0x106,
Mouse7 = 0x107,
}
// Sigh. I guess we have to do this for Monster at some poing anyway,
// so this work isn't completely wasted. Later we can make a generic
// conversion between OIS-keysyms, SDL-keysyms and others to the
// Monster keysyms. It sucks that everybody tries to reinvent the
// wheel as often as they can, but that's the way it goes.
const char[][] keysymToString =
[
KC.UNASSIGNED : "UNASSIGNED",
KC.ESCAPE : "escape",
KC.N1 : "1",
KC.N2 : "2",
KC.N3 : "3",
KC.N4 : "4",
KC.N5 : "5",
KC.N6 : "6",
KC.N7 : "7",
KC.N8 : "8",
KC.N9 : "9",
KC.N0 : "0",
KC.MINUS : "minus",
KC.EQUALS : "equals",
KC.BACK : "backspace",
KC.TAB : "tab",
KC.Q : "q",
KC.W : "w",
KC.E : "e",
KC.R : "r",
KC.T : "t",
KC.Y : "y",
KC.U : "u",
KC.I : "i",
KC.O : "o",
KC.P : "p",
KC.LBRACKET : "{",
KC.RBRACKET : "}",
KC.RETURN : "enter",
KC.LCONTROL : "left_ctrl",
KC.A : "a",
KC.S : "s",
KC.D : "d",
KC.F : "f",
KC.G : "g",
KC.H : "h",
KC.J : "j",
KC.K : "k",
KC.L : "l",
KC.SEMICOLON : "semicolon",
KC.APOSTROPHE : "apostrophe",
KC.GRAVE : "grave",
KC.LSHIFT : "left_shift",
KC.BACKSLASH : "backslash",
KC.Z : "z",
KC.X : "x",
KC.C : "c",
KC.V : "v",
KC.B : "b",
KC.N : "n",
KC.M : "m",
KC.COMMA : "comma",
KC.PERIOD : "period",
KC.SLASH : "slash",
KC.RSHIFT : "right_shift",
KC.MULTIPLY : "numpad_mult",
KC.LMENU : "left_alt",
KC.SPACE : "space",
KC.CAPITAL : "capital",
KC.F1 : "f1",
KC.F2 : "f2",
KC.F3 : "f3",
KC.F4 : "f4",
KC.F5 : "f5",
KC.F6 : "f6",
KC.F7 : "f7",
KC.F8 : "f8",
KC.F9 : "f9",
KC.F10 : "f10",
KC.NUMLOCK : "numlock",
KC.SCROLL : "scroll",
KC.NUMPAD7 : "numpad_7",
KC.NUMPAD8 : "numpad_8",
KC.NUMPAD9 : "numpad_9",
KC.SUBTRACT : "numpad_minus",
KC.NUMPAD4 : "numpad_4",
KC.NUMPAD5 : "numpad_5",
KC.NUMPAD6 : "numpad_6",
KC.ADD : "numpad_plus",
KC.NUMPAD1 : "numpad_1",
KC.NUMPAD2 : "numpad_2",
KC.NUMPAD3 : "numpad_3",
KC.NUMPAD0 : "numpad_0",
KC.DECIMAL : "numpad_period",
KC.OEM_102 : "oem102",
KC.F11 : "f11",
KC.F12 : "f12",
KC.F13 : "f13",
KC.F14 : "f14",
KC.F15 : "f15",
KC.KANA : "kana",
KC.ABNT_C1 : "abnt_c1",
KC.CONVERT : "convert",
KC.NOCONVERT : "noconvert",
KC.YEN : "yen",
KC.ABNT_C2 : "abnt_c2",
KC.NUMPADEQUALS: "numpad_equals",
KC.PREVTRACK : "prev_track",
KC.AT : "at",
KC.COLON : "colon",
KC.UNDERLINE : "underline",
KC.KANJI : "kanji",
KC.STOP : "stop",
KC.AX : "ax",
KC.UNLABELED : "unlabeled",
KC.NEXTTRACK : "next_track",
KC.NUMPADENTER : "numpad_enter",
KC.RCONTROL : "right_control",
KC.MUTE : "mute",
KC.CALCULATOR : "calculator",
KC.PLAYPAUSE : "play_pause",
KC.MEDIASTOP : "media_stop",
KC.VOLUMEDOWN : "volume_down",
KC.VOLUMEUP : "volume_up",
KC.WEBHOME : "webhome",
KC.NUMPADCOMMA : "numpad_comma",
KC.DIVIDE : "numpad_divide",
KC.SYSRQ : "print_screen",
KC.RMENU : "right_alt",
KC.PAUSE : "pause",
KC.HOME : "home",
KC.UP : "up",
KC.PGUP : "page_up",
KC.LEFT : "left",
KC.RIGHT : "right",
KC.END : "end",
KC.DOWN : "down",
KC.PGDOWN : "page_down",
KC.INSERT : "insert",
KC.DELETE : "delete",
KC.LWIN : "left_win",
KC.RWIN : "right_win",
KC.APPS : "app_menu",
KC.POWER : "power",
KC.SLEEP : "sleep",
KC.WAKE : "wake",
KC.WEBSEARCH : "web_search",
KC.WEBFAVORITES: "web_favorites",
KC.WEBREFRESH : "web_refresh",
KC.WEBSTOP : "web_stop",
KC.WEBFORWARD : "web_forward",
KC.WEBBACK : "web_back",
KC.MYCOMPUTER : "my_computer",
KC.MAIL : "mail",
KC.MEDIASELECT : "media_select",
KC.CharOnly : "CHAR_ONLY", // Set when the keysym is 0 but the
// character is set. This happens
// with many international
// characters or reassigned
// characters in OIS (and it
// SUCKS.)
KC.Mouse0 : "mouse0",
KC.Mouse1 : "mouse1",
KC.Mouse2 : "mouse2",
KC.Mouse3 : "mouse3",
KC.Mouse4 : "mouse4",
KC.Mouse5 : "mouse5",
KC.Mouse6 : "mouse6",
KC.Mouse7 : "mouse7",
];
enum ComponentType : int
{
Unknown = 0,
Button = 1, // ie. Key, mouse button, joy button, etc
Axis = 2, // ie. A joystick or mouse axis
Slider = 3, //
POV = 4, // ie. Arrow direction keys
Vector3 = 5 // ie. WiiMote orientation
}
align(4) struct Axis
{
ComponentType type;
int abs, rel;
bool absOnly;
}
// The C++ size of Axis is 16
static assert(Axis.sizeof == 16);
struct MouseState
{
/* Represents the height/width of your display area.. used if mouse
clipping or mouse grabbed in case of X11 - defaults to 50.. Make
sure to set this and change when your size changes.. */
int width, height;
// X Axis component
Axis X;
// Y Axis Component
Axis Y;
// Z Axis Component
Axis Z;
// represents all buttons - bit position indicates button down
int buttons;
// Button down test
bool buttonDown( MB button )
{
return (buttons & ( 1 << button )) != 0;
}
}
// Check that we match the C++ size
static assert(MouseState.sizeof == 60);

@ -1,41 +0,0 @@
// Get current camera orientation, in the form of 'front' and 'up'
// vectors.
extern "C" void ogre_getCameraOrientation(float *fx, float *fy, float *fz,
float *ux, float *uy, float *uz)
{
Vector3 front = mCamera->getDirection();
Vector3 up = mCamera->getUp();
*fx = front[0];
*fy = -front[2];
*fz = front[1];
*ux = up[0];
*uy = -up[2];
*uz = up[1];
}
// Move camera
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+90,-y));
}
// Rotate camera using Morrowind rotation specifiers
extern "C" void ogre_setCameraRotation(float r1, float r2, float r3)
{
// TODO: This translation is probably not correct, but for now I
// have no reference point. Fix it later when we teleport from one
// cell to another, so we have something to compare against.
// Rotate around X axis
Quaternion xr(Radian(-r1), Vector3::UNIT_X);
// Rotate around Y axis
Quaternion yr(Radian(r3+3.14), Vector3::UNIT_Y);
// Rotate around Z axis
Quaternion zr(Radian(-r2), Vector3::UNIT_Z);
// Rotates first around z, then y, then x
mCamera->setOrientation(xr*yr*zr);
}

@ -1,135 +0,0 @@
// Copy a scene node and all its children
void cloneNode(SceneNode *from, SceneNode *to, char* name)
{
to->setPosition(from->getPosition());
to->setOrientation(from->getOrientation());
to->setScale(from->getScale());
SceneNode::ObjectIterator it = from->getAttachedObjectIterator();
while(it.hasMoreElements())
{
// We can't handle non-entities.
Entity *e = dynamic_cast<Entity*> (it.getNext());
if(e)
{
e = e->clone(String(name) + ":" + e->getName());
to->attachObject(e);
}
}
// Recursively clone all child nodes
SceneNode::ChildNodeIterator it2 = from->getChildIterator();
while(it2.hasMoreElements())
{
cloneNode((SceneNode*)it2.getNext(), to->createChildSceneNode(), name);
}
}
// Supposed to insert a copy of the node, for now it just inserts the
// actual node.
extern "C" SceneNode *ogre_insertNode(SceneNode *base, char* name,
float *pos, float *quat,
float scale)
{
//std::cout << "ogre_insertNode(" << name << ")\n";
SceneNode *node = mwRoot->createChildSceneNode(name);
// Make a copy of the node
cloneNode(base, node, name);
// Apply transformations
node->setPosition(pos[0], pos[1], pos[2]);
node->setOrientation(quat[0], quat[1], quat[2], quat[3]);
node->setScale(scale, scale, scale);
return node;
}
// Get the world transformation of a node (the total transformation of
// this node and all parent nodes). Return it as a translation
// (3-vector) and a rotation / scaling part (3x3 matrix)
extern "C" void ogre_getWorldTransform(SceneNode *node,
float *trans, // Storage for translation
float *matrix)// For 3x3 matrix
{
// Get the world transformation first
Matrix4 trafo;
node->getWorldTransforms(&trafo);
// Extract the translation part and pass it to the caller
Vector3 tr = trafo.getTrans();
trans[0] = tr[0];
trans[1] = tr[1];
trans[2] = tr[2];
// Next extract the matrix
Matrix3 mat;
trafo.extract3x3Matrix(mat);
matrix[0] = mat[0][0];
matrix[1] = mat[0][1];
matrix[2] = mat[0][2];
matrix[3] = mat[1][0];
matrix[4] = mat[1][1];
matrix[5] = mat[1][2];
matrix[6] = mat[2][0];
matrix[7] = mat[2][1];
matrix[8] = mat[2][2];
}
// Create the water plane. It doesn't really resemble "water" yet
// though.
extern "C" void ogre_createWater(float level)
{
// Create a plane aligned with the xy-plane.
MeshManager::getSingleton().createPlane("water",
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Plane(Vector3::UNIT_Z, level),
150000,150000
);
Entity *ent = mSceneMgr->createEntity( "WaterEntity", "water" );
mwRoot->createChildSceneNode()->attachObject(ent);
ent->setCastShadows(false);
}
extern "C" SceneNode *ogre_getDetachedNode()
{
SceneNode *node = mwRoot->createChildSceneNode();
mwRoot->removeChild(node);
return node;
}
extern "C" SceneNode* ogre_createNode(
char *name,
float *trafo,
SceneNode *parent,
int32_t noRot)
{
//std::cout << "ogre_createNode(" << name << ")";
SceneNode *node = parent->createChildSceneNode(name);
//std::cout << " ... done\n";
// First is the translation vector
// TODO should be "if(!noRot)" only for exterior cells!? Yay for
// consistency. Apparently, the displacement of the base node in NIF
// files must be ignored for meshes in interior cells, but not for
// exterior cells. Or at least that's my hypothesis, and it seems
// work. There might be some other NIF trickery going on though, you
// never know when you're reverse engineering someone else's file
// format. We will handle this later.
if(!noRot)
node->setPosition(trafo[0], trafo[1], trafo[2]);
// Then a 3x3 rotation matrix.
if(!noRot)
node->setOrientation(Quaternion(Matrix3(trafo[3], trafo[4], trafo[5],
trafo[6], trafo[7], trafo[8],
trafo[9], trafo[10], trafo[11]
)));
// Scale is at the end
node->setScale(trafo[12],trafo[12],trafo[12]);
return node;
}

@ -1,391 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (meshloader.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module ogre.meshloader;
import std.stdio;
import std.stream;
import nif.nif;
import nif.record;
import core.resource;
import ogre.bindings;
import bullet.bindings;
import util.uniquename;
/*
There are some problems that will have to be looked into later:
- Some meshes crash Ogre when shadows are turned on. (Not tested in
newer versions of Ogre). Shadows are completely disabled for now.
- There are obviously some boundry problems, some times the mesh
disappears even though some part of it is inside the screen. This
is especially a problem with animated meshes, since the animation
might step outside the original bounding box.
*/
MeshLoader meshLoader;
struct MeshLoader
{
// Not sure how to handle the bounding box, just ignore it for now.
char[] baseName; // NIF file name. Used in scene node names etc. so
// that we can identify where they came from in
// case of error messages.
// Load a NIF mesh. Assumes nifMesh is already opened. This creates
// a "template" scene node containing this mesh, and removes it from
// the main scene. This node can later be "cloned" so that multiple
// instances of the object can be inserted into the world without
// inserting the mesh more than once.
void loadMesh(char[] name, out NodePtr base, out BulletShape shape)
{
baseName = name;
// Check if the first record is a node
Node n = cast(Node) nifMesh.records[0];
if(n is null)
{
// TODO: Figure out what to do in this case, we should
// probably throw.
writefln("NIF '%s' IS NOT A MESH", name);
return;
}
// Get a fresh SceneNode and detatch it from the root. We use this
// as the base for our mesh.
base = ogre_getDetachedNode();
// Recursively insert nodes (don't rotate the first node)
insertNode(n, base, 0, true);
// Get the final shape, if any
shape = bullet_getFinalShape();
return base;
}
private:
void insertNode(Node data, NodePtr parent,
int flags,
bool noRot = false)
{
// 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
// 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: 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);
// Handle any general properties here
// Call functions that do node-specific things, like handleNiNode
// or handleNiTriShape.
{
NiNode n = cast(NiNode)data;
if(n !is null)
// Handle the NiNode, and any children it might have
handleNiNode(n, node, flags);
}
{
NiTriShape n = cast(NiTriShape)data;
if(n !is null)
// Trishape, with a mesh
handleNiTriShape(n, node, flags);
}
}
void handleNiNode(NiNode data, NodePtr node, int flags)
{
// Ignore sound activators and similar objects.
NiStringExtraData d = cast(NiStringExtraData) data.extra;
if(d !is null)
{
// Marker objects are only visible in the editor. We
// completely ignore them.
if(d.string == "MRK")
return;
// No collision
if(d.string == "NCO")
flags |= 0x800; // Temporary internal marker
}
// Handle any effects here
// In the cases where meshes have skeletal animations, we must
// insert the children as bones in a skeleton instead, like we
// originally did for all nodes. Update: A much better way is to
// first insert the nodes normally, and then create the
// skeleton. The nodes can then be moved one by one over to the
// appropriate bones.
// Check the controller
auto cont = data.controller;
while(cont !is null)
{
auto kc = cast(NiKeyframeController)cont;
auto pc = cast(NiPathController)cont;
if(kc !is null)
{
/*
writefln("Found keyframe controller");
writefln(" Node name was: %s", data.name);
assert(cont.target is data);
auto kcd = kc.data;
writefln(" Types: %s %s %s",
kcd.rotType, kcd.traType, kcd.scaleType);
*/
/*
Adding keyframes:
Skeleton -> Animation -> NodeAnimationTrack nt;
TransformKeyFrame * tf = nt->createNodeKeyFrame(time);
tf->setTranslate(Vector3);
tf->setScale(Vector3);
tf->setRotation(Quaternion);
nt->applyToNode(node, time);
evt
Animation an;
an->apply(skeleton, time);
*/
}
else if(pc !is null)
{
//writefln("Found path controller");
assert(cont.target is data);
}
//else writefln("Other controller (%s)", cont);
cont = cont.next;
}
// Loop through children
foreach(Node n; data.children)
insertNode(n, node, flags);
}
void handleNiTriShape(NiTriShape shape, NodePtr node, int flags)
{
char[] texture;
char[] material;
char[] newName = UniqueName(baseName);
NiMaterialProperty mp;
// Special alpha settings, if the NiAlphaProperty is present
int alphaFlags = -1;
ubyte alphaTest;
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; bbcollide=false; }
// Skip the entire material phase for hidden nodes
if(hidden) goto nomaterial;
// Scan the property list for textures
foreach(Property p; shape.properties)
{
// NiTexturingProperty block
{
NiTexturingProperty t = cast(NiTexturingProperty) p;
if(t !is null && t.textures[0].inUse)
{
// Ignore all other options for now
NiSourceTexture st = t.textures[0].texture;
if(st.external)
{
// Find the resource for this texture
TextureIndex ti = resources.lookupTexture(st.filename);
// Insert a manual loader into OGRE
// ti.load();
// Get the resource name. We use getNewName to get
// the real texture name, not the lookup
// name. NewName has been converted to .dds if
// necessary, to match the file name in the bsa
// archives.
texture = ti.getNewName();
}
else
{
// Internal textures
texture = "BLAH";
writefln("Internal texture, cannot read this yet.");
writefln("Final resource name: '%s'", texture);
}
continue;
}
}
// NiAlphaProperty
{
NiAlphaProperty a = cast(NiAlphaProperty) p;
if(a !is null)
{
alphaFlags = a.flags;
alphaTest = a.threshold;
}
}
// NiMaterialProperty block
{
NiMaterialProperty tmp = cast(NiMaterialProperty) p;
if(tmp !is null)
{
if(mp !is null) writefln("WARNING: More than one material!");
mp = tmp;
material = newName;
continue;
}
}
}
// Get a pointer to the texture name
char* texturePtr;
if(texture.length) texturePtr = toStringz(texture);
else texturePtr = null;
// Create the material
if(material.length)
// A material is present. Use it.
ogre_createMaterial(material.ptr, mp.ambient.array.ptr,
mp.diffuse.array.ptr,
mp.specular.array.ptr, mp.emissive.array.ptr,
mp.glossiness, mp.alpha, texturePtr,
alphaFlags, alphaTest);
else if(texturePtr)
{
// Texture, but no material. Make a default one.
writefln("WARNING: Making default material for %s", texture);
float[3] zero;
float[3] one;
zero[] = 0.0;
one[] = 1.0;
ogre_createMaterial(newName.ptr, one.ptr, one.ptr, zero.ptr, zero.ptr,
0.0, 1.0, texturePtr, alphaFlags, alphaTest);
}
nomaterial:
with(shape.data)
{
//writefln("Number of vertices: ", vertices.length);
float *normalsPtr;
float *colorsPtr;
float *uvsPtr;
short *facesPtr;
// Point pointers into the correct arrays, if they are present. If
// not, the pointers retain their values of null.
if(normals.length) normalsPtr = normals.ptr;
if(colors.length) colorsPtr = colors.ptr;
if(uvlist.length) uvsPtr = uvlist.ptr;
if(triangles.length) facesPtr = triangles.ptr;
float
minX = float.infinity,
minY = float.infinity,
minZ = float.infinity,
maxX = -float.infinity,
maxY = -float.infinity,
maxZ = -float.infinity;
// 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];
if( vertices[i+1] < minY ) minY = vertices[i+1];
if( vertices[i+2] < minZ) minZ = vertices[i+2];
if( vertices[i] > maxX) maxX = vertices[i];
if( vertices[i+1] > maxY) maxY = vertices[i+1];
if( vertices[i+2] > maxZ) maxZ = vertices[i+2];
}
// Get the node world transformation, needed to set up
// the collision shape properly.
float[3] trans;
float[9] matrix;
ogre_getWorldTransform(node, trans.ptr, matrix.ptr);
// Next we must create the actual OGRE mesh and the collision
// objects, based on the flags we have been given. TODO: I
// guess the NIF bounding box is better than the one we have
// calculated ourselves? This code definitely doesn't work,
// but I haven't look into why yet.
assert(!bbcollide);
if(bbcollide)
// Insert the bounding box into the collision system
bullet_createBoxShape(minX, minY, minZ, maxX, maxY, maxZ,
trans.ptr, matrix.ptr);
// 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);
}
}
}

@ -1,471 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2009 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (openmw.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module openmw;
import std.stdio;
import std.string;
import std.cstream;
import std.file;
import ogre.ogre;
import ogre.bindings;
import gui.bindings;
import gui.gui;
import bullet.bullet;
import scene.celldata;
import scene.soundlist;
import scene.gamesettings;
import scene.player;
import core.resource;
import core.memory;
import core.config;
import monster.util.string;
import monster.vm.mclass;
import monster.vm.dbg;
import mscripts.setup;
import sound.audio;
import input.events;
import terrain.terrain;
// Set up exit handler
alias void function() c_func;
extern(C) int atexit(c_func);
bool cleanExit = false;
void exitHandler()
{
// If we exit uncleanly, print the function stack.
if(!cleanExit)
writefln(dbg.getTrace());
}
//*
import std.gc;
import gcstats;
void poolSize()
{
GCStats gc;
getStats(gc);
writefln("Pool size: ", comma(gc.poolsize));
writefln("Used size: ", comma(gc.usedsize));
}
//*/
void main(char[][] args)
{
bool render = true;
bool help = false;
bool resetKeys = false;
bool showOgreFlag = false;
bool debugOut = false;
bool extTest = false;
bool doGen = false;
bool nextSave = false;
bool loadSave = false;
// Some examples to try:
//
// "Abandoned Shipwreck, Upper Level";
// "Gro-Bagrat Plantation";
// "Abinabi";
// "Abebaal Egg Mine";
// "Ald-ruhn, Ald Skar Inn";
// "Koal Cave";
// "Ald-ruhn, Arobar Manor Bedrooms"
// "Sud";
// "Vivec, The Lizard's Head";
// "ToddTest";
// Cells to load
char[][] cells;
// Savegame to load
char[] savefile;
foreach(char[] a; args[1..$])
if(a == "-n") render = false;
else if(a == "-ex") extTest = true;
else if(a == "-gen") doGen = true;
else if(a == "-h") help=true;
else if(a == "-rk") resetKeys = true;
else if(a == "-oc") showOgreFlag = true;
else if(a == "-ns") config.noSound = true;
else if(a == "-save") nextSave = true;
else if(a == "-debug")
{
// Enable Monster debug output
dbg.dbgOut = dout;
// Tell OGRE to do the same later on
debugOut = true;
}
else if(nextSave)
{
savefile = a;
nextSave = false;
}
else cells ~= a;
if(cells.length > 1)
{
writefln("More than one cell specified, rendering disabled");
render=false;
}
void showHelp()
{
writefln("Syntax: %s [options] cell-name [cell-name]", args[0]);
writefln(" Options:");
writefln(" -n Only load, do not render");
writefln(" -ex Test the terrain system");
writefln(" -gen Generate landscape cache");
writefln(" -rk Reset key bindings to default");
writefln(" -oc Show the Ogre config dialogue");
writefln(" -ns Completely disable sound");
writefln(" -debug Print debug information");
writefln(" -save <file> Load cell/pos from savegame");
writefln(" -h Show this help");
writefln("");
writefln("Specifying more than one cell implies -n");
}
if(help)
{
showHelp();
return;
}
initializeMemoryRegions();
initMonsterScripts();
// This is getting increasingly hackish, but this entire engine
// design is now quickly outgrowing its usefulness, and a rewrite is
// coming soon anyway.
PlayerSaveInfo pi;
if(savefile != "")
{
if(cells.length)
{
writefln("Please don't specify both a savegame file (%s) and cell names (%s)", savefile, cells);
return;
}
loadSave = true;
writefln("Loading savegame %s", savefile);
pi = importSavegame(savefile);
writefln(" Player name: %s", pi.playerName);
writefln(" Cell name: %s", pi.cellName);
writefln(" Pos: %s", pi.pos.position);
writefln(" Rot: %s", pi.pos.rotation);
cells = [pi.cellName];
}
config.initialize(resetKeys);
scope(exit) config.writeConfig();
// Check if the data directory exists
if(!exists(config.dataDir) || !isdir(config.dataDir))
{
writefln("Cannot find data directory '", config.dataDir,
"' - please edit openmw.ini.");
return;
}
// If the -oc parameter is specified, we override any config
// setting.
if(showOgreFlag) config.finalOgreConfig = true;
if(cells.length == 0)
if(config.defaultCell.length)
cells ~= config.defaultCell;
if(cells.length == 1 && !loadSave)
config.defaultCell = cells[0];
if(cells.length == 0)
{
showHelp();
return;
}
if(!config.noSound) initializeSound();
resources.initResources();
// Load all ESM and ESP files
loadTESFiles(config.gameFiles);
scene.gamesettings.loadGameSettings();
CellData cd = cellList.get();
foreach(char[] cName; cells)
{
// Release the last cell data
cellList.release(cd);
// Get a cell data holder and load an interior cell
cd = cellList.get();
try cd.loadIntCell(cName);
catch(Exception e)
{
writefln("\nUnable to load cell '%s'.", cName);
writefln("\nDetails: %s", e);
writefln("
Perhaps this cell does not exist in your Morrowind language version?
Try specifying another cell name on the command line, or edit openmw.ini.");
return;
}
// If we're loading from save, override the player position
if(loadSave)
*playerData.position = pi.pos;
}
// Simple safety hack
NodePtr putObject(MeshIndex m, Placement *pos, float scale,
bool collide=false)
{
if(m is null)
writefln("WARNING: CANNOT PUT NULL OBJECT");
else if(!m.isEmpty)
return placeObject(m, pos, scale, collide);
//writefln("WARNING: CANNOT INSERT EMPTY MESH '%s'", m.getName);
return null;
}
if(cd.inCell !is null)
// Set the name for the GUI (temporary hack)
gui_setCellName(cd.inCell.id.ptr);
// Set up the exit handler
atexit(&exitHandler);
scope(exit) cleanExit = true;
if(render)
{
// Warm up OGRE
setupOgre(debugOut);
scope(exit) cleanupOgre();
// Create the GUI system
initGUI(debugOut);
// Set up Bullet
initBullet();
scope(exit) cleanupBullet();
// Initialize the internal input and event manager. The
// lower-level input system (OIS) is initialized by the
// setupOgre() call further up.
initializeInput();
// Play some old tunes
if(extTest)
{
// Exterior cell
/*
Color c;
c.red = 180;
c.green = 180;
c.blue = 180;
setAmbient(c, c, c, 0);
// Put in the water
ogre_createWater(cd.water);
// Create an ugly sky
ogre_makeSky();
*/
initTerrain(doGen);
}
else
{
// Interior cell
assert(cd.inCell !is null);
setAmbient(cd.ambi.ambient, cd.ambi.sunlight,
cd.ambi.fog, cd.ambi.fogDensity);
// Not all interior cells have water
if(cd.inCell.flags & CellFlags.HasWater)
ogre_createWater(cd.water);
// Insert the meshes of statics into the scene
foreach(ref LiveStatic ls; cd.statics)
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
// Inventory lights
foreach(ref LiveLight ls; cd.lights)
{
NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale());
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
if(!config.noSound)
{
Sound *s = ls.m.sound;
if(s)
{
ls.loopSound = soundScene.insert(s, true);
if(ls.loopSound)
{
auto p = ls.getPos();
ls.loopSound.setPos(p.position[0],
p.position[1],
p.position[2]);
}
}
}
}
// Static lights
foreach(ref LiveLight ls; cd.statLights)
{
NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
if(!config.noSound)
{
Sound *s = ls.m.sound;
if(s)
{
ls.loopSound = soundScene.insert(s, true);
if(ls.loopSound)
{
auto p = ls.getPos();
ls.loopSound.setPos(p.position[0],
p.position[1],
p.position[2]);
}
}
}
}
// Misc items
foreach(ref LiveMisc ls; cd.miscItems)
putObject(ls.m.model, ls.getPos(), ls.getScale());
/*
// NPCs (these are complicated, usually do not have normal meshes)
foreach(ref LiveNPC ls; cd.npcs)
putObject(ls.m.model, ls.getPos(), ls.getScale());
*/
// Containers
foreach(ref LiveContainer ls; cd.containers)
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
// Doors
foreach(ref LiveDoor ls; cd.doors)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Activators (including beds etc)
foreach(ref LiveActivator ls; cd.activators)
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
// Potions
foreach(ref LivePotion ls; cd.potions)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Apparatus
foreach(ref LiveApparatus ls; cd.appas)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Ingredients
foreach(ref LiveIngredient ls; cd.ingredients)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Armors
foreach(ref LiveArmor ls; cd.armors)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Weapons
foreach(ref LiveWeapon ls; cd.weapons)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Books
foreach(ref LiveBook ls; cd.books)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Clothes
foreach(ref LiveClothing ls; cd.clothes)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Tools
foreach(ref LiveTool ls; cd.tools)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// Creatures (not displayed very well yet)
foreach(ref LiveCreature ls; cd.creatures)
putObject(ls.m.model, ls.getPos(), ls.getScale());
// End of interior cell
}
// Run GUI system
startGUI();
// Play some old tunes
if(!config.noSound)
Music.play();
// Run it until the user tells us to quit
startRendering();
}
else if(debugOut) writefln("Skipping rendering");
if(!config.noSound)
{
soundScene.kill();
shutdownSound();
}
if(debugOut)
{
writefln();
writefln("%d statics", cd.statics.length);
writefln("%d misc items", cd.miscItems.length);
writefln("%d inventory lights", cd.lights.length);
writefln("%d static lights", cd.statLights.length);
writefln("%d NPCs", cd.npcs.length);
writefln("%d containers", cd.containers.length);
writefln("%d doors", cd.doors.length);
writefln("%d activators", cd.activators.length);
writefln("%d potions", cd.potions.length);
writefln("%d apparatuses", cd.appas.length);
writefln("%d ingredients", cd.ingredients.length);
writefln("%d armors", cd.armors.length);
writefln("%d weapons", cd.weapons.length);
writefln("%d books", cd.books.length);
writefln("%d tools", cd.tools.length);
writefln("%d clothes", cd.clothes.length);
writefln("%d creatures", cd.creatures.length);
writefln();
}
// This isn't necessary but it's here for testing purposes.
cellList.release(cd);
// Write some statistics
if(debugOut)
{
poolSize();
writefln(esmRegion);
writefln("Total objects: ", MonsterClass.getTotalObjects);
}
}

@ -1,461 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2009 Nicolay Korslund
WWW: http://openmw.sourceforge.net/
This file (archive.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module terrain.archive;
const float TEX_SCALE = 1.0/16;
// This should be part of the generic cache system.
const int CACHE_MAGIC = 0x345AF815;
import std.mmfile;
import std.string;
import std.stdio;
import terrain.myfile;
version(Windows)
static int pageSize = 64*1024;
else
static int pageSize = 4*1024;
extern(C)
{
// Convert a texture index to string
char *d_terr_getTexName(int index)
{ return g_archive.getString(index).ptr; }
// Fill various hardware buffers from cache
void d_terr_fillVertexBuffer(MeshInfo *mi, float *buffer, ulong size)
{ mi.fillVertexBuffer(buffer[0..size]); }
void d_terr_fillIndexBuffer(MeshInfo *mi, ushort *buffer, ulong size)
{ mi.fillIndexBuffer(buffer[0..size]); }
void d_terr_fillAlphaBuffer(AlphaInfo *mi, ubyte *buffer, ulong size)
{ mi.fillAlphaBuffer(buffer[0..size]); }
// Get a given alpha map struct belonging to a mesh
AlphaInfo *d_terr_getAlphaInfo(MeshInfo *mi, int index)
{ return mi.getAlphaInfo(index); }
int d_terr_getAlphaSize() { return g_archive.alphaSize; }
}
// Info about the entire quad. TODO: Some of this (such as the texture
// scale and probably the width and radius) can be generated at
// loadtime and is common for all quads on the same level. We could
// just make a QuadLevelInfo struct.
struct QuadInfo
{
// Basic info
int cellX, cellY;
int level;
// Bounding box info
float minHeight, maxHeight;
float worldWidth;
float boundingRadius;
// True if we should make the given child
bool hasChild[4];
// Number of mesh segments in this quad
int meshNum;
// Location of this quad in the main archive file. The size includes
// everything related to this quad, including mesh data, alpha maps,
// etc.
size_t offset, size;
}
// Info about an alpha map belonging to a mesh
struct AlphaInfo
{
// Position of the actual image data
ulong bufSize, bufOffset;
// The texture name for this layer. The actual string is stored in
// the archive's string buffer.
int texName = -1;
int alphaName = -1;
// Fill the alpha texture buffer
void fillAlphaBuffer(ubyte abuf[])
{
assert(abuf.length == bufSize);
g_archive.copy(abuf.ptr, bufOffset, bufSize);
}
}
static assert(AlphaInfo.sizeof == 6*4);
// Info about each submesh
// If you change this struct please check whether align(1) still fits.
align(1)
struct MeshInfo
{
// Bounding box info
float minHeight, maxHeight;
float worldWidth;
// Vertex and index numbers
int vertRows, vertCols;
// Height offset to apply to all vertices
float heightOffset;
// Size and offset of the vertex buffer
ulong vertBufSize, vertBufOffset;
// Texture name. Index to the string table.
int texName = -1;
// Number and offset of AlphaInfo blocks
int alphaNum;
ulong alphaOffset;
// Fill the given vertex buffer
void fillVertexBuffer(float vdest[])
{
// The height map and normals from the archive
byte *hmap = cast(byte*)g_archive.getRelSlice(vertBufOffset, vertBufSize).ptr;
// The generic part, containing the x,y coordinates and the uv
// maps.
float *gmap = g_archive.getVertexBuffer(getLevel()).ptr;
// Destination pointer
float *vbuf = vdest.ptr;
assert(vdest.length == vertRows*vertCols*8);
// Merge the two data sets together into the output buffer.
float offset = heightOffset;
for(int y=0; y<vertRows; y++)
{
// The offset for the entire row is determined by the first
// height value. All the values in a row gives the height
// relative to the previous value, and the first value in each
// row is relative to the first value in the previous row.
offset += *cast(short*)hmap;
// This is the 'sliding offset' for this row. It's adjusted
// for each vertex that's added, but only affects this row.
float rowofs = offset;
for(int x=0; x<vertCols; x++)
{
hmap+=2; // Skip the height we just read
// X and Y from the pregenerated buffer
*vbuf++ = *gmap++;
*vbuf++ = *gmap++;
// The height is calculated from the current offset
*vbuf++ = rowofs * 8;
// Normal vector.
*vbuf++ = *hmap++;
*vbuf++ = *hmap++;
*vbuf++ = *hmap++;
// UV
*vbuf++ = *gmap++;
*vbuf++ = *gmap++;
// Adjust the offset for the next vertex.
if(x < vertCols-1)
rowofs += *cast(short*)hmap;
}
}
}
// Fill the index buffer
void fillIndexBuffer(ushort ibuf[])
{
// The index buffer is pregenerated. It is identical for all
// meshes on the same level, so just copy it over.
ushort generic[] = g_archive.getIndexBuffer();
assert(ibuf.length == generic.length);
ibuf[] = generic[];
}
int getLevel()
{
assert(g_archive.curQuad);
return g_archive.curQuad.level;
}
// Get an alpha map belonging to this mesh
AlphaInfo *getAlphaInfo(int num)
{
assert(num < alphaNum && num >= 0);
assert(getLevel() == 1);
AlphaInfo *res = cast(AlphaInfo*)g_archive.getRelSlice
(alphaOffset, alphaNum*AlphaInfo.sizeof);
res += num;
return res;
}
}
static assert(MeshInfo.sizeof == 14*4);
// The first part of the .index file
struct ArchiveHeader
{
// "Magic" number to make sure we're actually reading an archive
// file
int magic;
// Total number of quads in the archive
int quads;
// Level of the 'root' quad. There will only be one quad on this
// level.
int rootLevel;
// Size of the alpha maps, in pixels along one side.
int alphaSize;
// Number of strings in the string table
int stringNum;
// Size of the string buffer
size_t stringSize;
}
TerrainArchive g_archive;
// This class handles the cached terrain data.
struct TerrainArchive
{
MeshInfo *curMesh;
QuadInfo *curQuad;
QuadInfo *rootQuad;
void openFile(char[] name)
{
mmf = new MmFile(name,
MmFile.Mode.Read,
0, null, pageSize);
// Read the index file first
MyFile ifile = new MyFile(name ~ ".index");
ArchiveHeader head;
ifile.fill(head);
// Reads data into an array. Would be better if this was part of
// the stream.
// Sanity check
assert(head.magic == CACHE_MAGIC);
assert(head.quads > 0 && head.quads < 8192);
// Store header info
alphaSize = head.alphaSize;
// Read all the quads
quadList = new QuadInfo[head.quads];
ifile.fillArray(quadList);
// Create an index of all the quads
foreach(int index, qn; quadList)
{
int x = qn.cellX;
int y = qn.cellY;
int l = qn.level;
assert(l >= 1);
quadMap[l][x][y] = index;
assert(index == quadMap[l][x][y]);
// Store the root quad
if(l == head.rootLevel)
{
assert(rootQuad == null);
rootQuad = &quadList[index];
}
else
assert(l < head.rootLevel);
}
// Make sure the root was set
assert(rootQuad !is null);
// Next read the string table. First read the main string buffer.
stringBuf = new char[head.stringSize];
ifile.fillArray(stringBuf);
// Then read the string offsets
int[] offsets = new int[head.stringNum];
ifile.fillArray(offsets);
// Set up the string table
char *strptr = stringBuf.ptr;
strings.length = head.stringNum;
foreach(int i, ref str; strings)
{
// toString(char*) returns the string up to the zero
// terminator byte
str = toString(strptr + offsets[i]);
assert(str.ptr + str.length <=
stringBuf.ptr + stringBuf.length);
}
delete offsets;
// Read the vertex buffer data
int bufNum = head.rootLevel;
assert(bufNum == 7);
vertBufData.length = bufNum;
// Fill the vertex buffers. Start at level 1.
for(int i=1;i<bufNum;i++)
{
// Vertex buffer
ifile.readArray(vertBufData[i]);
}
// Index buffer
ifile.readArray(indexBufData);
}
bool hasQuad(int X, int Y, int level)
{
if((level in quadMap) is null ||
(X in quadMap[level]) is null ||
(Y in quadMap[level][X]) is null)
return false;
return true;
}
// Get info about a given quad from the index.
QuadInfo *getQuad(int X, int Y, int level)
{
assert(hasQuad(X,Y,level), format("Cannot find quad %s %s level %s",
X, Y, level));
int ind = quadMap[level][X][Y];
QuadInfo *res = &quadList[ind];
assert(res);
return res;
}
// Maps the terrain and material info for a given quad into
// memory. This is typically called right before the meshes are
// created.
void mapQuad(QuadInfo *info)
{
assert(info);
// Store the quad for later
curQuad = info;
doMap(info.offset, info.size);
}
// Get the info struct for a given segment. Remembers the MeshInfo
// for all later calls.
MeshInfo *getMeshInfo(int segNum)
{
assert(curQuad);
assert(segNum < curQuad.meshNum);
// The mesh headers are at the beginning of the mapped segment.
curMesh = cast(MeshInfo*) getRelSlice(0, MeshInfo.sizeof*curQuad.meshNum);
curMesh += segNum;
return curMesh;
}
float[] getVertexBuffer(int level)
{
assert(level>=1 && level<vertBufData.length);
return vertBufData[level];
}
ushort[] getIndexBuffer()
{
return indexBufData;
}
private:
// All quad headers (from the index) are stored in this array
QuadInfo quadList[];
// A map of all quads. Contain indices to the above array. Indexed
// by [level][X][Y].
int[int][int][int] quadMap;
// These contain pregenerated mesh data that is common for all
// meshes on a given level.
float[][] vertBufData;
ushort[] indexBufData;
// Used for the mmapped file
MmFile mmf;
ubyte mapped[];
// Stores the string table
char[] stringBuf;
char[][] strings;
// Texture size of the alpha maps.
int alphaSize;
char[] getString(int index)
{
assert(index >= 0);
assert(index < strings.length);
return strings[index];
}
void doMap(size_t offset, size_t size)
{
assert(mmf !is null);
assert(size);
mapped = cast(ubyte[])mmf[offset..offset+size];
assert(mapped.length == size);
}
// Get a slice of a given buffer within the mapped window. The
// offset is relative to the start of the window, and the size must
// fit inside the window.
ubyte[] getRelSlice(size_t offset, size_t size)
{
assert(mapped.length);
return mapped[offset..offset+size];
}
// Copy a given buffer from the file. The buffer might be a
// compressed stream, so it's important that the buffers are written
// in the same block sizes as they are read. (Ie. you can't write a
// buffer as one operation and read it as two, or vice versa. Also,
// buffers cannot overlap.) The offset is relative to the current
// mapped file window.
void copy(void *dst, size_t offset, size_t inSize)
{
ubyte source[] = getRelSlice(offset, inSize);
// Just copy it for now
ubyte* dest = cast(ubyte*)dst;
dest[0..source.length] = source[];
}
}

@ -1,27 +0,0 @@
module terrain.bindings;
alias void *SceneNode;
alias void *Bounds;
alias void *MeshObj;
// These are all defined in cpp_terrain.cpp:
extern(C):
SceneNode terr_createChildNode(float relX, float relY, SceneNode);
void terr_destroyNode(SceneNode);
Bounds terr_makeBounds(float minHeight, float maxHeight, float width, SceneNode);
void terr_killBounds(Bounds);
float terr_getSqCamDist(Bounds);
MeshObj terr_makeMesh(SceneNode,void*,int,float);
void terr_killMesh(MeshObj);
void terr_genData();
void terr_setupRendering();
void terr_makeLandMaterial(char*,float);
ubyte *terr_makeAlphaLayer(char*,int);
void terr_closeAlpha(char*,char*,float);
void terr_cleanupAlpha(char*,void*,int);
void terr_resize(void*,void*,int,int);
void terr_saveImage(void*,int,char*);

@ -1,337 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2009 Nicolay Korslund
WWW: http://openmw.sourceforge.net/
This file (cachewriter.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module terrain.cachewriter;
import terrain.archive;
import terrain.outbuffer;
import std.stdio, std.stream, std.string;
import terrain.myfile;
import std.math2;
import monster.util.string;
import monster.vm.dbg;
// Helper structs
struct AlphaHolder
{
AlphaInfo info;
// Actual pixel buffer
ubyte[] buffer;
}
struct MeshHolder
{
MeshInfo info;
// Actual buffers
byte[] vertexBuffer;
// Alpha maps (if any)
AlphaHolder alphas[];
}
// A struct that gathers all the relevant quad data in one place.
struct QuadHolder
{
QuadInfo info;
MeshHolder meshes[];
}
struct CacheWriter
{
// Opens the main archive file for output
void openFile(char[] fname)
{
mainFile = new File(fname, FileMode.OutNew);
iname = fname ~ ".index";
buf = new OutBuffer;
}
void setParams(int mxLev, int alphSize)
{
maxLevel = mxLev;
alphaSize = alphSize;
vertBuf.length = maxLevel;
}
// Closes the main archive file and writes the index.
void finish()
{
mainFile.close();
// Write the index file
scope MyFile ofile = new MyFile(iname, FileMode.OutNew);
// Header first
ArchiveHeader head;
head.magic = CACHE_MAGIC;
head.quads = quadList.length;
head.rootLevel = maxLevel;
head.alphaSize = alphaSize;
head.stringNum = stringList.length;
head.stringSize = totalStringLength;
ofile.dump(head);
// Write the quads
ofile.dumpArray(quadList);
// String table next. We need to sort it in order of the indices
// first.
char[][] strVector;
strVector.length = head.stringNum;
foreach(char[] key, int value; stringList)
strVector[value] = key;
// Next, write the strings to file while we fill in the offset
// list
int[] offsets = new int[head.stringNum];
size_t curOffs = 0;
for(int i=0; i<head.stringNum; i++)
{
// Add one byte for the zero terminator
int len = strVector[i].length + 1;
char *ptr = strVector[i].ptr;
if(ptr[len-1] != 0)
ptr = toStringz(strVector[i]);
assert(ptr[len-1] == 0);
ofile.writeExact(ptr, len);
// Store the offset
offsets[i] = curOffs;
curOffs += len;
}
// At the end the offset should match the buffer size we set in
// the header.
assert(curOffs == head.stringSize);
// Finally, write the offset table itself
ofile.dumpArray(offsets);
// Write the common vertex and index buffers
assert(maxLevel == 7);
for(int i=1;i<maxLevel;i++)
{
// Write vertex buffer
ofile.writeArray(vertBuf[i]);
delete vertBuf[i];
}
// Then the index buffer
ofile.writeArray(indexBuf);
// Don't need these anymore
delete offsets;
delete strVector;
delete quadList;
delete vertBuf;
delete indexBuf;
delete buf;
delete mainFile;
}
// Add a common vertex buffer for a given level
void addVertexBuffer(int level, float[] buf)
{
assert(vertBuf.length > level);
vertBuf[level] = buf;
}
// Add a common index buffer
void setIndexBuffer(ushort[] buf)
{
indexBuf = buf;
}
// Write a finished quad to the archive file. All the offsets and
// numbers in the *Info structs are filled in automatically based on
// the additional data in the Holder structs.
void writeQuad(ref QuadHolder qh)
{
scope auto _trc = new MTrace("writeQuad");
// Write the MeshInfo's first
int meshNum = qh.meshes.length;
MeshInfo meshes[] = buf.write!(MeshInfo)(meshNum);
float minh = float.infinity;
float maxh = -float.infinity;
// Then write the mesh data in approximately the order it's read
for(int i=0; i<meshNum; i++)
{
assert(meshes !is null);
auto mh = &qh.meshes[i];
// Copy the basic data first
meshes[i] = mh.info;
minh = min(minh,mh.info.minHeight);
maxh = max(maxh,mh.info.maxHeight);
// Set everything else except the offsets
int alphaNum = mh.alphas.length;
meshes[i].alphaNum = alphaNum;
// Write the vertex buffer
meshes[i].vertBufOffset = buf.size;
meshes[i].vertBufSize = mh.vertexBuffer.length;
writeBuf(mh.vertexBuffer);
assert(buf.size == meshes[i].vertBufOffset + meshes[i].vertBufSize);
// Next write the alpha maps, if any
meshes[i].alphaOffset = buf.size;
AlphaInfo ais[] = buf.write!(AlphaInfo)(alphaNum);
// Loop through the alpha maps
foreach(int k, ref ai; ais)
{
AlphaHolder ah = mh.alphas[k];
ai = ah.info;
// Write the alpha pixel buffer
ai.bufOffset = buf.size;
ai.bufSize = ah.buffer.length;
writeBuf(ah.buffer);
}
}
// Finally set up the QuadInfo itself
QuadInfo qi;
// Copy basic info
qi = qh.info;
// Derived info
qi.meshNum = meshNum;
qi.offset = fileOffset;
qi.size = buf.size;
qi.minHeight = minh;
qi.maxHeight = maxh;
// Get the side length, or the height difference if that is bigger
qi.boundingRadius = max(maxh-minh,qi.worldWidth);
// Multiply with roughly sqrt(1/2), converts from side length to
// radius with some extra slack
qi.boundingRadius *= 0.8;
// The quad cache is done, write it to file
buf.writeTo(mainFile);
// Update the main offset
fileOffset += qi.size;
// Add the quad to the list. This list isn't written to the main
// cache file, but to the index file.
quadList ~= qi;
}
// Add a texture name as a string. Will convert .tga file names to
// .dds as a convenience. TODO: Use the resource system to do this,
// it automatically searches for the dds variant.
int addTexture(char[] orig)
{
if(orig.iEnds(".tga"))
orig = orig[0..$-3] ~ "dds";
return addString(orig);
}
// Convert a string to an index
int addString(char[] str)
{
// Do we already have the string?
if(str in stringList)
return stringList[str];
// Nope, insert it
int index = stringList.length;
stringList[str] = index;
stringLookup[index] = str;
// Sum up the string lengths + 1 byte for the zero
totalStringLength += str.length + 1;
return index;
}
char[] getString(int index)
{
char[] res = stringLookup[index];
assert(stringList[res] == index);
return res;
}
private:
// Write the given block of memory to 'buf', possibly compressing
// the data.
void writeBuf(void[] ptr)
{
ulong size = ptr.length;
// Reserve the maximum bytes needed.
void toPtr[] = buf.reserve(size);
// Store the data
toPtr[] = ptr[];
// Add the result buffer
buf.add(toPtr[0..size]);
}
// Used for 'writing' to a changable memory buffer before writing to
// file
OutBuffer buf;
// Common vertex and index buffers for all quads. One buffer per
// level.
float[][] vertBuf;
ushort[] indexBuf;
// Variables that must be set during the gen phase
int maxLevel;
int alphaSize;
// Contains a unique index for each string
int[char[]] stringList;
char[][int] stringLookup;
size_t totalStringLength;
// List of all quads
QuadInfo[] quadList;
// Output file
File mainFile;
size_t fileOffset;
// Index file name
char[] iname;
}

@ -1,101 +0,0 @@
class BaseLand
{
public:
BaseLand()
{
createMesh();
}
~BaseLand()
{
destroyMesh();
}
// Repositions the mesh based on camera location
void update()
{
Ogre::Real vd = mCamera->getFarClipDistance();
// Recreate the mesh if the view distance has increased
if ( vd > mMeshDistance )
{
destroyMesh();
createMesh();
}
Ogre::Vector3 p = mCamera->getDerivedPosition();
p.x -= ((int)p.x % CELL_WIDTH);
p.z -= ((int)p.z % CELL_WIDTH);
float h = (p.y + 2048)*2.0/CELL_WIDTH;
h *= h;
mNode->setPosition(p.x, -p.z, -32 -h);
}
private:
void createMesh()
{
float vd = mCamera->getFarClipDistance();
mMeshDistance = vd;
vd = vd/CELL_WIDTH * 32;
mMat = Ogre::MaterialManager::getSingleton().
create("BaseLandMat",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::TextureUnitState* us = mMat->getTechnique(0)->getPass(0)->createTextureUnitState("_land_default.dds");
us->setTextureScale(1.0f/vd,1.0f/vd);
mMat->getTechnique(0)->getPass(0)->setDepthBias(-1);
mObject = mSceneMgr->createManualObject("BaseLand");
mObject->begin("BaseLandMat", Ogre::RenderOperation::OT_TRIANGLE_LIST);
vd = mMeshDistance;
const int HEIGHT = -2048 - 10;
mObject->position(-vd,vd,HEIGHT);
mObject->textureCoord(0, 1);
mObject->position(-vd,-vd,HEIGHT);
mObject->textureCoord(0, 0);
mObject->position(vd,-vd,HEIGHT);
mObject->textureCoord(1, 0);
mObject->position(vd,vd,HEIGHT);
mObject->textureCoord(1, 1);
mObject->quad(0,1,2,3);
mObject->end();
mNode = g_rootTerrainNode->createChildSceneNode();
mNode->attachObject(mObject);
}
void destroyMesh()
{
mNode->detachAllObjects();
mSceneMgr->destroyManualObject(mObject);
mNode->getParentSceneNode()->removeAndDestroyChild(mNode->getName());
mMat->getCreator()->remove(mMat->getHandle());
mMat = Ogre::MaterialPtr();
}
///the created mesh
Ogre::ManualObject* mObject;
///The material for the mesh
Ogre::MaterialPtr mMat;
///scene node for the mesh
Ogre::SceneNode* mNode;
///In essence, the farViewDistance of the camera last frame
Ogre::Real mMeshDistance;
};

@ -1,286 +0,0 @@
// The Ogre renderable used to hold and display the terrain meshes.
class TerrainMesh : public Ogre::Renderable, public Ogre::MovableObject
{
public:
TerrainMesh(Ogre::SceneNode *parent, const MeshInfo &info,
int level, float scale)
: Ogre::Renderable(),
Ogre::MovableObject()
{
TRACE("TerrainMesh()");
mLevel = level;
// This is a bit messy, with everything in one function. We could
// split it up later.
// Use MW coordinates all the way
assert(info.worldWidth > 0);
assert(info.minHeight <= info.maxHeight);
mBounds.setExtents(0,0,info.minHeight,
info.worldWidth, info.worldWidth,
info.maxHeight);
mCenter = mBounds.getCenter();
mBoundingRadius = mBounds.getHalfSize().length();
// TODO: VertexData has a clone() function. This probably means we
// can set this up once and then clone it, to get a completely
// unnoticable increase in performance :)
mVertices = new VertexData();
mVertices->vertexStart = 0;
mVertices->vertexCount = info.vertRows*info.vertCols;
VertexDeclaration* vertexDecl = mVertices->vertexDeclaration;
size_t currOffset = 0;
vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_POSITION);
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_NORMAL);
currOffset += VertexElement::getTypeSize(VET_FLOAT3);
vertexDecl->addElement(0, currOffset, VET_FLOAT2,
VES_TEXTURE_COORDINATES, 0);
currOffset += VertexElement::getTypeSize(VET_FLOAT2);
assert(vertexDecl->getVertexSize(0) == currOffset);
HardwareVertexBufferSharedPtr mMainBuffer;
mMainBuffer = HardwareBufferManager::getSingleton().createVertexBuffer
(
vertexDecl->getVertexSize(0), // size of one whole vertex
mVertices->vertexCount, // number of vertices
HardwareBuffer::HBU_STATIC_WRITE_ONLY, // usage
false); // no shadow buffer
// Bind the data
mVertices->vertexBufferBinding->setBinding(0, mMainBuffer);
// Fill the buffer
float* verts = static_cast<float*>
(mMainBuffer->lock(HardwareBuffer::HBL_DISCARD));
info.fillVertexBuffer(verts,8*mVertices->vertexCount);
mMainBuffer->unlock();
// Create the index data holder
mIndices = new IndexData();
mIndices->indexCount = 64*64*6; // TODO: Shouldn't be hard-coded
mIndices->indexBuffer =
HardwareBufferManager::getSingleton().createIndexBuffer
( HardwareIndexBuffer::IT_16BIT,
mIndices->indexCount,
HardwareBuffer::HBU_STATIC_WRITE_ONLY,
false);
// Fill the buffer with warm fuzzy archive data
unsigned short* indices = static_cast<unsigned short*>
(mIndices->indexBuffer->lock
(0, mIndices->indexBuffer->getSizeInBytes(),
HardwareBuffer::HBL_DISCARD));
info.fillIndexBuffer(indices,mIndices->indexCount);
mIndices->indexBuffer->unlock();
// Finally, create the material
const std::string texName = info.getTexName();
// Set up the scene node.
mNode = parent->createChildSceneNode();
mNode->attachObject(this);
// Finally, create or retrieve the material
if(MaterialManager::getSingleton().resourceExists(texName))
{
mMaterial = MaterialManager::getSingleton().getByName
(texName);
return;
}
// No existing material. Create a new one.
mMaterial = MaterialManager::getSingleton().create
(texName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Pass* pass = mMaterial->getTechnique(0)->getPass(0);
pass->setLightingEnabled(false);
if(level > 1)
{
// This material just has a normal texture
pass->createTextureUnitState(texName)
//->setTextureAddressingMode(TextureUnitState::TAM_CLAMP)
;
}
else
{
assert(level == 1);
// Get the background texture. TODO: We should get this from
// somewhere, no file names should be hard coded. The texture
// might exist as a .tga in earlier versions of the game, and
// we might also want to specify a different background
// texture on some meshes.
//const char *bgTex = info.getBackgroundTex();
const char *bgTex = "_land_default.dds";
pass->createTextureUnitState(bgTex)
->setTextureScale(scale,scale);
// Loop through all the textures in this mesh
for(int tnum=0; tnum<info.alphaNum; tnum++)
{
const AlphaInfo &alpha = *info.getAlphaInfo(tnum);
// Name of the alpha map texture to create
std::string alphaName = alpha.getAlphaName();
// Name of the texture
std::string tname = alpha.getTexName();
// Create the alpha texture if it doesn't exist
if(!TextureManager::getSingleton().resourceExists(alphaName))
{
TexturePtr texPtr = Ogre::TextureManager::
getSingleton().createManual
(alphaName,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D,
g_alphaSize,g_alphaSize,
1,0, // depth, mipmaps
Ogre::PF_A8, // One-channel alpha
Ogre::TU_STATIC_WRITE_ONLY);
// Get the pointer
Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texPtr->getBuffer();
pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
Ogre::uint8* pDest = static_cast<Ogre::uint8*>(pixelBox.data);
// Copy alpha data from file
alpha.fillAlphaBuffer(pDest,g_alphaSize*g_alphaSize);
// Close the buffer
pixelBuffer->unlock();
}
pass = mMaterial->getTechnique(0)->createPass();
pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
pass->setLightingEnabled(false);
pass->setDepthFunction(Ogre::CMPF_EQUAL);
Ogre::TextureUnitState* tus = pass->createTextureUnitState(alphaName);
//tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setIsAlpha(true);
// Add the actual texture on top of the alpha map.
tus = pass->createTextureUnitState(tname);
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_CURRENT);
tus->setTextureScale(scale, scale);
}
}
}
~TerrainMesh()
{
assert(mNode);
mNode->detachAllObjects();
mNode->getCreator()->destroySceneNode(mNode);
// TODO: This still crashes on level1 meshes. Find out why!
if(mLevel!=1)delete mVertices;
delete mIndices;
}
//-----------------------------------------------------------------------
// These are all Ogre functions that we have to override
//-----------------------------------------------------------------------
// Internal Ogre function. We should call visitor->visit on all
// Renderables that are part of this object. In our case, this is
// only ourselves.
void visitRenderables(Renderable::Visitor* visitor,
bool debugRenderables = false) {
visitor->visit(this, 0, false);
}
void getRenderOperation( Ogre::RenderOperation& op ) {
op.useIndexes = true;
op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
op.vertexData = mVertices;
op.indexData = mIndices;
}
void getWorldTransforms( Ogre::Matrix4* xform ) const {
*xform = mNode->_getFullTransform();
}
const Ogre::Quaternion& getWorldOrientation(void) const {
return mNode->_getDerivedOrientation();
}
const Ogre::Vector3& getWorldPosition(void) const {
return mNode->_getDerivedPosition();
}
Ogre::Real getSquaredViewDepth(const Ogre::Camera *cam) const {
Ogre::Vector3 diff = mCenter - cam->getDerivedPosition();
// Use squared length to avoid square root
return diff.squaredLength();
}
const Ogre::LightList& getLights(void) const {
if (mLightListDirty) {
getParentSceneNode()->getCreator()->_populateLightList
(mCenter, mBoundingRadius, mLightList);
mLightListDirty = false;
}
return mLightList;
}
virtual const Ogre::String& getMovableType( void ) const {
static Ogre::String t = "MW_TERRAIN";
return t;
}
void _updateRenderQueue( Ogre::RenderQueue* queue ) {
mLightListDirty = true;
queue->addRenderable(this, mRenderQueueID);
}
const Ogre::AxisAlignedBox& getBoundingBox( void ) const
{
return mBounds;
}
Ogre::Real getBoundingRadius(void) const {
return mBoundingRadius;
}
virtual const MaterialPtr& getMaterial(void) const
{ return mMaterial; }
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
private:
int mLevel;
Ogre::SceneNode* mNode;
Ogre::MaterialPtr mMaterial;
Ogre::VertexData* mVertices;
Ogre::IndexData* mIndices;
mutable bool mLightListDirty;
Ogre::Real mBoundingRadius;
Ogre::AxisAlignedBox mBounds;
Ogre::Vector3 mCenter;
};

@ -1,413 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2009 Jacob Essex, Nicolay Korslund
WWW: http://openmw.sourceforge.net/
This file (cpp_terrain.cpp) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
const int CELL_WIDTH = 8192;
SceneNode *g_rootTerrainNode;
int g_alphaSize;
struct MeshInfo;
struct AlphaInfo;
// D functions
extern "C"
{
void d_terr_superman();
void d_terr_terrainUpdate();
char *d_terr_getTexName(int32_t);
void d_terr_fillVertexBuffer(const MeshInfo*,float*,uint64_t);
void d_terr_fillIndexBuffer(const MeshInfo*,uint16_t*,uint64_t);
AlphaInfo *d_terr_getAlphaInfo(const MeshInfo*,int32_t);
void d_terr_fillAlphaBuffer(const AlphaInfo*,uint8_t*,uint64_t);
int32_t d_terr_getAlphaSize();
}
// Info about a submesh. This is a clone of the struct defined in
// archive.d. TODO: Make sure the D and C++ structs are of the same
// size and alignment.
struct MeshInfo
{
// Bounding box info
float minHeight, maxHeight;
float worldWidth;
// Vertex and index numbers
int32_t vertRows, vertCols;
// Height offset to apply to all vertices
float heightOffset;
// Size and offset of the vertex buffer
int64_t vertBufSize, vertBufOffset;
// Texture name. Index to the string table.
int32_t texName;
// Number and offset of AlphaInfo blocks
int32_t alphaNum;
uint64_t alphaOffset;
inline void fillVertexBuffer(float *buffer, uint64_t size) const
{
d_terr_fillVertexBuffer(this, buffer, size);
}
inline void fillIndexBuffer(uint16_t *buffer, uint64_t size) const
{
d_terr_fillIndexBuffer(this, buffer, size);
}
inline char* getTexName() const
{
return d_terr_getTexName(texName);
}
inline AlphaInfo *getAlphaInfo(int tnum) const
{
return d_terr_getAlphaInfo(this, tnum);
}
};
// Info about an alpha map belonging to a mesh
struct AlphaInfo
{
// Position of the actual image data
uint64_t bufSize, bufOffset;
// The texture name for this layer. The actual string is stored in
// the archive's string buffer.
int32_t texName;
int32_t alphaName;
inline char* getTexName() const
{
return d_terr_getTexName(texName);
}
inline char* getAlphaName() const
{
return d_terr_getTexName(alphaName);
}
inline void fillAlphaBuffer(uint8_t *buffer, uint64_t size) const
{
return d_terr_fillAlphaBuffer(this, buffer, size);
}
};
#include "cpp_baseland.cpp"
#include "cpp_mesh.cpp"
BaseLand *g_baseLand;
class TerrainFrameListener : public FrameListener
{
protected:
bool frameEnded(const FrameEvent& evt)
{
TRACE("Terrain frame");
d_terr_terrainUpdate();
if(g_baseLand)
g_baseLand->update();
return true;
}
};
// Renders a material into a texture
Ogre::TexturePtr getRenderedTexture(Ogre::MaterialPtr mp,
const std::string& name,
int texSize, Ogre::PixelFormat tt)
{
Ogre::CompositorPtr cp = Ogre::CompositorManager::getSingleton().
create("Rtt_Comp",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::CompositionTargetPass* ctp = cp->createTechnique()->getOutputTargetPass();
Ogre::CompositionPass* cpass = ctp->createPass();
cpass->setType(Ogre::CompositionPass::PT_RENDERQUAD);
cpass->setMaterial(mp);
// Create the destination texture
Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().
createManual(name + "_T",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D,
texSize,
texSize,
0,
tt,
Ogre::TU_RENDERTARGET
);
Ogre::RenderTexture* renderTexture = texture->getBuffer()->getRenderTarget();
Ogre::Viewport* vp = renderTexture->addViewport(mCamera);
Ogre::CompositorManager::getSingleton().addCompositor(vp, "Rtt_Comp");
Ogre::CompositorManager::getSingleton().setCompositorEnabled(vp,"Rtt_Comp", true);
renderTexture->update();
// Call the OGRE renderer.
Ogre::Root::getSingleton().renderOneFrame();
Ogre::CompositorManager::getSingleton().removeCompositor(vp, "Rtt_Comp");
Ogre::CompositorManager::getSingleton().remove(cp->getHandle());
renderTexture->removeAllViewports();
return texture;
}
// These are used between some functions below. Kinda messy. Since
// these are GLOBAL instances, they are terminated at program
// exit. However, OGRE itself is terminated before that, so we have to
// make sure we have no 'active' shared pointers after OGRE is
// finished (otherwise we get a segfault at exit.)
std::list<Ogre::ResourcePtr> createdResources;
Ogre::HardwarePixelBuffer *pixelBuffer;
MaterialPtr mat;
// Functions called from D
extern "C"
{
SceneNode* terr_createChildNode(float x, float y,
SceneNode *parent)
{
Ogre::Vector3 pos(x,y,0);
if(parent == NULL)
parent = g_rootTerrainNode;
assert(parent);
return parent->createChildSceneNode(pos);
}
void terr_destroyNode(SceneNode *node)
{
node->removeAndDestroyAllChildren();
mSceneMgr->destroySceneNode(node);
}
// TODO: We could make allocation a little more refined than new and
// delete. But that's true for everything here. A freelist based
// approach is best in most of these cases, as we have continuous
// allocation/deallocation of fixed-size structs.
Ogre::AxisAlignedBox *terr_makeBounds(float minHeight, float maxHeight,
float width, SceneNode* node)
{
TRACE("terr_makeBounds");
AxisAlignedBox *mBounds = new AxisAlignedBox;
assert(maxHeight >= minHeight);
mBounds->setExtents(0,0,minHeight,
width,width,maxHeight);
// Transform the box to world coordinates, so it can be compared
// with the camera later.
mBounds->transformAffine(node->_getFullTransform());
return mBounds;
}
void terr_killBounds(AxisAlignedBox *bounds)
{
TRACE("terr_killBounds");
delete bounds;
}
float terr_getSqCamDist(AxisAlignedBox *mBounds)
{
TRACE("terr_getSqCamDist");
Ogre::Vector3 cpos = mCamera->getDerivedPosition();
Ogre::Vector3 diff(0, 0, 0);
diff.makeFloor(cpos - mBounds->getMinimum() );
diff.makeCeil(cpos - mBounds->getMaximum() );
return diff.squaredLength();
}
TerrainMesh *terr_makeMesh(SceneNode *parent,
MeshInfo *info,
int level, float scale)
{
return new TerrainMesh(parent, *info, level, scale);
}
void terr_killMesh(TerrainMesh *mesh)
{
TRACE("terr_killMesh");
delete mesh;
}
// Set up the rendering system
void terr_setupRendering()
{
TRACE("terr_setupRendering()");
// Make sure the C++ sizes match the D sizes, since the structs
// are shared between the two.
assert(sizeof(MeshInfo) == 14*4);
assert(sizeof(AlphaInfo) == 6*4);
// Add the terrain directory as a resource location. TODO: Get the
// name from D.
ResourceGroupManager::getSingleton().
addResourceLocation("cache/terrain/", "FileSystem", "General");
// Enter superman mode
mCamera->setFarClipDistance(40*CELL_WIDTH);
//ogre_setFog(0.7, 0.7, 0.7, 200, 32*CELL_WIDTH);
d_terr_superman();
// Create a root scene node first. The 'root' node is rotated to
// match the MW coordinate system
g_rootTerrainNode = mwRoot->createChildSceneNode("TERRAIN_ROOT");
// Add the base land. This is the ground beneath the actual
// terrain mesh that makes the terrain look infinite.
//g_baseLand = new BaseLand();
g_alphaSize = d_terr_getAlphaSize();
// Add the frame listener
mRoot->addFrameListener(new TerrainFrameListener);
}
// The next four functions are called in the function genLevel2Map()
// only. This is very top-down-programming-ish and a bit messy, but
// that's what I get for mixing C++ and D like this.
void terr_makeLandMaterial(const char* name, float scale)
{
// Get a new material
mat = Ogre::MaterialManager::getSingleton().
create(name,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
// Put the default texture in the bottom 'layer', so that we don't
// end up seeing through the landscape.
Ogre::Pass* np = mat->getTechnique(0)->getPass(0);
np->setLightingEnabled(false);
np->createTextureUnitState("_land_default.dds")
->setTextureScale(scale,scale);
}
uint8_t *terr_makeAlphaLayer(const char* name, int32_t width)
{
// Create alpha map for this texture.
Ogre::TexturePtr texPtr = Ogre::TextureManager::getSingleton().
createManual(name,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D,
width, width,
1,0, // depth, mipmaps
Ogre::PF_A8, // One-channel alpha
Ogre::TU_STATIC_WRITE_ONLY);
createdResources.push_back(texPtr);
pixelBuffer = texPtr->getBuffer().get();
pixelBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();
return static_cast<Ogre::uint8*>(pixelBox.data);
}
void terr_closeAlpha(const char *alphaName,
const char *texName, float scale)
{
// Close the alpha pixel buffer opened in the previous function
pixelBuffer->unlock();
// Create a pass containing the alpha map
Pass *np = mat->getTechnique(0)->createPass();
np->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
np->setLightingEnabled(false);
np->setDepthFunction(Ogre::CMPF_EQUAL);
Ogre::TextureUnitState* tus = np->createTextureUnitState(alphaName);
tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
// Set various blending options
tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setIsAlpha(true);
// Add the terrain texture to the pass and scale it.
tus = np->createTextureUnitState(texName);
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_CURRENT);
tus->setTextureScale(scale, scale);
}
// Clean up after the above functions, render the material to
// texture and save the data in outdata and in the file outname.
void terr_cleanupAlpha(const char *outname,
void *outData, int32_t toSize)
{
TexturePtr tex1 = getRenderedTexture(mat,outname,
toSize,Ogre::PF_R8G8B8);
// Blit the texture into the given memory buffer
PixelBox pb = PixelBox(toSize, toSize, 1, PF_R8G8B8);
pb.data = outData;
tex1->getBuffer()->blitToMemory(pb);
// Clean up
TextureManager::getSingleton().remove(tex1->getHandle());
const std::list<Ogre::ResourcePtr>::const_iterator iend = createdResources.end();
for ( std::list<Ogre::ResourcePtr>::const_iterator itr = createdResources.begin();
itr != iend;
++itr)
(*itr)->getCreator()->remove((*itr)->getHandle());
createdResources.clear();
MaterialManager::getSingleton().remove(mat->getHandle());
mat.setNull();
}
void terr_resize(void* srcPtr, void* dstPtr, int32_t fromW, int32_t toW)
{
// Create pixelboxes
PixelBox src = PixelBox(fromW, fromW, 1, PF_R8G8B8);
PixelBox dst = PixelBox(toW, toW, 1, PF_R8G8B8);
src.data = srcPtr;
dst.data = dstPtr;
// Resize the image. The nearest neighbour filter makes sure
// there is no blurring.
Image::scale(src, dst, Ogre::Image::FILTER_NEAREST);
}
void terr_saveImage(void *data, int32_t width, const char* name)
{
Image img;
img.loadDynamicImage((uchar*)data, width, width, PF_R8G8B8);
img.save(name);
}
}

@ -1,158 +0,0 @@
module terrain.esmland;
import esm.loadltex;
import esm.loadcell;
import util.regions;
import esm.filereader;
import std.stdio;
const int LAND_NUM_VERTS = 65*65;
MWLand mwland;
// Interface to the ESM landscape data
struct MWLand
{
RegionManager reg;
// These structs/types represent the way data is actually stored in
// the ESM files.
// Heightmap
align(1)
struct VHGT
{
float heightOffset;
byte heightData[LAND_NUM_VERTS];
short unknown1;
char unknown2;
}
// Normals
typedef byte[LAND_NUM_VERTS*3] VNML;
// Land textures. This is organized in 4x4 buffers of 4x4 squares
// each. This is how the original engine splits up the cell meshes,
// and it's probably a good idea for us to do the same.
typedef short[4][4][4][4] VTEX;
static assert(VHGT.sizeof == 4232);
static assert(VNML.sizeof == 12675);
static assert(VTEX.sizeof == 512);
// Landscape data for one cell
struct LandData
{
VHGT vhgt;
VNML normals;
}
// Texture data for one cell
struct LTEXData
{
// Global list of land textures from the source ES file
LandTextureList.TextureList source;
// Texture indices for this cell
VTEX vtex;
// Get the texture x2,y2 from the sub map x1,x2
char[] getTexture(int x1, int y1, int x2, int y2)
{
// Get the texture index relative to the current esm/esp file
short texID = vtex[y1][x1][y2][x2] - 1;
if(texID == -1)
return null;
// Return the 'new' texture name. This name was automatically
// been converted to .dds at load time if the .tga file was not
// found.
assert(source !is null);
assert(texID >= 0 && texID < source.length);
return source[texID].getNewName();
}
// Get a texture from the 16x16 grid in one cell
char[] getTexture(int x, int y)
{
return getTexture(x/4,y/4,x%4,y%4);
}
}
// Get the maximum absolute coordinate value in any direction
int getMaxCoord()
{ return cells.maxXY; }
// Does the given cell exist and does it have land data?
bool hasData(int x, int y)
{
// Does the cell exist?
if(!cells.hasExt(x,y))
return false;
// And does it have terrain data?
auto ex = cells.getExt(x,y);
return ex.hasLand();
}
LandData *getLandData(int x, int y)
{
loadCell(x, y);
return &currentLand;
}
LTEXData *getLTEXData(int x, int y)
{
loadCell(x, y);
return &currentLtex;
}
private:
int currentX = -1234;
int currentY = 4321;
LandData currentLand;
LTEXData currentLtex;
// Make sure the given cell is loaded
void loadCell(int x, int y)
{
// If the right cell is already loaded, don't do anything
if(x == currentX && y == currentY)
return;
assert(hasData(x,y));
currentX = x;
currentY = y;
// Get the file context for the terrain data. This can be used to
// skip to the right part of the ESM file.
auto cont = cells.getExt(x,y).land.context;
// Get the land texture list from the file
currentLtex.source = landTextures.files[cont.filename];
// We should use an existing region later, or at least delete this
// once we're done with the gen process.
if(reg is null)
reg = new RegionManager();
// Open the ESM at this cell
esFile.restoreContext(cont, reg);
// Store the cell-specific data
esFile.readHNExact(currentLand.normals.ptr,
currentLand.normals.length, "VNML");
esFile.readHNExact(&currentLand.vhgt, VHGT.sizeof, "VHGT");
// These aren't used yet
if(esFile.isNextSub("WNAM")) esFile.skipHSubSize(81);
if(esFile.isNextSub("VCLR")) esFile.skipHSubSize(12675);
esFile.readHNExact(&currentLtex.vtex, VTEX.sizeof, "VTEX");
}
}

File diff suppressed because it is too large Load Diff

@ -1,71 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2009 Nicolay Korslund
WWW: http://openmw.sourceforge.net/
This file (archive.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module terrain.myfile;
import std.stream;
import std.stdio;
// Add a couple of helper functions to the file stream
class MyFile : File
{
this(string filename, FileMode mode = FileMode.In)
{
super(filename, mode);
}
void fill(T)(ref T t)
{
readExact(&t, T.sizeof);
}
void dump(T)(ref T t)
{
writeExact(&t, T.sizeof);
}
void fillArray(T)(T[] t)
{
readExact(t.ptr, t.length*T.sizeof);
}
void dumpArray(T)(T[] t)
{
writeExact(t.ptr, t.length*T.sizeof);
}
void readArray(T)(ref T[] arr)
{
int size;
read(size);
assert(size < 1024*1024 && size > 0);
arr = new T[size];
fillArray!(T)(arr);
}
void writeArray(T)(T[] t)
{
int size = t.length;
write(size);
dumpArray!(T)(t);
}
}

@ -1,94 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2009 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.sourceforge.net/
This file (outbuffer.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
/*
This files provides a simple buffer class used for writing the cache
files. It lets you 'write' data to a growing memory buffer and
allows you to change the written data after the fact (since it's
retained in memory.) When you're done, you can write the entire
buffer to a stream in one operation.
*/
module terrain.outbuffer;
import util.regions;
import std.stream;
class OutBuffer
{
private:
RegionManager reg;
long used;
void[][] buffers;
public:
this()
{
reg = new RegionManager("Outbuf", 200*1024);
}
void reset()
{
if(buffers.length)
delete buffers;
reg.freeAll();
used = 0;
buffers = null;
}
// Write everyting to a stream as one buffer
void writeTo(Stream str)
{
foreach(void[] v; buffers)
str.writeExact(v.ptr, v.length);
reset();
}
// Get a pointer to a new block at least 'bytes' large, but don't
// add it to the list.
void[] reserve(size_t bytes)
{ return reg.allocate(bytes); }
// Get a new block which is 'bytes' size large.
void[] add(size_t bytes)
{
void[] p = reserve(bytes);
add(p);
return p;
}
// Add an existing block to the write list
void add(void[] p)
{
buffers ~= p;
used += p.length;
}
T[] write(T)(size_t num)
{
return cast(T[])add(num * T.sizeof);
}
size_t size() { return used; }
}

@ -1,286 +0,0 @@
module terrain.quad;
import terrain.archive;
import terrain.bindings;
import std.stdio;
import monster.vm.dbg;
const int CELL_WIDTH = 8192;
const float SPLIT_FACTOR = 0.5;
const float UNSPLIT_FACTOR = 2.0;
class Quad
{
this(int cellX=0, int cellY=0, Quad parent = null)
{
scope auto _trc = new MTrace("Quad.this");
mCellX = cellX;
mCellY = cellY;
// Do we have a parent?
if(parent !is null)
{
mLevel = parent.mLevel-1;
// Coordinates relative to our parent
int relX = cellX - parent.mCellX;
int relY = cellY - parent.mCellY;
// The coordinates give the top left corner of the quad, or our
// relative coordinates within that should always be positive.
assert(relX >= 0);
assert(relY >= 0);
// Create a child scene node. The scene node position is given in
// world units, ie. CELL_WIDTH units per cell.
mNode = terr_createChildNode(relX*CELL_WIDTH,
relY*CELL_WIDTH,
parent.mNode);
// Get the archive data for this quad.
mInfo = g_archive.getQuad(mCellX,mCellY,mLevel);
// Set up the bounding box. Use MW coordinates all the
// way.
mBounds = terr_makeBounds(mInfo.minHeight,
mInfo.maxHeight,
mInfo.worldWidth,
mNode);
float radius = mInfo.boundingRadius;
mSplitDistance = radius * SPLIT_FACTOR;
mUnsplitDistance = radius * UNSPLIT_FACTOR;
// Square the distances
mSplitDistance *= mSplitDistance;
mUnsplitDistance *= mUnsplitDistance;
if(mLevel == 1)
{
// Create the terrain and leave it there.
buildTerrain();
isStatic = true;
}
}
else
{
// No parent, this is the top-most quad. Get all the info from
// the archive.
mInfo = g_archive.rootQuad;
assert(mInfo);
mLevel = mInfo.level;
cellX = mCellX = mInfo.cellX;
cellY = mCellY = mInfo.cellY;
mNode = terr_createChildNode(cellX*CELL_WIDTH,
cellY*CELL_WIDTH,
null);
// Split up
split();
// The root can never be unsplit
isStatic = true;
}
assert(mLevel >= 1);
assert(mNode !is null);
// Update the terrain. This will create the mesh or children if
// necessary.
update();
}
~this()
{
scope auto _trc = new MTrace("Quad.~this");
// TODO: We might rewrite the code so that the quads are never
// actually destroyed, just 'inactivated' by hiding their scene
// node. We only call update on our children if we don't have a
// mesh ourselves.
if(hasMesh)
destroyTerrain();
else if(hasChildren)
for (size_t i = 0; i < 4; i++)
delete mChildren[i];
terr_destroyNode(mNode);
if(mBounds !is null)
terr_killBounds(mBounds);
}
// Remove the landscape for this quad, and create children.
void split()
{
scope auto _trc = new MTrace("split");
// Never split a static quad or a quad that already has children.
assert(!isStatic);
assert(!hasChildren);
assert(mLevel > 1);
if(hasMesh)
destroyTerrain();
// Find the cell width of our children
int cWidth = 1 << (mLevel-2);
// Create children
for ( size_t i = 0; i < 4; ++i )
{
if(!mInfo.hasChild[i])
continue;
// The cell coordinates for this child quad
int x = (i%2)*cWidth + mCellX;
int y = (i/2)*cWidth + mCellY;
mChildren[i] = new Quad(x,y,this);
}
hasChildren = true;
}
// Removes children and rebuilds terrain
void unsplit()
{
scope auto _trc = new MTrace("unsplit");
// Never unsplit the root quad
assert(mLevel < g_archive.rootQuad.level);
// Never unsplit a static or quad that isn't split.
assert(!isStatic);
assert(hasChildren);
assert(!hasMesh);
for( size_t i = 0; i < 4; i++ )
{
delete mChildren[i];
mChildren[i] = null;
}
buildTerrain();
hasChildren = false;
}
// Determines whether to split or unsplit the quad, and immediately
// does it.
void update()
{
scope auto _trc = new MTrace("Quad.update()");
// Static quads don't change
if(!isStatic)
{
assert(mUnsplitDistance > mSplitDistance);
// Get (squared) camera distance. TODO: shouldn't this just
// be a simple vector difference from the mesh center?
assert(mBounds !is null);
float camDist = terr_getSqCamDist(mBounds);
// No children?
if(!hasChildren)
{
// If we're close, split now.
if(camDist < mSplitDistance)
split();
else
{
// We're not close, and don't have any children. Should we
// built terrain?
if(!hasMesh)
buildTerrain();
return;
}
}
// If we get here, we either had children when we entered,
// or we just performed a split.
assert(!hasMesh);
assert(hasChildren);
// If the camera is too far away, kill the children.
if(camDist > mUnsplitDistance)
{
unsplit();
return;
}
}
else if(!hasChildren)
return;
// We have children and we're happy about it. Update them too.
for(int i; i < 4; ++i)
{
Quad q = mChildren[i];
if(q !is null) q.update();
}
}
// Build the terrain for this quad
void buildTerrain()
{
scope auto _trc = new MTrace("buildTerrain");
assert(!hasMesh);
assert(!isStatic);
// Map the terrain data into memory.
assert(mInfo);
g_archive.mapQuad(mInfo);
// Create one mesh for each segment in the quad. TerrainMesh takes
// care of the loading.
meshList.length = mInfo.meshNum;
foreach(i, ref m; meshList)
{
MeshInfo *mi = g_archive.getMeshInfo(i);
m = terr_makeMesh(mNode, mi, mInfo.level, TEX_SCALE);
}
hasMesh = true;
}
void destroyTerrain()
{
scope auto _trc = new MTrace("destroyTerrain");
assert(hasMesh);
foreach(m; meshList)
terr_killMesh(m);
meshList[] = null;
hasMesh = false;
}
private:
// List of meshes, if any. The meshes are C++ objects.
MeshObj meshList[];
// Scene node. All child quads are added to this.
SceneNode mNode;
// Bounding box, transformed to world coordinates. Used to calculate
// camera distance.
Bounds mBounds;
float mSplitDistance,mUnsplitDistance;
Quad mChildren[4];
// Contains the 'level' of this node. Level 1 is the closest and
// most detailed level
int mLevel;
int mCellX, mCellY;
QuadInfo *mInfo;
bool hasMesh;
bool hasChildren;
bool isStatic; // Static quads are never split or unsplit
}

@ -1,103 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2009 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (terrain.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
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
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module terrain.terrain;
import terrain.generator;
import terrain.archive;
import terrain.bindings;
import terrain.quad;
import std.file, std.stdio;
char[] cacheDir = "cache/terrain/";
// Enable this to render single terrain meshes instead of the entire
// data set
//debug=singleMesh;
void initTerrain(bool doGen)
{
char[] fname = cacheDir ~ "landscape.cache";
if(!exists(fname))
{
writefln("Cache file '%s' not found. Creating:",
fname);
doGen = true;
}
if(doGen)
generate(fname);
// Load the archive file
g_archive.openFile(fname);
terr_setupRendering();
debug(singleMesh)
{
int X = 22;
int Y = 0;
bool next = false;
void doQuad(int x, int y, int lev, int diffx, int diffy)
{
if(!g_archive.hasQuad(x,y,lev))
return;
diffx *= 8192;
diffy *= 8192;
auto node = terr_createChildNode(20000+diffx,-60000+diffy,null);
auto info = g_archive.getQuad(x,y,lev);
g_archive.mapQuad(info);
auto mi = g_archive.getMeshInfo(0);
terr_makeMesh(node, mi, info.level, TEX_SCALE);
}
doQuad(X,Y,1, 0,0);
doQuad(X+1,Y,1, 1,0);
doQuad(X,Y+1,1, 0,1);
doQuad(X+1,Y+1,1, 1,1);
doQuad(X + (next?2:0),Y,2, 2,0);
doQuad(20,Y,3, 0,2);
}
else
{
// Create the root quad
rootQuad = new Quad;
}
}
extern(C) void d_terr_terrainUpdate()
{
debug(singleMesh) return;
// Update the root quad each frame.
assert(rootQuad !is null);
rootQuad.update();
}
Quad rootQuad;
Loading…
Cancel
Save