openmw-tes3coop/components/nifogre/mesh.cpp
Chris Robinson 59137d93c9 Partially handle NiGeomMorpherController
The morphs aren't actually applied yet, but the Ogre controller is set up so
all that has to be done is to implement the setValue method.
2013-04-22 03:24:02 -07:00

380 lines
13 KiB
C++

#include "mesh.hpp"
#include <limits>
#include <OgreMeshManager.h>
#include <OgreMesh.h>
#include <OgreSubMesh.h>
#include <OgreBone.h>
#include <OgreHardwareBufferManager.h>
#include <OgreMaterialManager.h>
#include <OgreSkeletonManager.h>
#include <OgreRenderSystem.h>
#include <OgreRoot.h>
#include <components/nif/node.hpp>
#include <components/misc/stringops.hpp>
#include "material.hpp"
namespace NifOgre
{
// Helper class that computes the bounding box and of a mesh
class BoundsFinder
{
struct MaxMinFinder
{
float max, min;
MaxMinFinder()
{
min = std::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;
}
};
NIFMeshLoader::LoaderMap NIFMeshLoader::sLoaders;
void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape)
{
Ogre::SkeletonPtr skel;
const Nif::NiTriShapeData *data = shape->data.getPtr();
const Nif::NiSkinInstance *skin = (shape->skin.empty() ? NULL : shape->skin.getPtr());
std::vector<Ogre::Vector3> srcVerts = data->vertices;
std::vector<Ogre::Vector3> srcNorms = data->normals;
Ogre::HardwareBuffer::Usage vertUsage = Ogre::HardwareBuffer::HBU_STATIC;
bool vertShadowBuffer = false;
if(!shape->controller.empty())
{
Nif::ControllerPtr ctrl = shape->controller;
do {
if(ctrl->recType == Nif::RC_NiGeomMorpherController)
{
vertUsage = Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY;
vertShadowBuffer = true;
break;
}
} while(!(ctrl=ctrl->next).empty());
}
if(skin != NULL)
{
vertUsage = Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY;
vertShadowBuffer = true;
// Only set a skeleton when skinning. Unskinned meshes with a skeleton will be
// explicitly attached later.
mesh->setSkeletonName(mName);
// Get the skeleton resource, so vertices can be transformed into the bones' initial state.
Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr();
skel = skelMgr->getByName(mName);
// Convert vertices and normals to bone space from bind position. It would be
// better to transform the bones into bind position, but there doesn't seem to
// be a reliable way to do that.
std::vector<Ogre::Vector3> newVerts(srcVerts.size(), Ogre::Vector3(0.0f));
std::vector<Ogre::Vector3> newNorms(srcNorms.size(), Ogre::Vector3(0.0f));
const Nif::NiSkinData *data = skin->data.getPtr();
const Nif::NodeList &bones = skin->bones;
for(size_t b = 0;b < bones.length();b++)
{
Ogre::Bone *bone = skel->getBone(bones[b]->name);
Ogre::Matrix4 mat;
mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale),
Ogre::Quaternion(data->bones[b].trafo.rotation));
mat = bone->_getFullTransform() * mat;
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[b].weights;
for(size_t i = 0;i < weights.size();i++)
{
size_t index = weights[i].vertex;
float weight = weights[i].weight;
newVerts.at(index) += (mat*srcVerts[index]) * weight;
if(newNorms.size() > index)
{
Ogre::Vector4 vec4(srcNorms[index][0], srcNorms[index][1], srcNorms[index][2], 0.0f);
vec4 = mat*vec4 * weight;
newNorms[index] += Ogre::Vector3(&vec4[0]);
}
}
}
srcVerts = newVerts;
srcNorms = newNorms;
}
else
{
Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr();
if(skelMgr->getByName(mName).isNull())
{
// No skinning and no skeleton, so just transform the vertices and
// normals into position.
Ogre::Matrix4 mat4 = shape->getWorldTransform();
for(size_t i = 0;i < srcVerts.size();i++)
{
Ogre::Vector4 vec4(srcVerts[i].x, srcVerts[i].y, srcVerts[i].z, 1.0f);
vec4 = mat4*vec4;
srcVerts[i] = Ogre::Vector3(&vec4[0]);
}
for(size_t i = 0;i < srcNorms.size();i++)
{
Ogre::Vector4 vec4(srcNorms[i].x, srcNorms[i].y, srcNorms[i].z, 0.0f);
vec4 = mat4*vec4;
srcNorms[i] = Ogre::Vector3(&vec4[0]);
}
}
}
// Set the bounding box first
BoundsFinder bounds;
bounds.add(&srcVerts[0][0], srcVerts.size());
if(!bounds.isValid())
{
float v[3] = { 0.0f, 0.0f, 0.0f };
bounds.add(&v[0], 1);
}
mesh->_setBounds(Ogre::AxisAlignedBox(bounds.minX()-0.5f, bounds.minY()-0.5f, bounds.minZ()-0.5f,
bounds.maxX()+0.5f, bounds.maxY()+0.5f, bounds.maxZ()+0.5f));
mesh->_setBoundingSphereRadius(bounds.getRadius());
// This function is just one long stream of Ogre-barf, but it works
// great.
Ogre::HardwareBufferManager *hwBufMgr = Ogre::HardwareBufferManager::getSingletonPtr();
Ogre::HardwareVertexBufferSharedPtr vbuf;
Ogre::HardwareIndexBufferSharedPtr ibuf;
Ogre::VertexBufferBinding *bind;
Ogre::VertexDeclaration *decl;
int nextBuf = 0;
Ogre::SubMesh *sub = mesh->createSubMesh();
// Add vertices
sub->useSharedVertices = false;
sub->vertexData = new Ogre::VertexData();
sub->vertexData->vertexStart = 0;
sub->vertexData->vertexCount = srcVerts.size();
decl = sub->vertexData->vertexDeclaration;
bind = sub->vertexData->vertexBufferBinding;
if(srcVerts.size())
{
vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
srcVerts.size(), vertUsage, vertShadowBuffer);
vbuf->writeData(0, vbuf->getSizeInBytes(), &srcVerts[0][0], true);
decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
bind->setBinding(nextBuf++, vbuf);
}
// Vertex normals
if(srcNorms.size())
{
vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
srcNorms.size(), vertUsage, vertShadowBuffer);
vbuf->writeData(0, vbuf->getSizeInBytes(), &srcNorms[0][0], true);
decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
bind->setBinding(nextBuf++, vbuf);
}
// Vertex colors
const std::vector<Ogre::Vector4> &colors = data->colors;
if(colors.size())
{
Ogre::RenderSystem *rs = Ogre::Root::getSingleton().getRenderSystem();
std::vector<Ogre::RGBA> colorsRGB(colors.size());
for(size_t i = 0;i < colorsRGB.size();i++)
{
Ogre::ColourValue clr(colors[i][0], colors[i][1], colors[i][2], colors[i][3]);
rs->convertColourValue(clr, &colorsRGB[i]);
}
vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
colorsRGB.size(), Ogre::HardwareBuffer::HBU_STATIC);
vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB[0], true);
decl->addElement(nextBuf, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
bind->setBinding(nextBuf++, vbuf);
}
// Texture UV coordinates
size_t numUVs = data->uvlist.size();
for(size_t i = 0;i < numUVs;i++)
{
vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2),
srcVerts.size(), Ogre::HardwareBuffer::HBU_STATIC);
vbuf->writeData(0, vbuf->getSizeInBytes(), &data->uvlist[i][0], true);
decl->addElement(nextBuf, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, i);
bind->setBinding(nextBuf++, vbuf);
}
// Triangle faces
const std::vector<short> &srcIdx = data->triangles;
if(srcIdx.size())
{
ibuf = hwBufMgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, srcIdx.size(),
Ogre::HardwareBuffer::HBU_STATIC);
ibuf->writeData(0, ibuf->getSizeInBytes(), &srcIdx[0], true);
sub->indexData->indexBuffer = ibuf;
sub->indexData->indexCount = srcIdx.size();
sub->indexData->indexStart = 0;
}
// Assign bone weights for this TriShape
if(skin != NULL)
{
const Nif::NiSkinData *data = skin->data.getPtr();
const Nif::NodeList &bones = skin->bones;
for(size_t i = 0;i < bones.length();i++)
{
Ogre::VertexBoneAssignment boneInf;
boneInf.boneIndex = skel->getBone(bones[i]->name)->getHandle();
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
for(size_t j = 0;j < weights.size();j++)
{
boneInf.vertexIndex = weights[j].vertex;
boneInf.weight = weights[j].weight;
sub->addBoneAssignment(boneInf);
}
}
}
const Nif::NiTexturingProperty *texprop = NULL;
const Nif::NiMaterialProperty *matprop = NULL;
const Nif::NiAlphaProperty *alphaprop = NULL;
const Nif::NiVertexColorProperty *vertprop = NULL;
const Nif::NiZBufferProperty *zprop = NULL;
const Nif::NiSpecularProperty *specprop = NULL;
const Nif::NiWireframeProperty *wireprop = NULL;
bool needTangents = false;
shape->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop);
std::string matname = NIFMaterialLoader::getMaterial(data, mesh->getName(), mGroup,
texprop, matprop, alphaprop,
vertprop, zprop, specprop,
wireprop, needTangents);
if(matname.length() > 0)
sub->setMaterialName(matname);
// build tangents if the material needs them
if (needTangents)
{
unsigned short src,dest;
if (!mesh->suggestTangentVectorBuildParams(Ogre::VES_TANGENT, src,dest))
mesh->buildTangentVectors(Ogre::VES_TANGENT, src,dest);
}
}
NIFMeshLoader::NIFMeshLoader(const std::string &name, const std::string &group, size_t idx)
: mName(name), mGroup(group), mShapeIndex(idx)
{
}
void NIFMeshLoader::loadResource(Ogre::Resource *resource)
{
Ogre::Mesh *mesh = dynamic_cast<Ogre::Mesh*>(resource);
OgreAssert(mesh, "Attempting to load a mesh into a non-mesh resource!");
Nif::NIFFile::ptr nif = Nif::NIFFile::create(mName);
if(mShapeIndex >= nif->numRecords())
{
Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr();
if(!skelMgr->getByName(mName).isNull())
mesh->setSkeletonName(mName);
return;
}
const Nif::Record *record = nif->getRecord(mShapeIndex);
createSubMesh(mesh, dynamic_cast<const Nif::NiTriShape*>(record));
}
void NIFMeshLoader::createMesh(const std::string &name, const std::string &fullname, const std::string &group, size_t idx)
{
NIFMeshLoader::LoaderMap::iterator loader;
loader = sLoaders.insert(std::make_pair(fullname, NIFMeshLoader(name, group, idx))).first;
Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton();
Ogre::MeshPtr mesh = meshMgr.createManual(fullname, group, &loader->second);
mesh->setAutoBuildEdgeLists(false);
}
}