Implemented correct bounding box computation in NIFLoader

actorid
Nicolay Korslund 15 years ago
parent 782a90066d
commit 3a0600b84c

@ -34,6 +34,9 @@
// For warning messages
#include <iostream>
// float infinity
#include <limits>
typedef unsigned char ubyte;
using namespace std;
@ -58,6 +61,78 @@ static void warn(const string &msg)
cout << "WARNING (NIF:" << errName << "): " << msg << endl;
}
// Helper class that computes the bounding box and of a mesh
class BoundsFinder
{
struct MaxMinFinder
{
float max, min;
MaxMinFinder()
{
min = numeric_limits<float>::infinity();
max = -min;
}
void add(float f)
{
if(f > max) max = f;
if(f < min) min = f;
}
// Return Max(max**2, min**2)
float getMaxSquared()
{
float m1 = max*max;
float m2 = min*min;
if(m1 >= m2) return m1;
return m2;
}
};
MaxMinFinder X, Y, Z;
public:
// Add 'verts' vertices to the calculation. The 'data' pointer is
// expected to point to 3*verts floats representing x,y,z for each
// point.
void add(float *data, int verts)
{
for(int i=0;i<verts;i++)
{
X.add(*(data++));
Y.add(*(data++));
Z.add(*(data++));
}
}
// True if this structure has valid values
bool isValid()
{
return
minX() <= maxX() &&
minY() <= maxY() &&
minZ() <= maxZ();
}
// Compute radius
float getRadius()
{
assert(isValid());
// The radius is computed from the origin, not from the geometric
// center of the mesh.
return sqrt(X.getMaxSquared() + Y.getMaxSquared() + Z.getMaxSquared());
}
float minX() { return X.min; }
float maxX() { return X.max; }
float minY() { return Y.min; }
float maxY() { return Y.max; }
float minZ() { return Z.min; }
float maxZ() { return Z.max; }
};
// Conversion of blend / test mode from NIF -> OGRE. Not in use yet.
static SceneBlendFactor getBlendFactor(int mode)
{
@ -357,8 +432,10 @@ static void vectorMul(const Matrix &A, float *C)
C[i] = a*A.v[i].array[0] + b*A.v[i].array[1] + c*A.v[i].array[2];
}
static void handleNiTriShape(Mesh *mesh, NiTriShape *shape, int flags)
static void handleNiTriShape(Mesh *mesh, NiTriShape *shape, int flags, BoundsFinder &bounds)
{
assert(shape != NULL);
// Interpret flags
bool hidden = (flags & 0x01) != 0; // Not displayed
bool collide = (flags & 0x02) != 0; // Use mesh for collision
@ -478,47 +555,53 @@ static void handleNiTriShape(Mesh *mesh, NiTriShape *shape, int flags)
alphaFlags, alphaTest, texName);
}
}
}
} // End of material block, if(!hidden) ...
{
/* Do in-place transformation of all the vertices and normals. This
is pretty messy stuff, but we need it to make the sub-meshes
appear in the correct place. Neither Ogre nor Bullet support
nested levels of sub-meshes with transformations applied to each
level.
*/
NiTriShapeData *data = shape->data.getPtr();
int numVerts = data->vertices.length / 3;
/* Do in-place transformation of all the vertices and normals. This
is pretty messy stuff, but we need it to make the sub-meshes
appear in the correct place. Neither Ogre nor Bullet support
nested levels of sub-meshes with transformations applied to each
level.
*/
NiTriShapeData *data = shape->data.getPtr();
int numVerts = data->vertices.length / 3;
float *ptr = (float*)data->vertices.ptr;
float *ptr = (float*)data->vertices.ptr;
float *optr = ptr;
// Rotate, scale and translate all the vertices
const Matrix &rot = shape->trafo->rotation;
const Vector &pos = shape->trafo->pos;
float scale = shape->trafo->scale;
for(int i=0; i<numVerts; i++)
{
vectorMulAdd(rot, pos, ptr, scale);
ptr += 3;
}
// Rotate, scale and translate all the vertices
const Matrix &rot = shape->trafo->rotation;
const Vector &pos = shape->trafo->pos;
float scale = shape->trafo->scale;
for(int i=0; i<numVerts; i++)
{
vectorMulAdd(rot, pos, ptr, scale);
ptr += 3;
}
// Remember to rotate all the vertex normals as well
if(data->normals.length)
{
ptr = (float*)data->normals.ptr;
for(int i=0; i<numVerts; i++)
{
vectorMul(rot, ptr);
ptr += 3;
}
}
}
// Remember to rotate all the vertex normals as well
if(data->normals.length)
{
ptr = (float*)data->normals.ptr;
for(int i=0; i<numVerts; i++)
{
vectorMul(rot, ptr);
ptr += 3;
}
}
if(!hidden)
createOgreMesh(mesh, shape, material);
{
// Add this vertex set to the bounding box
bounds.add(optr, numVerts);
// Create the submesh
createOgreMesh(mesh, shape, material);
}
}
static void handleNode(Mesh* mesh, Nif::Node *node, int flags, const Transformation *trafo = NULL)
static void handleNode(Mesh* mesh, Nif::Node *node, int flags,
const Transformation *trafo, BoundsFinder &bounds)
{
// Accumulate the flags from all the child nodes. This works for all
// the flags we currently use, at least.
@ -554,7 +637,7 @@ static void handleNode(Mesh* mesh, Nif::Node *node, int flags, const Transformat
if(trafo)
{
// Get a non-const reference to the node's data, since we're
// overwriting it.
// overwriting it. TODO: Is this necessary?
Transformation &final = *((Transformation*)node->trafo);
// For both position and rotation we have that:
@ -566,7 +649,7 @@ static void handleNode(Mesh* mesh, Nif::Node *node, int flags, const Transformat
matrixMul(trafo->rotation, final.rotation);
// Scalar values are so nice to deal with. Why can't everything
// just be scalars?
// just be scalar?
final.scale *= trafo->scale;
}
@ -578,12 +661,12 @@ static void handleNode(Mesh* mesh, Nif::Node *node, int flags, const Transformat
for(int i=0; i<n; i++)
{
if(list.has(i))
handleNode(mesh, &list[i], flags, node->trafo);
handleNode(mesh, &list[i], flags, node->trafo, bounds);
}
}
else if(node->recType == RC_NiTriShape)
// For shapes
handleNiTriShape(mesh, (NiTriShape*)node, flags);
handleNiTriShape(mesh, dynamic_cast<NiTriShape*>(node), flags, bounds);
}
void NIFLoader::loadResource(Resource *resource)
@ -604,6 +687,9 @@ void NIFLoader::loadResource(Resource *resource)
return;
}
// Helper that computes bounding boxes for us.
BoundsFinder bounds;
// Load the NIF. TODO: Wrap this in a try-catch block once we're out
// of the early stages of development. Right now we WANT to catch
// every error as early and intrusively as possible, as it's most
@ -620,19 +706,25 @@ void NIFLoader::loadResource(Resource *resource)
Record *r = nif.getRecord(0);
assert(r != NULL);
if(r->recType != RC_NiNode && r->recType != RC_NiTriShape)
Nif::Node *node = dynamic_cast<Nif::Node*>(r);
if(node == NULL)
{
warn("First record in file was not a NiNode, but a " +
warn("First record in file was not a node, but a " +
r->recName.toString() + ". Skipping file.");
return;
}
// Handle the node
handleNode(mesh, (Nif::Node*)r, 0);
handleNode(mesh, node, 0, NULL, bounds);
// Finally, set the bounding value. Just use bogus info right now.
mesh->_setBounds(AxisAlignedBox(-10,-10,-10,10,10,10));
mesh->_setBoundingSphereRadius(10);
// Finally, set the bounding value.
if(bounds.isValid())
{
mesh->_setBounds(AxisAlignedBox(bounds.minX(), bounds.minY(), bounds.minZ(),
bounds.maxX(), bounds.maxY(), bounds.maxZ()));
mesh->_setBoundingSphereRadius(bounds.getRadius());
}
}
MeshPtr NIFLoader::load(const std::string &name,

Loading…
Cancel
Save