mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-04 08:26:39 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			408 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			408 lines
		
	
	
	
		
			14 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)
 | 
						|
{
 | 
						|
    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(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);
 | 
						|
 | 
						|
        // 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::Matrix4 mat;
 | 
						|
            mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale),
 | 
						|
                              Ogre::Quaternion(data->bones[b].trafo.rotation));
 | 
						|
            mat = bones[b]->getWorldTransform() * 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();
 | 
						|
    if (numUVs)
 | 
						|
    {
 | 
						|
        size_t elemSize = Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2);
 | 
						|
 | 
						|
        for(size_t i = 0; i < numUVs; i++)
 | 
						|
            decl->addElement(nextBuf, elemSize*i, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, i);
 | 
						|
 | 
						|
        vbuf = hwBufMgr->createVertexBuffer(decl->getVertexSize(nextBuf), srcVerts.size(),
 | 
						|
                                            Ogre::HardwareBuffer::HBU_STATIC);
 | 
						|
 | 
						|
        std::vector<Ogre::Vector2> allUVs;
 | 
						|
        allUVs.reserve(srcVerts.size()*numUVs);
 | 
						|
        for (size_t vert = 0; vert<srcVerts.size(); ++vert)
 | 
						|
            for(size_t i = 0; i < numUVs; i++)
 | 
						|
                allUVs.push_back(data->uvlist[i][vert]);
 | 
						|
 | 
						|
        vbuf->writeData(0, elemSize*srcVerts.size()*numUVs, &allUVs[0], true);
 | 
						|
 | 
						|
        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)
 | 
						|
    {
 | 
						|
        Ogre::SkeletonPtr skel = Ogre::SkeletonManager::getSingleton().getByName(mName);
 | 
						|
 | 
						|
        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);
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    if(!shape->controller.empty())
 | 
						|
    {
 | 
						|
        Nif::ControllerPtr ctrl = shape->controller;
 | 
						|
        do {
 | 
						|
            // Load GeomMorpherController into an Ogre::Pose and Animation
 | 
						|
            if(ctrl->recType == Nif::RC_NiGeomMorpherController)
 | 
						|
            {
 | 
						|
                const Nif::NiGeomMorpherController *geom =
 | 
						|
                        static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr());
 | 
						|
 | 
						|
                const std::vector<Nif::NiMorphData::MorphData>& morphs = geom->data.getPtr()->mMorphs;
 | 
						|
                // Note we are not interested in morph 0, which just contains the original vertices
 | 
						|
                for (unsigned int i = 1; i < morphs.size(); ++i)
 | 
						|
                {
 | 
						|
                    Ogre::Pose* pose = mesh->createPose(i);
 | 
						|
                    const Nif::NiMorphData::MorphData& data = morphs[i];
 | 
						|
                    for (unsigned int v = 0; v < data.mVertices.size(); ++v)
 | 
						|
                        pose->addVertex(v, data.mVertices[v]);
 | 
						|
 | 
						|
                    Ogre::String animationID = Ogre::StringConverter::toString(ctrl->recIndex)
 | 
						|
                            + "_" + Ogre::StringConverter::toString(i);
 | 
						|
                    Ogre::VertexAnimationTrack* track =
 | 
						|
                            mesh->createAnimation(animationID, 0)
 | 
						|
                            ->createVertexTrack(1, Ogre::VAT_POSE);
 | 
						|
                    Ogre::VertexPoseKeyFrame* keyframe = track->createVertexPoseKeyFrame(0);
 | 
						|
                    keyframe->addPoseReference(i-1, 1);
 | 
						|
                }
 | 
						|
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        } while(!(ctrl=ctrl->next).empty());
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
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);
 | 
						|
}
 | 
						|
 | 
						|
}
 |