You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
392 lines
12 KiB
D
392 lines
12 KiB
D
/*
|
|
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);
|
|
}
|
|
}
|
|
}
|