mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-31 22:56:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			848 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			848 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|   OpenMW - The completely unofficial reimplementation of Morrowind
 | |
|   Copyright (C) 2008-2010  Nicolay Korslund
 | |
|   Email: < korslund@gmail.com >
 | |
|   WWW: http://openmw.sourceforge.net/
 | |
| 
 | |
|   This file (ogre_nif_loader.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 "ogre_nif_loader.hpp"
 | |
| #include <Ogre.h>
 | |
| #include <stdio.h>
 | |
| 
 | |
| #include <libs/mangle/vfs/servers/ogre_vfs.hpp>
 | |
| #include "components/nif/nif_file.hpp"
 | |
| #include "components/nif/node.hpp"
 | |
| #include "components/nif/data.hpp"
 | |
| #include "components/nif/property.hpp"
 | |
| #include "libs/platform/strings.h"
 | |
| 
 | |
| // For warning messages
 | |
| #include <iostream>
 | |
| 
 | |
| // float infinity
 | |
| #include <limits>
 | |
| 
 | |
| typedef unsigned char ubyte;
 | |
| 
 | |
| using namespace std;
 | |
| using namespace Ogre;
 | |
| using namespace Nif;
 | |
| using namespace Mangle::VFS;
 | |
| 
 | |
| // This is the interface to the Ogre resource system. It allows us to
 | |
| // load NIFs from BSAs, in the file system and in any other place we
 | |
| // tell Ogre to look (eg. in zip or rar files.) It's also used to
 | |
| // check for the existence of texture files, so we can exchange the
 | |
| // extension from .tga to .dds if the texture is missing.
 | |
| static OgreVFS *vfs;
 | |
| 
 | |
| // Singleton instance used by load()
 | |
| static NIFLoader g_sing;
 | |
| 
 | |
| // Makeshift error reporting system
 | |
| static string errName;
 | |
| 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, so let's comment it out.
 | |
| static SceneBlendFactor getBlendFactor(int mode)
 | |
| {
 | |
|   switch(mode)
 | |
|     {
 | |
|     case 0: return SBF_ONE;
 | |
|     case 1: return SBF_ZERO;
 | |
|     case 2: return SBF_SOURCE_COLOUR;
 | |
|     case 3: return SBF_ONE_MINUS_SOURCE_COLOUR;
 | |
|     case 4: return SBF_DEST_COLOUR;
 | |
|     case 5: return SBF_ONE_MINUS_DEST_COLOUR;
 | |
|     case 6: return SBF_SOURCE_ALPHA;
 | |
|     case 7: return SBF_ONE_MINUS_SOURCE_ALPHA;
 | |
|     case 8: return SBF_DEST_ALPHA;
 | |
|     case 9: return SBF_ONE_MINUS_DEST_ALPHA;
 | |
|       // [Comment from Chris Robinson:] Can't handle this mode? :/
 | |
|       // case 10: return SBF_SOURCE_ALPHA_SATURATE;
 | |
|     default:
 | |
|       return SBF_SOURCE_ALPHA;
 | |
|     }
 | |
| }
 | |
| */
 | |
| 
 | |
| /* This is also unused
 | |
| static CompareFunction getTestMode(int mode)
 | |
| {
 | |
|   switch(mode)
 | |
|     {
 | |
|     case 0: return CMPF_ALWAYS_PASS;
 | |
|     case 1: return CMPF_LESS;
 | |
|     case 2: return CMPF_EQUAL;
 | |
|     case 3: return CMPF_LESS_EQUAL;
 | |
|     case 4: return CMPF_GREATER;
 | |
|     case 5: return CMPF_NOT_EQUAL;
 | |
|     case 6: return CMPF_GREATER_EQUAL;
 | |
|     case 7: return CMPF_ALWAYS_FAIL;
 | |
|     default:
 | |
|       return CMPF_ALWAYS_PASS;
 | |
|     }
 | |
| }
 | |
| */
 | |
| 
 | |
| static void createMaterial(const String &name,
 | |
|                            const Vector &ambient,
 | |
|                            const Vector &diffuse,
 | |
|                            const Vector &specular,
 | |
|                            const Vector &emissive,
 | |
|                            float glossiness, float alpha,
 | |
|                            float alphaFlags, float alphaTest,
 | |
|                            const String &texName)
 | |
| {
 | |
|   MaterialPtr material = MaterialManager::getSingleton().create(name, "General");
 | |
| 
 | |
|   // This assigns the texture to this material. If the texture name is
 | |
|   // a file name, and this file exists (in a resource directory), it
 | |
|   // will automatically be loaded when needed. If not (such as for
 | |
|   // internal NIF textures that we might support later), we should
 | |
|   // already have inserted a manual loader for the texture.
 | |
|   if(!texName.empty())
 | |
|     {
 | |
|       Pass *pass = material->getTechnique(0)->getPass(0);
 | |
|       /*TextureUnitState *txt =*/ pass->createTextureUnitState(texName);
 | |
| 
 | |
|       /* As of yet UNTESTED code from Chris:
 | |
|       pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC);
 | |
|       pass->setDepthFunction(Ogre::CMPF_LESS_EQUAL);
 | |
|       pass->setDepthCheckEnabled(true);
 | |
| 
 | |
|       // Add transparency if NiAlphaProperty was present
 | |
|       if(alphaFlags != -1)
 | |
|         {
 | |
|           if((alphaFlags&1))
 | |
|             {
 | |
|               pass->setDepthWriteEnabled(false);
 | |
|               pass->setSceneBlending(getBlendFactor((alphaFlags>>1)&0xf),
 | |
|                                      getBlendFactor((alphaFlags>>5)&0xf));
 | |
|             }
 | |
|           else
 | |
|             pass->setDepthWriteEnabled(true);
 | |
| 
 | |
|           if((alphaFlags>>9)&1)
 | |
|             pass->setAlphaRejectSettings(getTestMode((alphaFlags>>10)&0x7),
 | |
|                                          alphaTest);
 | |
| 
 | |
|           pass->setTransparentSortingEnabled(!((alphaFlags>>13)&1));
 | |
|         }
 | |
|       else
 | |
|         pass->setDepthWriteEnabled(true);
 | |
|       */
 | |
| 
 | |
|       // Add transparency if NiAlphaProperty was present
 | |
|       if(alphaFlags != -1)
 | |
|         {
 | |
|           // The 237 alpha flags are by far the most common. Check
 | |
|           // NiAlphaProperty in nif/property.h if you need to decode
 | |
|           // other values. 237 basically means normal transparencly.
 | |
|           if(alphaFlags == 237)
 | |
|             {
 | |
|               // Enable transparency
 | |
|               pass->setSceneBlending(SBT_TRANSPARENT_ALPHA);
 | |
| 
 | |
|               //pass->setDepthCheckEnabled(false);
 | |
|               pass->setDepthWriteEnabled(false);
 | |
|             }
 | |
|           else
 | |
|             warn("Unhandled alpha setting for texture " + texName);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   // Add material bells and whistles
 | |
|   material->setAmbient(ambient.array[0], ambient.array[1], ambient.array[2]);
 | |
|   material->setDiffuse(diffuse.array[0], diffuse.array[1], diffuse.array[2], alpha);
 | |
|   material->setSpecular(specular.array[0], specular.array[1], specular.array[2], alpha);
 | |
|   material->setSelfIllumination(emissive.array[0], emissive.array[1], emissive.array[2]);
 | |
|   material->setShininess(glossiness);
 | |
| }
 | |
| 
 | |
| // Takes a name and adds a unique part to it. This is just used to
 | |
| // make sure that all materials are given unique names.
 | |
| static String getUniqueName(const String &input)
 | |
| {
 | |
|   static int addon = 0;
 | |
|   static char buf[8];
 | |
|   snprintf(buf, 8, "_%d", addon++);
 | |
| 
 | |
|   // Don't overflow the buffer
 | |
|   if(addon > 999999) addon = 0;
 | |
| 
 | |
|   return input + buf;
 | |
| }
 | |
| 
 | |
| // Check if the given texture name exists in the real world. If it
 | |
| // does not, change the string IN PLACE to say .dds instead and try
 | |
| // that. The texture may still not exist, but no information of value
 | |
| // is lost in that case.
 | |
| static void findRealTexture(String &texName)
 | |
| {
 | |
|   assert(vfs);
 | |
|   if(vfs->isFile(texName)) return;
 | |
| 
 | |
|   int len = texName.size();
 | |
|   if(len < 4) return;
 | |
| 
 | |
|   // Change texture extension to .dds
 | |
|   texName[len-3] = 'd';
 | |
|   texName[len-2] = 'd';
 | |
|   texName[len-1] = 's';
 | |
| }
 | |
| 
 | |
| // Convert Nif::NiTriShape to Ogre::SubMesh, attached to the given
 | |
| // mesh.
 | |
| static void createOgreMesh(Mesh *mesh, NiTriShape *shape, const String &material)
 | |
| {
 | |
|   NiTriShapeData *data = shape->data.getPtr();
 | |
|   SubMesh *sub = mesh->createSubMesh(shape->name.toString());
 | |
| 
 | |
|   int nextBuf = 0;
 | |
| 
 | |
|   // This function is just one long stream of Ogre-barf, but it works
 | |
|   // great.
 | |
| 
 | |
|   // Add vertices
 | |
|   int numVerts = data->vertices.length / 3;
 | |
|   sub->vertexData = new VertexData();
 | |
|   sub->vertexData->vertexCount = numVerts;
 | |
|   sub->useSharedVertices = false;
 | |
|   VertexDeclaration *decl = sub->vertexData->vertexDeclaration;
 | |
|   decl->addElement(nextBuf, 0, VET_FLOAT3, VES_POSITION);
 | |
|   HardwareVertexBufferSharedPtr vbuf =
 | |
|     HardwareBufferManager::getSingleton().createVertexBuffer(
 | |
|       VertexElement::getTypeSize(VET_FLOAT3),
 | |
|       numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
 | |
|   vbuf->writeData(0, vbuf->getSizeInBytes(), data->vertices.ptr, true);
 | |
|   VertexBufferBinding* bind = sub->vertexData->vertexBufferBinding;
 | |
|   bind->setBinding(nextBuf++, vbuf);
 | |
| 
 | |
|   // Vertex normals
 | |
|   if(data->normals.length)
 | |
|     {
 | |
|       decl->addElement(nextBuf, 0, VET_FLOAT3, VES_NORMAL);
 | |
|       vbuf = HardwareBufferManager::getSingleton().createVertexBuffer(
 | |
|           VertexElement::getTypeSize(VET_FLOAT3),
 | |
|           numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
 | |
|       vbuf->writeData(0, vbuf->getSizeInBytes(), data->normals.ptr, true);
 | |
|       bind->setBinding(nextBuf++, vbuf);
 | |
|     }
 | |
| 
 | |
|   // Vertex colors
 | |
|   if(data->colors.length)
 | |
|     {
 | |
|       const float *colors = data->colors.ptr;
 | |
|       RenderSystem* rs = Root::getSingleton().getRenderSystem();
 | |
|       std::vector<RGBA> colorsRGB(numVerts);
 | |
|       RGBA *pColour = &colorsRGB.front();
 | |
|       for(int i=0; i<numVerts; i++)
 | |
| 	{
 | |
| 	  rs->convertColourValue(ColourValue(colors[0],colors[1],colors[2],
 | |
|                                              colors[3]),pColour++);
 | |
| 	  colors += 4;
 | |
| 	}
 | |
|       decl->addElement(nextBuf, 0, VET_COLOUR, VES_DIFFUSE);
 | |
|       vbuf = HardwareBufferManager::getSingleton().createVertexBuffer(
 | |
|           VertexElement::getTypeSize(VET_COLOUR),
 | |
| 	  numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
 | |
|       vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB.front(), true);
 | |
|       bind->setBinding(nextBuf++, vbuf);
 | |
|     }
 | |
| 
 | |
|   // Texture UV coordinates
 | |
|   if(data->uvlist.length)
 | |
|     {
 | |
|       decl->addElement(nextBuf, 0, VET_FLOAT2, VES_TEXTURE_COORDINATES);
 | |
|       vbuf = HardwareBufferManager::getSingleton().createVertexBuffer(
 | |
|           VertexElement::getTypeSize(VET_FLOAT2),
 | |
|           numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
 | |
| 
 | |
|       vbuf->writeData(0, vbuf->getSizeInBytes(), data->uvlist.ptr, true);
 | |
|       bind->setBinding(nextBuf++, vbuf);
 | |
|     }
 | |
| 
 | |
|   // Triangle faces
 | |
|   int numFaces = data->triangles.length;
 | |
|   if(numFaces)
 | |
|     {
 | |
|       HardwareIndexBufferSharedPtr ibuf = HardwareBufferManager::getSingleton().
 | |
| 	createIndexBuffer(HardwareIndexBuffer::IT_16BIT,
 | |
| 			  numFaces,
 | |
| 			  HardwareBuffer::HBU_STATIC_WRITE_ONLY);
 | |
|       ibuf->writeData(0, ibuf->getSizeInBytes(), data->triangles.ptr, true);
 | |
|       sub->indexData->indexBuffer = ibuf;
 | |
|       sub->indexData->indexCount = numFaces;
 | |
|       sub->indexData->indexStart = 0;
 | |
|     }
 | |
| 
 | |
|   // Set material if one was given
 | |
|   if(!material.empty()) sub->setMaterialName(material);
 | |
| 
 | |
|   /* Old commented D code. Might be useful when reimplementing
 | |
|      animation.
 | |
|   // Assign this submesh to the given bone
 | |
|   VertexBoneAssignment v;
 | |
|   v.boneIndex = ((Bone*)bone)->getHandle();
 | |
|   v.weight = 1.0;
 | |
| 
 | |
|   std::cerr << "+ Assigning bone index " << v.boneIndex << "\n";
 | |
| 
 | |
|   for(int i=0; i < numVerts; i++)
 | |
|     {
 | |
|       v.vertexIndex = i;
 | |
|       sub->addBoneAssignment(v);
 | |
|     }
 | |
|   */
 | |
| }
 | |
| 
 | |
| // Helper math functions. Reinventing linear algebra for the win!
 | |
| 
 | |
| // Computes B = AxB (matrix*matrix)
 | |
| static void matrixMul(const Matrix &A, Matrix &B)
 | |
| {
 | |
|   for(int i=0;i<3;i++)
 | |
|     {
 | |
|       float a = B.v[0].array[i];
 | |
|       float b = B.v[1].array[i];
 | |
|       float c = B.v[2].array[i];
 | |
| 
 | |
|       B.v[0].array[i] = a*A.v[0].array[0] + b*A.v[0].array[1] + c*A.v[0].array[2];
 | |
|       B.v[1].array[i] = a*A.v[1].array[0] + b*A.v[1].array[1] + c*A.v[1].array[2];
 | |
|       B.v[2].array[i] = a*A.v[2].array[0] + b*A.v[2].array[1] + c*A.v[2].array[2];
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Computes C = B + AxC*scale
 | |
| static void vectorMulAdd(const Matrix &A, const Vector &B, float *C, float scale)
 | |
| {
 | |
|   // Keep the original values
 | |
|   float a = C[0];
 | |
|   float b = C[1];
 | |
|   float c = C[2];
 | |
| 
 | |
|   // Perform matrix multiplication, scaling and addition
 | |
|   for(int i=0;i<3;i++)
 | |
|     C[i] = B.array[i] + (a*A.v[i].array[0] + b*A.v[i].array[1] + c*A.v[i].array[2])*scale;
 | |
| }
 | |
| 
 | |
| // Computes B = AxB (matrix*vector)
 | |
| static void vectorMul(const Matrix &A, float *C)
 | |
| {
 | |
|   // Keep the original values
 | |
|   float a = C[0];
 | |
|   float b = C[1];
 | |
|   float c = C[2];
 | |
| 
 | |
|   // Perform matrix multiplication, scaling and addition
 | |
|   for(int i=0;i<3;i++)
 | |
|     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, BoundsFinder &bounds)
 | |
| {
 | |
|   assert(shape != NULL);
 | |
| 
 | |
|   // Interpret flags
 | |
|   bool hidden    = (flags & 0x01) != 0; // Not displayed
 | |
|   bool collide   = (flags & 0x02) != 0; // Use mesh for collision
 | |
|   bool bbcollide = (flags & 0x04) != 0; // Use bounding box for collision
 | |
| 
 | |
|   // Bounding box collision isn't implemented, always use mesh for now.
 | |
|   if(bbcollide)
 | |
|     {
 | |
|       collide = true;
 | |
|       bbcollide = false;
 | |
|     }
 | |
| 
 | |
|   // If the object was marked "NCO" earlier, it shouldn't collide with
 | |
|   // anything.
 | |
|   if(flags & 0x800)
 | |
|     { collide = false; bbcollide = false; }
 | |
| 
 | |
|   if(!collide && !bbcollide && hidden)
 | |
|     // This mesh apparently isn't being used for anything, so don't
 | |
|     // bother setting it up.
 | |
|     return;
 | |
| 
 | |
|   // Material name for this submesh, if any
 | |
|   String material;
 | |
| 
 | |
|   // Skip the entire material phase for hidden nodes
 | |
|   if(!hidden)
 | |
|     {
 | |
|       // These are set below if present
 | |
|       NiTexturingProperty *t = NULL;
 | |
|       NiMaterialProperty *m = NULL;
 | |
|       NiAlphaProperty *a = NULL;
 | |
| 
 | |
|       // Scan the property list for material information
 | |
|       PropertyList &list = shape->props;
 | |
|       int n = list.length();
 | |
|       for(int i=0; i<n; i++)
 | |
|         {
 | |
|           // Entries may be empty
 | |
|           if(!list.has(i)) continue;
 | |
| 
 | |
|           Property *pr = &list[i];
 | |
| 
 | |
|           if(pr->recType == RC_NiTexturingProperty)
 | |
|             t = (NiTexturingProperty*)pr;
 | |
|           else if(pr->recType == RC_NiMaterialProperty)
 | |
|             m = (NiMaterialProperty*)pr;
 | |
|           else if(pr->recType == RC_NiAlphaProperty)
 | |
|             a = (NiAlphaProperty*)pr;
 | |
|         }
 | |
| 
 | |
|       // Texture
 | |
|       String texName;
 | |
|       if(t && t->textures[0].inUse)
 | |
|         {
 | |
|           NiSourceTexture *st = t->textures[0].texture.getPtr();
 | |
|           if(st->external)
 | |
|             {
 | |
|               SString tname = st->filename;
 | |
| 
 | |
|               /* findRealTexture checks if the file actually
 | |
|                  exists. If it doesn't, and the name ends in .tga, it
 | |
|                  will try replacing the extension with .dds instead
 | |
|                  and search for that. Bethesda at some at some point
 | |
|                  converted all their BSA textures from tga to dds for
 | |
|                  increased load speed, but all texture file name
 | |
|                  references were kept as .tga.
 | |
| 
 | |
|                  The function replaces the name in place (that's why
 | |
|                  we cast away the const modifier), but this is no
 | |
|                  problem since all the nif data is stored in a local
 | |
|                  throwaway buffer.
 | |
|                */
 | |
|               texName = "textures\\" + tname.toString();
 | |
|               findRealTexture(texName);
 | |
|             }
 | |
|           else warn("Found internal texture, ignoring.");
 | |
|         }
 | |
| 
 | |
|       // Alpha modifiers
 | |
|       int alphaFlags = -1;
 | |
|       ubyte alphaTest = 0;
 | |
|       if(a)
 | |
|         {
 | |
|           alphaFlags = a->flags;
 | |
|           alphaTest  = a->data->threshold;
 | |
|         }
 | |
| 
 | |
|       // Material
 | |
|       if(m || !texName.empty())
 | |
|         {
 | |
|           // If we're here, then this mesh has a material. Thus we
 | |
|           // need to calculate a snappy material name. It should
 | |
|           // contain the mesh name (mesh->getName()) but also has to
 | |
|           // be unique. One mesh may use many materials.
 | |
|           material = getUniqueName(mesh->getName());
 | |
| 
 | |
|           if(m)
 | |
|             {
 | |
|               // Use NiMaterialProperty data to create the data
 | |
|               const S_MaterialProperty *d = m->data;
 | |
|               createMaterial(material, d->ambient, d->diffuse, d->specular, d->emissive,
 | |
|                              d->glossiness, d->alpha, alphaFlags, alphaTest, texName);
 | |
|             }
 | |
|           else
 | |
|             {
 | |
|               // We only have a texture name. Create a default
 | |
|               // material for it.
 | |
|               Vector zero, one;
 | |
|               for(int i=0; i<3;i++)
 | |
|                 {
 | |
|                   zero.array[i] = 0.0;
 | |
|                   one.array[i] = 1.0;
 | |
|                 }
 | |
| 
 | |
|               createMaterial(material, one, one, zero, zero, 0.0, 1.0,
 | |
|                              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;
 | |
| 
 | |
|   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;
 | |
|     }
 | |
| 
 | |
|   // 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)
 | |
|     {
 | |
|       // 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, BoundsFinder &bounds)
 | |
| {
 | |
|   // Accumulate the flags from all the child nodes. This works for all
 | |
|   // the flags we currently use, at least.
 | |
|   flags |= node->flags;
 | |
| 
 | |
|   // Check for extra data
 | |
|   Extra *e = node;
 | |
|   while(!e->extra.empty())
 | |
|     {
 | |
|       // Get the next extra data in the list
 | |
|       e = e->extra.getPtr();
 | |
|       assert(e != NULL);
 | |
| 
 | |
|       if(e->recType == RC_NiStringExtraData)
 | |
|         {
 | |
|           // String markers may contain important information
 | |
|           // affecting the entire subtree of this node
 | |
|           NiStringExtraData *sd = (NiStringExtraData*)e;
 | |
| 
 | |
|           if(sd->string == "NCO")
 | |
|             // No collision. Use an internal flag setting to mark this.
 | |
|             flags |= 0x800;
 | |
|           else if(sd->string == "MRK")
 | |
|             // Marker objects. These are only visible in the
 | |
|             // editor. Until and unless we add an editor component to
 | |
|             // the engine, just skip this entire node.
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   // Apply the parent transformation to this node. We overwrite the
 | |
|   // existing data with the final transformation.
 | |
|   if(trafo)
 | |
|     {
 | |
|       // Get a non-const reference to the node's data, since we're
 | |
|       // overwriting it. TODO: Is this necessary?
 | |
|       Transformation &final = *((Transformation*)node->trafo);
 | |
| 
 | |
|       // For both position and rotation we have that:
 | |
|       // final_vector = old_vector + old_rotation*new_vector*old_scale
 | |
|       vectorMulAdd(trafo->rotation, trafo->pos, final.pos.array, trafo->scale);
 | |
|       vectorMulAdd(trafo->rotation, trafo->velocity, final.velocity.array, trafo->scale);
 | |
| 
 | |
|       // Merge the rotations together
 | |
|       matrixMul(trafo->rotation, final.rotation);
 | |
| 
 | |
|       // Scalar values are so nice to deal with. Why can't everything
 | |
|       // just be scalar?
 | |
|       final.scale *= trafo->scale;
 | |
|     }
 | |
| 
 | |
|   // For NiNodes, loop through children
 | |
|   if(node->recType == RC_NiNode)
 | |
|     {
 | |
|       NodeList &list = ((NiNode*)node)->children;
 | |
|       int n = list.length();
 | |
|       for(int i=0; i<n; i++)
 | |
|         {
 | |
|           if(list.has(i))
 | |
|             handleNode(mesh, &list[i], flags, node->trafo, bounds);
 | |
|         }
 | |
|     }
 | |
|   else if(node->recType == RC_NiTriShape)
 | |
|     // For shapes
 | |
|     handleNiTriShape(mesh, dynamic_cast<NiTriShape*>(node), flags, bounds);
 | |
| }
 | |
| 
 | |
| void NIFLoader::loadResource(Resource *resource)
 | |
| {
 | |
|   // Set up the VFS if it hasn't been done already
 | |
|   if(!vfs) vfs = new OgreVFS("General");
 | |
| 
 | |
|   // Get the mesh
 | |
|   Mesh *mesh = dynamic_cast<Mesh*>(resource);
 | |
|   assert(mesh);
 | |
| 
 | |
|   // Look it up
 | |
|   const String &name = mesh->getName();
 | |
|   errName = name; // Set name for error messages
 | |
|   if(!vfs->isFile(name))
 | |
|     {
 | |
|       warn("File not found.");
 | |
|       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
 | |
|   // likely a sign of incomplete code rather than faulty input.
 | |
|   NIFFile nif(vfs->open(name), name);
 | |
| 
 | |
|   if(nif.numRecords() < 1)
 | |
|     {
 | |
|       warn("Found no records in NIF.");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   // The first record is assumed to be the root node
 | |
|   Record *r = nif.getRecord(0);
 | |
|   assert(r != NULL);
 | |
| 
 | |
|   Nif::Node *node = dynamic_cast<Nif::Node*>(r);
 | |
| 
 | |
|   if(node == NULL)
 | |
|     {
 | |
|       warn("First record in file was not a node, but a " +
 | |
|            r->recName.toString() + ". Skipping file.");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   // Handle the node
 | |
|   handleNode(mesh, node, 0, NULL, bounds);
 | |
| 
 | |
|   // 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,
 | |
|                         const std::string &group)
 | |
| {
 | |
|   MeshManager *m = MeshManager::getSingletonPtr();
 | |
| 
 | |
|   // Check if the resource already exists
 | |
|   ResourcePtr ptr = m->getByName(name, group);
 | |
|   if(!ptr.isNull())
 | |
|     return MeshPtr(ptr);
 | |
| 
 | |
|   // Nope, create a new one.
 | |
|   return MeshManager::getSingleton().createManual(name, group, &g_sing);
 | |
| }
 | |
| 
 | |
| /* More code currently not in use, from the old D source. This was
 | |
|    used in the first attempt at loading NIF meshes, where each submesh
 | |
|    in the file was given a separate bone in a skeleton. Unfortunately
 | |
|    the OGRE skeletons can't hold more than 256 bones, and some NIFs go
 | |
|    way beyond that. The code might be of use if we implement animated
 | |
|    submeshes like this (the part of the NIF that is animated is
 | |
|    usually much less than the entire file, but the method might still
 | |
|    not be water tight.)
 | |
| 
 | |
| // Insert a raw RGBA image into the texture system.
 | |
| extern "C" void ogre_insertTexture(char* name, uint32_t width, uint32_t height, void *data)
 | |
| {
 | |
|   TexturePtr texture = TextureManager::getSingleton().createManual(
 | |
|       name, 		// name
 | |
|       "General",	// group
 | |
|       TEX_TYPE_2D,     	// type
 | |
|       width, height,    // width & height
 | |
|       0,                // number of mipmaps
 | |
|       PF_BYTE_RGBA,     // pixel format
 | |
|       TU_DEFAULT);      // usage; should be TU_DYNAMIC_WRITE_ONLY_DISCARDABLE for
 | |
|                         // textures updated very often (e.g. each frame)
 | |
| 
 | |
|   // Get the pixel buffer
 | |
|   HardwarePixelBufferSharedPtr pixelBuffer = texture->getBuffer();
 | |
| 
 | |
|   // Lock the pixel buffer and get a pixel box
 | |
|   pixelBuffer->lock(HardwareBuffer::HBL_NORMAL); // for best performance use HBL_DISCARD!
 | |
|   const PixelBox& pixelBox = pixelBuffer->getCurrentLock();
 | |
| 
 | |
|   void *dest = pixelBox.data;
 | |
| 
 | |
|   // Copy the data
 | |
|   memcpy(dest, data, width*height*4);
 | |
| 
 | |
|   // Unlock the pixel buffer
 | |
|   pixelBuffer->unlock();
 | |
| }
 | |
| 
 | |
| // We need this later for animated meshes.
 | |
| extern "C" void* ogre_setupSkeleton(char* name)
 | |
| {
 | |
|   SkeletonPtr skel = SkeletonManager::getSingleton().create(
 | |
|     name, "Closet", true);
 | |
| 
 | |
|   skel->load();
 | |
| 
 | |
|   // Create all bones at the origin and unrotated. This is necessary
 | |
|   // since our submeshes each have their own model space. We must
 | |
|   // move the bones after creating an entity, then copy this entity.
 | |
|   return (void*)skel->createBone();
 | |
| }
 | |
| 
 | |
| extern "C" void *ogre_insertBone(char* name, void* rootBone, int32_t index)
 | |
| {
 | |
|   return (void*) ( ((Bone*)rootBone)->createChild(index) );
 | |
| }
 | |
| */
 | |
| /* This was the D part:
 | |
| 
 | |
|     // Create a skeleton and get the root bone (index 0)
 | |
|     BonePtr bone = ogre_setupSkeleton(name);
 | |
| 
 | |
|     // Reset the bone index. The next bone to be created has index 1.
 | |
|     boneIndex = 1;
 | |
|     // Create a mesh and assign the skeleton to it
 | |
|     MeshPtr mesh = ogre_setupMesh(name);
 | |
| 
 | |
|     // Loop through the nodes, creating submeshes, materials and
 | |
|     // skeleton bones in the process.
 | |
|     handleNode(node, bone, mesh);
 | |
| 
 | |
|   // Create the "template" entity
 | |
|   EntityPtr entity = ogre_createEntity(name);
 | |
| 
 | |
|   // Loop through once again, this time to set the right
 | |
|   // transformations on the entity's SkeletonInstance. The order of
 | |
|   // children will be the same, allowing us to reference bones using
 | |
|   // their boneIndex.
 | |
|   int lastBone = boneIndex;
 | |
|   boneIndex = 1;
 | |
|   transformBones(node, entity);
 | |
|   if(lastBone != boneIndex) writefln("WARNING: Bone number doesn't match");
 | |
| 
 | |
|   if(!hasBBox)
 | |
|     ogre_setMeshBoundingBox(mesh, minX, minY, minZ, maxX, maxY, maxZ);
 | |
| 
 | |
|   return entity;
 | |
| }
 | |
| void handleNode(Node node, BonePtr root, MeshPtr mesh)
 | |
| {
 | |
|   // Insert a new bone for this node
 | |
|   BonePtr bone = ogre_insertBone(node.name, root, boneIndex++);
 | |
| 
 | |
| }
 | |
| 
 | |
| void transformBones(Node node, EntityPtr entity)
 | |
| {
 | |
|   ogre_transformBone(entity, &node.trafo, boneIndex++);
 | |
| 
 | |
|   NiNode n = cast(NiNode)node;
 | |
|   if(n !is null)
 | |
|     foreach(Node nd; n.children)
 | |
|       transformBones(nd, entity);
 | |
| }
 | |
| */
 |