Cleaned away old nif code

actorid
Nicolay Korslund 15 years ago
parent 752237e245
commit 27ec79e33f

@ -54,7 +54,6 @@ struct Named : Controlled
};
typedef Named NiSequenceStreamHelper;
struct NiParticleGrowFade : Controlled
{
void read(NIFFile *nif)

@ -51,9 +51,9 @@ struct NiVertWeightsExtraData : Extra
// We should have s*4+2 == i, for some reason. Might simply be the
// size of the rest of the record, unhelpful as that may be.
int i = nif->getInt();
int s = nif->getShort();
int s = nif->getShort(); // number of vertices
nif->getFloatLen(s);
nif->getFloatLen(s); // vertex weights I guess
}
};

@ -53,6 +53,15 @@ void NIFFile::parse()
int recNum = getInt();
records.resize(recNum);
/* The format for 10.0.1.0 seems to be a bit different. After the
header, it contains the number of records, r (int), just like
4.0.0.2, but following that it contains a short x, followed by x
strings. Then again by r shorts, one for each record, giving
which of the above strings to use to identify the record. After
this follows two ints (zero?) and then the record data. However
we do not support or plan to support other versions yet.
*/
for(int i=0;i<recNum;i++)
{
SString rec = getString();
@ -147,4 +156,9 @@ void NIFFile::parse()
records[i] = r;
r->read(this);
}
/* After the data, the nif contains an int N and then a list of N
ints following it. This might be a list of the root nodes in the
tree, but for the moment we ignore it.
*/
}

@ -57,7 +57,7 @@ struct Node : Named
hasBounds = nif->getInt();
if(hasBounds)
{
nif->getInt();
nif->getInt(); // always 1
boundPos = nif->getVector();
boundRot = nif->getMatrix();
boundXYZ = nif->getVector();
@ -70,6 +70,15 @@ struct NiNode : Node
NodeList children;
NodeList effects;
/* Known NiNode flags:
0x01 hidden
0x02 use mesh for collision
0x04 use bounding box for collision (?)
0x08 unknown, but common
0x20, 0x40, 0x80 unknown
*/
void read(NIFFile *nif)
{
Node::read(nif);
@ -80,6 +89,13 @@ struct NiNode : Node
struct NiTriShape : Node
{
/* Possible flags:
0x40 - mesh has no vertex normals ?
Only flags included in 0x47 (ie. 0x01, 0x02, 0x04 and 0x40) have
been observed so far.
*/
NiTriShapeDataPtr data;
NiSkinInstancePtr skin;

@ -174,7 +174,7 @@ struct S_VertexColorProperty
struct S_AlphaProperty
{
/*
In NiAlphaProperty, the flags have the following meaning.
In NiAlphaProperty, the flags have the following meaning:
Bit 0 : alpha blending enable
Bits 1-4 : source blend mode

@ -1,211 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (base.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/ .
*/
// Basic file structure
module nif.base;
import nif.nif;
import nif.niffile;
import nif.misc;
import monster.util.string;
//import nif.controller;
//import nif.node;
import nif.record;
debug(verbose) public import std.stdio;
void parseFile()
{
with(nifFile)
{ // Read header
int ver; // Hex-encoded version
char[40] head; // Header string
// Read and check header string
getString(head);
if(!begins(head,"NetImmerse File Format"))
fail("Not a NetImmerse file");
debug(verbose) writefln("Header: \"%s\"", head[0..39]);
debug(strict)
if(head[0..39] != "NetImmerse File Format, Version 4.0.0.2" || head[39] != 0x0a)
fail("Unrecognized header string, most likely an unsupported NIF version");
// Get binary coded version
ver = getInt;
debug(verbose)
{
writef("BCD version tag: ");
for(ubyte *b = (cast(ubyte*)&ver)+3; b > (cast(ubyte*)&ver); b--)
writef(*b, ".");
writefln(cast(ubyte)ver);
}
if(ver != 0x04_00_00_02)
fail("Don't know how to process this version");
// The number of records in the file
nifMesh.records = nifRegion.allocateT!(Record)(getInt);
debug(verbose) writefln("Number of records: ", nifMesh.records.length);
// The format for 10.0.1.0 seems to be a bit different. After the
// header, it contains the number of records, r (int), just like
// 4.0.0.2, but following that it contains a short x, followed by
// x strings. Then again by r shorts, one for each record, giving
// which of the above strings to use to identify the record. After
// this follows two ints (zero?) and then the record data.
// Besides giving me more work to do, this structure suggests some
// obvious code optimizations we can now apply to the reading
// structure. Since we now know in advance how many records there
// are of each type, we could allocate the structures in bulk. D
// doesn't allow this for classes directly, but we can make a
// custom allocator and whatnot. I doubt we save a lot of time
// with it, though. I'll save this for later.
// As for the record data itself, I do not know what has
// changed. There are some new records. I might as well have
// separate reading functions for the entire main structure.
// EDIT 14. feb 06: Since we are concentrating on Morrowind, let
// us drop other versions for now. It would be nice to support
// other versions (or even other mesh formats), but the priority
// of this is very low at the moment.
}
endFor:
foreach(int i, ref Record o; nifMesh.records)
{
// Get type string, and update the NIFFiles error message to
// match it.
char[] recName = nifFile.getString;
nifFile.setRec(i,recName);
debug(verbose)
{
writefln("\n%d: %s (%x)", i, recName, nifFile.position);
}
switch(recName)
{
// These are the record types we know how to read
// Time controllers
case "NiVisController": o = new NiVisController; break;
case "NiGeomMorpherController": o = new NiGeomMorpherController; break;
case "NiKeyframeController": o = new NiKeyframeController; break;
case "NiAlphaController": o = new NiAlphaController; break;
case "NiUVController": o = new NiUVController; break;
case "NiPathController": o = new NiPathController; break;
case "NiMaterialColorController": o = new NiMaterialColorController; break;
case "NiBSPArrayController": o = new NiBSPArrayController; break;
case "NiParticleSystemController": o = new NiParticleSystemController; break;
// NiNodes
case "RootCollisionNode":
case "NiBSParticleNode":
case "NiBSAnimationNode":
case "NiBillboardNode":
case "AvoidNode":
case "NiNode": o = new NiNode; break;
// Other nodes
case "NiTriShape": o = new NiTriShape; break;
case "NiRotatingParticles": o = new NiRotatingParticles; break;
case "NiAutoNormalParticles": o = new NiAutoNormalParticles; break;
case "NiCamera": o = new NiCamera; break;
// Effects
case "NiAmbientLight":
case "NiDirectionalLight": o = new Light; break;
case "NiTextureEffect": o = new NiTextureEffect; break;
// Properties
case "NiTexturingProperty": o = new NiTexturingProperty; break;
case "NiMaterialProperty": o = new NiMaterialProperty; break;
case "NiZBufferProperty": o = new NiZBufferProperty; break;
case "NiAlphaProperty": o = new NiAlphaProperty; break;
case "NiVertexColorProperty": o = new NiVertexColorProperty; break;
case "NiShadeProperty": o = new NiShadeProperty; break;
case "NiDitherProperty": o = new NiDitherProperty; break;
case "NiWireframeProperty": o = new NiWireframeProperty; break;
case "NiSpecularProperty": o = new NiSpecularProperty; break;
// Extra Data
case "NiVertWeightsExtraData": o = new NiVertWeightsExtraData; break;
case "NiTextKeyExtraData": o = new NiTextKeyExtraData; break;
case "NiStringExtraData": o = new NiStringExtraData; break;
case "NiGravity": o = new NiGravity; break;
case "NiPlanarCollider": o = new NiPlanarCollider; break;
case "NiParticleGrowFade": o = new NiParticleGrowFade; break;
case "NiParticleColorModifier": o = new NiParticleColorModifier; break;
case "NiParticleRotation": o = new NiParticleRotation; break;
// Data
case "NiFloatData": o = new NiFloatData; break;
case "NiTriShapeData": o = new NiTriShapeData; break;
case "NiVisData": o = new NiVisData; break;
case "NiColorData": o = new NiColorData; break;
case "NiPixelData": o = new NiPixelData; break;
case "NiMorphData": o = new NiMorphData; break;
case "NiKeyframeData": o = new NiKeyframeData; break;
case "NiSkinData": o = new NiSkinData; break;
case "NiUVData": o = new NiUVData; break;
case "NiPosData": o = new NiPosData; break;
case "NiRotatingParticlesData": o = new NiRotatingParticlesData; break;
case "NiAutoNormalParticlesData": o = new NiAutoNormalParticlesData; break;
// Other
case "NiSequenceStreamHelper": o = new NiSequenceStreamHelper; break;
case "NiSourceTexture": o = new NiSourceTexture; break;
case "NiSkinInstance": o = new NiSkinInstance; break;
default:
nifFile.fail("Unknown record type " ~ recName);
}
if(o !is null)
{
//nifFile.setRec(i,recName ~ " (" ~ o.toString ~ ")");
o.read();
}
}
// Clear error message.
nifFile.setRec(-1,"");
// The 'footer', which it seems isn't always constant after all. I
// have no clue what it is for though.
int roots = nifFile.getInt;
for(int i; i<roots;i++)
nifFile.getInt();
debug(verbose) if(nifFile.eof()) writefln("End of file");
// A lot of files don't end where they should, so we just have to
// accept it.
//debug(check) if(!nifFile.eof()) nifFile.warn("End of file not reached");
}

@ -1,74 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (controlled.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 nif.controlled;
import nif.record;
import nif.extra;
// 'Controlled' is something that has a controller
abstract class Controlled : Extra
{
Controller controller;
override:
void read()
{
super.read();
debug(verbose) writef("Controller ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
controller = lookup!(Controller)(list);
}
}
// Record with name and controller/extra data link
abstract class Named : Controlled
{
// Name of this object. This is used to refer to the object from .kf
// files.
char[] name;
override:
char[] toString()
{
if(name.length)
return super.toString() ~ " '" ~ name ~ "'";
else
return super.toString();
}
void read()
{
//super.read(f);
name = nifFile.getString();
debug(verbose) writefln("Name: %s", name);
super.read();
}
}
class NiSequenceStreamHelper : Named {}

@ -1,316 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (controller.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 nif.controller;
import nif.record;
// Time controller
abstract class Controller : Record
{
Controller next;
ushort flags;
float frequency, phase; // Don't know what these do.
float timeStart, timeStop;
Controlled target;
override:
void read()
{with(nifFile){
super.read();
debug(verbose) writef("Next controller ");
getIndex();
flags = getUshort;
frequency = getFloatIs(1);
phase = getFloat;
timeStart = getFloat;
timeStop = getFloat;
debug(verbose) writef("Target ");
getIndex();
debug(verbose)
{
writefln("Flags: %x", flags);
writefln("Frequency: ", frequency);
writefln("Phase: ", phase);
writefln("Start time: ", timeStart);
writefln("Stop time: ", timeStop);
}
}}
void sortOut(Record[] list)
{
super.sortOut(list);
next = lookup!(Controller)(list);
target = lookup!(Controlled)(list);
}
}
alias NiBSPArrayController NiParticleSystemController;
class NiBSPArrayController : Controller
{
override:
void read()
{with(nifFile){
super.read();
// At the moment, just skip it all
//*
// 23 floats = 92 bytes
// 3 ints = 12 bytes
// 3 shorts = 6 bytes
// 1 byte = 1 byte
// Total: 111 bytes
seekCur(111);
short s = wgetShort;
seekCur(15 + s*40);
/*/
float speed, speedRandom, angle, angleRandom;
speed = wgetFloat;
speedRandom = wgetFloat;
angle = wgetFloat;
angleRandom = wgetFloat;
wgetFloat(); // Unknown
wgetFloat(); // Sometimes pi
for(int i; i<10; i++)
wgetFloat();
wgetByte();
wgetFloat();
wgetFloat();
wgetFloat();
wgetShort();
wgetVector();
debug(verbose) writef("Emitter: ");
wgetInt();
wgetShort();
wgetFloat();
wgetInt();
wgetInt();
wgetShort();
debug(verbose) writefln("begin particle group");
short s = wgetShort;
wgetShort();
for(short i; i<s; i++)
{
Matrix m;
getMatrix(m);
debug(verbose) writefln("Matrix: ", m.toString);
wgetShort();
wgetShort();
}
debug(verbose) writefln("end particle group");
debug(verbose) writefln("Links:");
wgetInt();
wgetInt();
wgetInt();
wgetByte();
//*/
}}
}
class NiMaterialColorController : Controller
{
NiPosData data;
override:
void read()
{
super.read();
debug(verbose) writef("PosData ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiPosData)(list);
}
}
class NiPathController : Controller
{
NiPosData posData;
NiFloatData floatData;
override:
void read()
{with(nifFile){
super.read();
wgetIntIs(1);
wgetFloat();
wgetFloat();
wgetShortIs(0,1);
debug(verbose) writef("Pos Data ");
getIndex();
debug(verbose) writef("Float Data ");
getIndex();
}}
void sortOut(Record[] list)
{
super.sortOut(list);
posData = lookup!(NiPosData)(list);
floatData = lookup!(NiFloatData)(list);
}
}
class NiUVController : Controller
{
NiUVData data;
override:
void read()
{
super.read();
short s = nifFile.getShortIs(0);
debug(verbose) writef("UV Data ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiUVData)(list);
}
}
// If there was more than four of these, I would template it.
class NiKeyframeController : Controller
{
NiKeyframeData data;
override:
void read()
{
super.read();
debug(check)
if(flags & 0xf0) nifFile.warn("Unknown flags");
debug(verbose) writef("KeyframeData ");
getIndex();
// If this index is empty, timeStart/timeStop are set to +/-
// float.max.
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiKeyframeData)(list);
}
}
class NiAlphaController : Controller
{
NiFloatData data;
override:
void read()
{
super.read();
debug(controllerCheck)
if(flags != 12) nifFile.warn("Unknown flags");
debug(verbose) writef("Float Data ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiFloatData)(list);
}
}
class NiGeomMorpherController : Controller
{
NiMorphData data;
override:
void read()
{
super.read();
debug(controllerCheck)
if(flags != 12) nifFile.warn("Unknown flags");
debug(verbose) writef("Morph Data ");
getIndex();
byte b = nifFile.wgetByteIs(0);
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiMorphData)(list);
}
}
class NiVisController : Controller
{
NiVisData data;
override:
void read()
{
super.read();
debug(controllerCheck)
if(flags != 12) nifFile.warn("Unknown flags");
debug(verbose) writef("Vis Data ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiVisData)(list);
}
}

@ -1,816 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (data.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 nif.data;
import nif.record;
class NiSourceTexture : Named
{
byte external;
union
{
char[] filename;
NiPixelData data;
}
/* Pixel layout
0 - Palettised
1 - High color 16
2 - True color 32
3 - Compressed
4 - Bumpmap
5 - Default */
int pixel;
/* Mipmap format
0 - no
1 - yes
2 - default */
int mipmap;
/* Alpha
0 - none
1 - binary
2 - smooth
3 - default (use material alpha, or multiply material with texture if present)
*/
int alpha;
override:
void sortOut(Record[] list)
{
super.sortOut(list);
if(!external)
data = lookup!(NiPixelData)(list);
}
void read()
{
super.read();
external = nifFile.getByteIs(0,1);
debug(verbose) writefln("Use external file: ", external);
if(external)
{
filename = nifFile.getString;
debug(verbose) writefln("Filename: ", filename);
}
else
{
nifFile.wgetByteIs(1);
debug(verbose) writef("Pixel Data ");
getIndex();
}
pixel = nifFile.getInt;
mipmap = nifFile.getInt;
alpha = nifFile.getIntIs(3);
debug(verbose)
{
writefln("Pixel layout: ", pixel);
writefln("Mipmap: ", mipmap);
writefln("Alpha: ", alpha);
}
nifFile.wgetByteIs(1);
}
}
// Common ancestor for several classes
class ShapeData : Record
{
float[] vertices, normals, colors, uvlist;
Vector center;
float radius;
override:
void read()
{
super.read();
short verts = nifFile.getShort;
debug(verbose) writefln("Vertices: ", verts);
if(nifFile.getInt != 0)
{
debug(verbose) writefln("Reading vertices");
if(verts == 0) nifFile.warn("Empty vertex array");
vertices = nifFile.getArraySize!(float)(verts*3);
}
else nifFile.warn("No vertices found");
if(nifFile.getInt != 0)
{
debug(verbose) writefln("Reading normals");
normals = nifFile.getArraySize!(float)(verts*3);
}
center = nifFile.getVector();
radius = nifFile.getFloat();
debug(verbose)
{
writefln("Center: ", center.toString);
writefln("Radius: ", radius);
}
if(nifFile.getInt != 0)
{
debug(verbose) writefln("Reading vertex colors");
colors = nifFile.getArraySize!(float)(verts*4);
}
short uvs = nifFile.getShort;
debug(verbose) writefln("Number of UV sets: ", uvs);
if(nifFile.getInt != 0)
{
if(uvs == 0) nifFile.warn("Empty UV list");
// Only the 6 first bits are used as a count, the rest are
// (unknown) flags.
if(uvs > 0b111111)
{
nifFile.warn("UV count contains (unknown) flags");
uvs = cast(short)(uvs & 0b111111);
}
uvlist = nifFile.getArraySize!(float)(uvs*verts*2);
}
else if(uvs != 0) nifFile.warn("UV list was unexpectedly missing");
}
}
class NiAutoNormalParticlesData : ShapeData
{
ushort activeCount;
override:
void read()
{
super.read();
nifFile.assertWarn(uvlist.length == 0, "UV coordinates are not expected in this record");
debug(verbose) writef("Active vertices: ");
// Number of active vertices (always matches count?)
activeCount = nifFile.wgetUshortIs(cast(ushort)(vertices.length/3));
nifFile.wgetFloat(); // Active radius (?)
nifFile.wgetShort(); // Number of valid entries in the following arrays
//writefln("%x", nifFile.position);
if(nifFile.wgetInt == 0) nifFile.warn("Particle size list is missing");
else
for(int i; i<activeCount; i++)
{
float fl = nifFile.getFloat; // Particle sizes
debug(veryverbose)
writefln(" %d: ", i, fl);
}
}
}
class NiRotatingParticlesData : NiAutoNormalParticlesData
{
override:
void read()
{
super.read();
nifFile.assertWarn(normals.length == 0, "Normals are not expected in this record");
Vector4 v;
if(nifFile.wgetInt == 0) nifFile.warn("Rotation data is not present");
else
// This is either activeCount or vertices.length/3, until I
// find a file in which the two differs (if one exists), we
// can't know which is correct.
for(int i; i<activeCount; i++)
{
v = nifFile.getVector4; // Quaternions
debug(veryverbose)
writefln(" %d: ", i, v.toString);
}
//writefln("%x", nifFile.position);
}
}
class NiPosData : Record
{
override:
void read()
{
super.read();
int count = nifFile.getInt;
debug(verbose) writefln("Count: ", count);
int type = nifFile.wgetIntIs(1,2);
for(int i; i<count; i++)
{
// TODO: Make a generalized struct of this? Seems to be the
// same as in NiKeyframeData.
float time = nifFile.getFloat;
debug(verbose) writef("Time %f: ", time);
Vector v;
if(type == 1)
{
v = nifFile.getVector;
debug(verbose) writef(v.toString);
}
else if(type == 2)
{
v = nifFile.getVector;
debug(verbose) writef(v.toString, " ");
v = nifFile.getVector;
debug(verbose) writef(v.toString, " ");
v = nifFile.getVector;
debug(verbose) writef(v.toString);
}
else nifFile.fail("Unknown type");
debug(verbose) writefln();
}
}
}
class NiUVData : Record
{
override:
void read()
{
super.read();
debug(verbose) writef("Count: ");
int count = nifFile.wgetInt();
// TODO: This is claimed to be a "float animation key", which is
// also used in FloatData and KeyframeData
if(count != 5 && count != 3 && count != 2 && count != 0)
nifFile.warn("Untested count");
if(count)
{
nifFile.wgetIntIs(2);
debug(verbose) writefln("%d entries in list 1:", count);
for(int i; i<count; i++)
{
float time = nifFile.getFloat;
Vector v = nifFile.getVector;
debug(verbose)
writefln(" Time %f: ", time, v.toString);
}
}
count = nifFile.getInt;
if(count)
{
nifFile.wgetIntIs(2);
debug(verbose) writefln("%d entries in list 2:", count);
for(int i; i<count; i++)
{
float time = nifFile.getFloat;
Vector v = nifFile.getVector;
debug(verbose)
writefln(" Time %f: ", time, v.toString);
}
}
nifFile.getIntIs(0);
nifFile.getIntIs(0);
}
}
class NiPixelData : Record
{
uint rmask, gmask, bmask, amask;
int bpp;
int mips;
override:
void read()
{
super.read();
nifFile.wgetIntIs(0,1);
rmask = nifFile.getUintIs(0xff);
gmask = nifFile.getUintIs(0xff00);
bmask = nifFile.getUintIs(0xff0000);
amask = nifFile.getUintIs(0xff000000,0);
bpp = nifFile.getIntIs(24,32);
nifFile.wgetByte();
nifFile.wgetByteIs(8);
nifFile.wgetUbyteIs(130);
nifFile.wgetByteIs(0,32);
nifFile.wgetByteIs(0);
nifFile.wgetByteIs(65);
nifFile.wgetByteIs(0,12);
nifFile.wgetByteIs(0);
nifFile.wgetIntIs(-1);
mips = nifFile.getInt;
int bytes = nifFile.getIntIs(bpp>>3);
debug(verbose)
{
writefln("Red mask %8xh", rmask);
writefln("Green mask %8xh", gmask);
writefln("Blue mask %8xh", bmask);
writefln("Alpha mask %8xh", amask);
writefln("Bits per pixel: ", bpp);
writefln("Number of mipmaps: ", mips);
writefln("Bytes per pixel: ", bytes);
}
for(int i; i<mips; i++)
{
int x = nifFile.getInt;
int y = nifFile.getInt;
uint offset = nifFile.getUint;
debug(verbose)
{
writefln("Mipmap %d:", i);
writefln(" Size: %dx%d", x, y);
writefln(" Offset in data field: %xh", offset);
}
}
uint dataSize = nifFile.getUint;
nifFile.seekCur(dataSize);
}
}
class NiColorData : Record
{
struct ColorData
{
float time, R, G, B, A;
}
static assert(ColorData.sizeof == 20);
ColorData colors[];
override:
void read()
{
super.read();
int count = nifFile.getInt;
nifFile.getIntIs(1);
colors = nifFile.getArraySize!(ColorData)(count);
}
}
class NiVisData : Record
{
align(1)
struct VisData
{
float time;
byte isSet;
static assert(VisData.sizeof == 5);
}
VisData vis[];
override:
void read()
{
super.read();
int count = nifFile.getInt;
vis = nifFile.getArraySize!(VisData)(count);
debug(check)
foreach(VisData v; vis)
if(v.isSet!=0 && v.isSet!=1) nifFile.warn("Unknown bool value");
}
}
class NiFloatData : Record
{
struct FloatData
{
float time;
float[3] alpha;
}
static assert(FloatData.sizeof == 16);
FloatData data[];
override:
void read()
{
super.read();
int cnt = nifFile.getInt;
nifFile.getIntIs(2);
data = nifFile.getArraySize!(FloatData)(cnt);
// This might be the "keyfloat" type, ie. a value and forward
// and backward tangents. Might be used for animating in the
// alpha space?
debug(verbose)
foreach(FloatData d; data)
writefln("Time: %f Alpha: %f, %f, %f", d.time,
d.alpha[0], d.alpha[1], d.alpha[2]);
}
}
class NiSkinData : Record
{
align(1)
struct Weight
{
ushort vertex;
float weight;
static assert(Weight.sizeof == 6);
}
Weight weights[][];
override:
void read()
{
super.read();
Matrix m;
Vector v;
Vector4 v4;
nifFile.getMatrix(m);
v = nifFile.getVector();
nifFile.getFloatIs(1);
debug(verbose)
{
writefln("Matrix:\n", m.toString);
writefln("Vector: ", v.toString);
writefln("Float: 1 (always)");
}
int count = nifFile.getInt;
debug(verbose) writefln("Bones: ", count);
nifFile.getIntIs(-1);
nifFile.fitArray(70,count);
weights = nifRegion.allocateT!(Weight[])(count);
foreach(int i, ref Weight[] wl; weights)
{
nifFile.getMatrix(m); // Rotation offset of the skin from this
// bone in bind position.
v = nifFile.getVector(); // Translation -ditto-
nifFile.getFloatIs(1); // Scale -ditto- ?
debug(verbose)
{
writefln("Bone #%d:", i);
writefln(" Rotation:\n", m.toString);
writefln(" Translation: ", v.toString);
writefln(" Scale: 1 (always)");
}
v4 = nifFile.getVector4;
// Number of vertex weights
ushort cnt2 = nifFile.getUshort;
debug(verbose)
{
writefln(" 4-Vector: ", v4.toString);
writefln(" Number of vertex weights: ", cnt2);
}
wl = nifFile.getArraySize!(Weight)(cnt2);
debug(veryverbose)
foreach(ref Weight w; wl)
writefln(" %d: ", w.vertex, w.weight);
}
}
}
class NiTriShapeData : ShapeData
{
short[] triangles;
short[][] matches;
override:
void read()
{
super.read();
ushort verts = cast(ushort)(vertices.length/3);
short tris = nifFile.getShort;
debug(verbose) writefln("Number of faces: ", tris);
if(tris)
{
// Must have three times as many vertices as triangles
int cnt = nifFile.getIntIs(tris*3);
triangles = nifFile.getArraySize!(short)(cnt);
}
short match = nifFile.getShort;
if(match)
{
nifFile.fitArray(match,2);
matches = nifRegion.allocateT!(short[])(match);
if(match != verts) nifFile.warn("Expected verts and match to be equal");
foreach(ref short[] s; matches)
{
short sh = nifFile.getShort;
if(sh>=verts || sh < 0)
nifFile.warn("Presumed vertex index was out of bounds");
s = nifFile.getArraySize!(short)(sh);
}
}
}
}
class NiMorphData : Record
{
//float[] vertexList;
override:
void read()
{
super.read();
int morphCount = nifFile.getInt;
int vertCount = nifFile.getInt;
debug(verbose)
{
writefln("Number of morphs: ", morphCount);
writefln("Vertices: ", vertCount);
}
nifFile.wgetByteIs(1);
//nifFile.getIntIs(0);
//nifFile.getIntIs(1);
//vertexList = nifFile.getArraySize!(float)(3*vertCount);
for(int i=0; i<morphCount; i++)
{
int magic = nifFile.getInt;
debug(veryverbose)
{
writefln("Getting morph batch %d", i);
writefln(" Number of 4-vectors ", magic);
}
nifFile.wgetIntIs(0,1,2); // Is always 1 for i=0, 0 or 2 otherwise
float[] l;
// Time, data, forward, backward tangents
if(magic)
l = nifFile.getArraySize!(float)(magic*4);
debug(veryverbose)
for(int j; j<magic; j++)
writefln(" Time %f : [", l[4*j], l[4*j+1],",", l[4*j+2],",", l[4*j+3],"]");
l = nifFile.getArraySize!(float)(vertCount*3);
debug(veryverbose)
for(int j; j<vertCount; j++)
writefln(" Vertex morph %d: ", j, " [%f, %f, %f]",
l[j*3], l[j*3+1], l[j*3+2]);
}
}
}
class NiKeyframeData : Record
{
// These should be moved out of this class, given their own reader
// functions, and used elsewhere. But it currently works, and I
// haven't really confirmed that these ARE used elsewhere, yet.
struct Rotation1
{
float time;
float[4] quaternion;
static assert(Rotation1.sizeof == 5*4);
}
struct Rotation3
{
float time;
float[4] quaternion;
float tension;
float bias;
float continuity;
static assert(Rotation3.sizeof == 8*4);
}
struct Rotation4
{
float time;
struct R
{
struct R1
{
float time;
float unknown;
static assert(R1.sizeof == 8);
}
struct R2
{
float time;
float unknown[3];
static assert(R2.sizeof == 16);
}
int type;
union
{
R1[] r1;
R2[] r2;
}
}
R r3[3];
}
struct Translation1
{
float time;
float[3] translation;
static assert(Translation1.sizeof==4*4);
}
struct Translation2
{
float time;
float[3] translation;
float[3] forward;
float[3] backward;
static assert(Translation2.sizeof==4*10);
}
struct Translation3
{
float time;
float[3] translation;
float tension;
float bias;
float continuity;
static assert(Translation3.sizeof==4*7);
}
struct Scale1
{
float time;
float scale;
static assert(Scale1.sizeof == 4*2);
}
struct Scale2
{
float time;
float scale;
float forward;
float backward;
static assert(Scale2.sizeof == 4*4);
}
struct Scale3
{
float time;
float scale;
float tension;
float bias;
float continuity;
static assert(Scale3.sizeof == 4*5);
}
// Data arrays. Only one of each type will be used. I used to have
// them in unions, but then I realized that was just asking for trouble.
Rotation1 rot1[];
Rotation3 rot3[];
Rotation4 rot4[];
Translation1 tra1[];
Translation2 tra2[];
Translation3 tra3[];
Scale1 sca1[];
Scale2 sca2[];
Scale3 sca3[];
int rotType, traType, scaleType;
override:
void read()
{
super.read();
int count;
count = nifFile.getInt;
debug(verbose) writefln("Number of rotations: ", count);
if(count)
{
rotType = nifFile.getInt;
if(rotType == 1)
rot1 = nifFile.getArraySize!(Rotation1)(count);
else if(rotType == 3)
rot3 = nifFile.getArraySize!(Rotation3)(count);
else if(rotType == 4)
{
if(count!=1) nifFile.warn("Rotation type 4, but count was not 1, it was "
~ .toString(count));
nifFile.fitArray(count,6*4); // Minimum size
rot4 = nifRegion.allocateT!(Rotation4)(count);
foreach(ref Rotation4 rr; rot4)
{
rr.time = nifFile.getFloat;
foreach(ref rr.R r; rr.r3)
{
int cnt = nifFile.getInt;
r.type = nifFile.getInt;
if(r.type == 1)
nifFile.getArraySize!(r.R1)(cnt);
else if(r.type == 2)
nifFile.getArraySize!(r.R2)(cnt);
else nifFile.fail("Unknown rotation subtype " ~ .toString(r.type));
}
}
}
else nifFile.fail("Don't know how to handle rotation type " ~ .toString(rotType));
} // End of rotations
count = nifFile.getInt;
debug(verbose) writefln("Number of translations: ", count);
if(count)
{
traType = nifFile.getInt;
if(traType == 1)
tra1 = nifFile.getArraySize!(Translation1)(count);
else if(traType == 2)
tra2 = nifFile.getArraySize!(Translation2)(count);
else if(traType == 3)
tra3 = nifFile.getArraySize!(Translation3)(count);
else nifFile.fail("Don't know how to handle translation type "
~ .toString(traType));
} // End of translations
count = nifFile.getInt;
debug(verbose) writefln("Number of scalings: ", count);
if(count)
{
int scaleType = nifFile.getInt;
if(scaleType == 1) sca1 = nifFile.getArraySize!(Scale1)(count);
else if(scaleType == 2) sca2 = nifFile.getArraySize!(Scale2)(count);
else if(scaleType == 3) sca3 = nifFile.getArraySize!(Scale3)(count);
else nifFile.fail("Don't know how to handle scaling type "
~ .toString(scaleType));
} // End of scalings
}
}

@ -1,110 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (effect.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 nif.effect;
import nif.record;
class Effect : Node {}
// Used for NiAmbientLight and NiDirectionalLight. Might also work for
// NiPointLight and NiSpotLight?
class Light : Effect
{
float dimmer;
Vector ambient, diffuse, specular;
override:
void read()
{
super.read();
debug(check)
if(flags != 2 && flags != 4) nifFile.warn("Unknown flags");
nifFile.getIntIs(1);
if(nifFile.getInt == 0) nifFile.warn("Light list might be missing");
dimmer = nifFile.getFloat;
ambient = nifFile.getVector;
diffuse = nifFile.getVector;
specular = nifFile.getVector;
debug(verbose)
{
writefln("Dimmer: ", dimmer);
writefln("Ambient: ", ambient.toString);
writefln("Diffuse: ", diffuse.toString);
writefln("Specular: ", specular.toString);
}
}
}
class NiTextureEffect : Effect
{
NiSourceTexture texture;
override:
void read()
{
super.read();
debug(check)
if(flags != 2 && flags != 4) nifFile.warn("Unknown flags");
int i = nifFile.wgetInt; // 0 or 1
if(i == 1) {if(nifFile.getInt == 0) nifFile.warn("List might be missing");}
else if(i != 0) nifFile.warn("Unknown value");
for(int j; j<3; j++)
{
nifFile.wgetFloatIs(1);
nifFile.wgetFloatIs(0);
nifFile.wgetFloatIs(0);
nifFile.wgetFloatIs(0);
}
nifFile.wgetIntIs(2);
nifFile.wgetIntIs(0,3);
nifFile.wgetIntIs(2);
nifFile.wgetIntIs(2);
debug(verbose) writef("Source Texture ");
getIndex();
nifFile.wgetByteIs(0);
nifFile.wgetFloatIs(1);
nifFile.wgetFloatIs(0);
nifFile.wgetFloatIs(0);
nifFile.wgetFloatIs(0);
nifFile.wgetShortIs(0);
nifFile.wgetShortIs(-75);
nifFile.wgetShortIs(0);
}
void sortOut(Record[] list)
{
super.sortOut(list);
texture = lookup!(NiSourceTexture)(list);
}
}

@ -1,212 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (extra.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 nif.extra;
import nif.record;
import nif.controlled;
import util.utfconvert;
abstract class Extra : Record
{
Extra extra;
override:
void read()
{
super.read();
debug(verbose) nifFile.writef("Extra Data ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
extra = lookup!(Extra)(list);
}
}
class NiVertWeightsExtraData : Extra
{
override:
void read()
{
super.read();
int i = nifFile.getInt;
short s = nifFile.getShort; // = number of vertices
if(s*4+2 != i)
nifFile.warn("Sizes don't add up");
debug(verbose)
{
writefln("Total bytes in record: ", i);
writefln("Number of vertex weights: ", s);
}
for(int j; j<s; j++)
{
float ff = nifFile.getFloat;
debug(verbose) writefln("Float: ", ff);
}
}
}
class NiTextKeyExtraData : Extra
{
struct Key
{
float time;
char[] string;
}
Key[] keys;
override void read()
{
super.read();
nifFile.getIntIs(0);
int keynum = nifFile.getInt;
nifFile.fitArray(keynum,8);
keys = nifRegion.allocateT!(Key)(keynum);
foreach(int i, ref Key k; keys)
{
k.time = nifFile.getFloat;
k.string = nifFile.getString;
debug(verbose)
writefln(" %d: %s @ %f ", i, makeUTF8(k.string).chomp(), k.time);
}
}
}
class NiStringExtraData : Extra
{
char[] string;
override void read()
{
super.read();
int size = nifFile.getInt;
string = nifFile.getString;
if(size != string.length + 4)
nifFile.warn("String size was incorrect.");
debug(verbose)
{
// "NCO" means 'no collision', I think
writefln("String: %s", makeUTF8(string));
}
}
}
class NiParticleGrowFade : Controlled
{
//float f1, f2;
override:
void read()
{
super.read();
nifFile.wgetFloat();
nifFile.wgetFloat();
}
}
class NiParticleColorModifier : Controlled
{
NiColorData data;
override:
void read()
{
super.read();
debug(verbose) writef("Color Data ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
lookup!(NiColorData)(list);
}
}
class NiGravity : Controlled
{
override:
void read()
{
super.read();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetInt();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
}
}
class NiPlanarCollider : Controlled
{
override:
void read()
{
super.read();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetVector();
nifFile.wgetVector();
nifFile.wgetVector();
nifFile.wgetVector();
}
}
class NiParticleRotation : Controlled
{
override:
void read()
{
super.read();
byte b = nifFile.wgetByteIs(0,1);
nifFile.wgetFloatIs(1);
nifFile.wgetFloat();
nifFile.wgetFloat();
nifFile.wgetFloat();
}
}

@ -1,139 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (misc.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/ .
*/
// This doesn't have to be part of the nif package at all.
module nif.misc;
import std.string;
import monster.util.string;
struct Vector
{
float array[3];
void set(float x, float y, float z)
{
array[0] = x;
array[1] = y;
array[2] = z;
}
char[] toString()
{
return format(array);
//return format("[", array[0], ",", array[1], ",", array[2], "]");
}
int opEquals(ref Vector v)
{
return v.array == array;
}
static assert(Vector.sizeof == 4*3);
}
unittest
{
Vector a, b;
a.set(1,2,3);
assert(a!=b);
b = a;
assert(a==b);
}
struct Vector4
{
float array[4];
void set(float x, float y, float z, float a)
{
array[0] = x;
array[1] = y;
array[2] = z;
array[3] = a;
}
char[] toString()
{
return format(array);
//return format("[", array[0], ",", array[1], ",", array[2], ",", array[3], "]");
}
int opEquals(ref Vector4 v)
{
return v.array == array;
}
static assert(Vector4.sizeof == 4*4);
}
unittest
{
Vector4 a, b;
a.set(1,2,3,5);
assert(a!=b);
b = a;
assert(a==b);
}
align(1)
struct Matrix
{
union
{
Vector v[3];
float[9] array;
}
char[] toString()
{
char[] res;
res ~= " Right: " ~ v[0].toString;
res ~= "\n Up: " ~ v[1].toString;
res ~= "\n Front: " ~ v[2].toString;
return res;
}
static assert(Matrix.sizeof == 3*3*4);
}
align(1)
struct Transformation
{
Vector pos;
Matrix rotation;
float scale;
Vector velocity;
char[] toString()
{
char[] res;
res ~= " Translation: " ~ pos.toString();
res ~= "\n" ~ rotation.toString();
res ~= "\n Scale: " ~ format(scale);
res ~= "\n Velocity: " ~ velocity.toString();
return res;
}
static assert(Transformation.sizeof == 5*Vector.sizeof + 4);
}

@ -1,106 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (nif.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 nif.nif;
import nif.base;
import nif.niffile;
import nif.record;
import core.memory;
/* The NIFMesh struct will open, read and close a .nif file. Unlike
* NIFFile (which it uses), it stores all the data from the NIF.
*/
NIFMesh nifMesh;
struct NIFMesh
{
public:
Record records[];
// Read from a file
void open(char[] file)
{
nifFile.open(file);
loadData(); // Load the data
}
// Read from a memory slice. The optitional file name is used for
// error messages only.
void open(void[] s, char[] name = "")
{
nifFile.open(s, name);
loadData(); // Load the data
}
// Clear all loaded data
void clear()
{
records = null;
// Clear the region manager
nifRegion.freeAll();
}
/*
void fail(char[] msg)
{
throw new NIFMeshException(msg);
}
*/
private void loadData()
{
// Remove any previously loaded data
clear();
// The actual parsing is done somewhere else.
nif.base.parseFile();
// Close the file
nifFile.close();
// Resolve inter-record references into object pointers.
foreach(int i, Record o; records)
if(o !is null)
{
nifFile.setRec(i, o);
o.sortOut(records);
}
// Let the records do consistency checks on their data.
foreach(int i, Record o; records)
if(o !is null)
{
nifFile.setRec(i, o);
o.check();
}
// Internal state check, used for debugging
debug(statecheck)
foreach(Record o; records)
if(o !is null) o.finalCheck();
}
}

@ -1,482 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (niffile.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 nif.niffile;
public import std.stream;
public import util.regions;
import std.string;
import nif.misc;
public import core.memory;
// Exception class thrown by most exceptions originating within the
// nif package
class NIFFileException : Exception
{
this(char[] msg) {super("NIFFile Exception: " ~ msg);}
}
/* The NIFFile struct is used for the task of reading from NIF
files. It provides specialized methods for handling common types,
records, etc., and also provides mechanisms for output and error
handling. It does not store any (non-transient) NIF data.
*/
NIFFile nifFile;
struct NIFFile
{
private:
Stream f; // Input stream
BufferedFile bf; // Used and reused for direct file input.
TArrayStream!(ubyte[]) ss; // For stream reading
// These are used for warnings and error messages only
char[] filename;
int recNum; // Record number
char[] recName; // Record name
Object recObj; // Record object
public:
/* ------------------------------------------------
* Object initialization and file management
* ------------------------------------------------
*/
// Open a file from the file system
void open(char[] file)
{
close();
// Create a BufferedFile, and reuse it later.
if(bf is null)
bf = new BufferedFile();
bf.open(file);
f = bf;
filename = file;
}
// Open a memory slice, the name is used for error messages.
void open(void[] data, char[] name)
{
close();
// Create a TArrayStream, and reuse it
if(ss is null)
{
ss = new TArrayStream!(ubyte[])(cast(ubyte[])data);
ss.writeable = false; // Read-only
}
else
{
// TArrayStream lacks the open() method. Instead, all the
// members are public.
ss.buf = cast(ubyte[])data;
ss.len = ss.buf.length;
ss.cur = 0;
}
f = ss;
filename = name;
}
// Close the file handle
void close()
{
// Close the buffered file
if(f !is null && f is bf) f.close();
f = null;
// Zero out variables
filename = null;
recNum = 0;
recName = null;
recObj = null;
}
ulong size()
in
{
if(f is null) fail("Cannot get size, file is not open");
}
body
{
return f.size();
}
bool eof() { return f.eof(); }
void seekCur(long skip)
in
{
if(f is null) fail("Cannot seek, file is not open");
}
body
{
f.seekCur(skip);
}
ulong position()
{
return f.position();
}
/* ------------------------------------------------
* Error reporting
* ------------------------------------------------
*/
// Used to format error messages
private char[] makeError(char[] str)
{
if(recNum > 0)
{
if(recName == "" && recObj !is null) recName = recObj.toString;
str = format("%s\n Record %d: %s", str, recNum-1, recName);
}
if(filename != "") str = format("%s\n File: %s", str, filename);
if(f !is null)
if(f.eof()) str = format("%s\n At end of file", str);
else str = format("%s\n Offset 0x%x", str, f.position);
return str ~ "\n";
}
void fail(char[] msg)
{
throw new NIFFileException(makeError(msg));
}
debug(warnstd) import std.stdio;
void warn(char[] msg)
{
debug(strict) fail("(fail on warning) " ~ msg);
else
{
debug(warnstd) writefln("WARNING: ", makeError(msg));
debug(warnlog) log.writefln("WARNING: ", makeError(msg));
}
}
void assertWarn(bool condition, char[] str)
{ if(!condition) warn(str); }
/*
void assertFail(bool condition, char[] str)
{ if(!condition) fail(str); }
*/
// Used for error message handling, hackish.
void setRec(int i, char[] n)
{
recNum = i+1;
recName = n;
recObj = null;
}
// This variant takes an object instead of a name. That way we only
// need to call toString when the name is needed (which it mostly
// isn't.)
void setRec(int i, Object o)
{
recNum = i+1;
recObj = o;
recName = null;
}
/* ------------------------------------------------
* Reader method templates (skip to next section
* for usable methods)
* ------------------------------------------------
*/
// Read a variable of a given type T
T getType(T)()
{
T t;
f.read(t);
return t;
}
// Read a variable and compare it to what we want it to be
template getTypeIs(T)
{
T getTypeIs(T[] pt ...)
{
T t = getType!(T)();
debug(check)
{
bool match;
foreach(T a; pt)
if(a == t) {match = true; break;}
if(!match)
{
char[] errMsg = format(typeid(T),
" mismatch: got %s, expected ", t);
if(pt.length > 1) errMsg ~= "one of: ";
foreach(T a; pt)
errMsg ~= format("%s ", a);
warn(errMsg);
}
}
return t;
}
}
debug(verbose)
{
// Read a variable of a given type T and print it to screen
template wgetType(T)
{
T wgetType()
{
T t;
f.read(t);
writefln(typeid(T), ": ", t);
return t;
}
}
// Read a variable and compare it to what we want it to be
template wgetTypeIs(T)
{
T wgetTypeIs(T pt[] ...)
{
T t = getType!(T)();
char[] wanted;
if(pt.length > 1) wanted = "one of: ";
foreach(T a; pt) wanted ~= format("%s ", a);
writefln(typeid(T), ": ", t, " (wanted %s)", wanted);
debug(check)
{
bool match;
foreach(T a; pt)
if(a == t) {match = true; break;}
if(!match)
{
warn(format(typeid(T),
" mismatch: got %s, expected %s", t, wanted));
}
}
return t;
}
}
}
// Fill the provided array of Ts
template getArrayLen(T)
{
T[] getArrayLen(T[] arr)
{
f.readExact(arr.ptr, arr.length*T.sizeof);
return arr;
}
}
// Set the size of the provided array to 'count', and fill it.
T[] getArraySize(T)(int count)
{
T[] arr;
fitArray(count, T.sizeof);
arr = cast(T[])nifRegion.allocate(count * T.sizeof);
getArrayLen!(T)(arr);
return arr;
}
// Get an array of Ts preceded by an array length of type Index
// (assumed to be an integer type)
template getArray(T,Index)
{
T[] getArray()
{
// Read array length
Index s = getType!(Index)();
// Is array larger than file?
fitArray(s,T.sizeof);
// Allocate the buffer
T[] result = cast(T[])nifRegion.allocate(s * T.sizeof);
// Read the buffer
return getArrayLen!(T)(result);
}
}
/* ------------------------------------------------
* Reader methods
* ------------------------------------------------
*/
// Strings
alias getArrayLen!(char) getString;
alias getArray!(char,int) getString;
// Other arrays
alias getArray!(int,int) getInts;
// Checks if an array of size elements each of size esize could
// possibly follow in the file.
void fitArray(int size, int esize)
in
{
assert(esize > 0);
}
body
{
if((size*esize+8) > (f.size() - f.position()) || size < 0)
fail(format(
"Array out of bounds: %d*%d + 8 bytes needed, but only %d bytes left in file",
size,esize,(f.size-f.position)));
}
// Base types
alias getType!(byte) getByte;
alias getType!(ubyte) getUbyte;
alias getType!(short) getShort;
alias getType!(ushort) getUshort;
alias getType!(int) getInt;
alias getType!(uint) getUint;
alias getType!(float) getFloat;
// Base types with error checking
alias getTypeIs!(short) getShortIs;
alias getTypeIs!(ushort) getUshortIs;
alias getTypeIs!(byte) getByteIs;
alias getTypeIs!(ubyte) getUbyteIs;
alias getTypeIs!(int) getIntIs;
alias getTypeIs!(uint) getUintIs;
alias getTypeIs!(float) getFloatIs;
debug(verbose)
{
// Base types
alias wgetType!(byte) wgetByte;
alias wgetType!(ubyte) wgetUbyte;
alias wgetType!(short) wgetShort;
alias wgetType!(ushort) wgetUshort;
alias wgetType!(int) wgetInt;
alias wgetType!(uint) wgetUint;
alias wgetType!(float) wgetFloat;
// Base types with error checking
alias wgetTypeIs!(short) wgetShortIs;
alias wgetTypeIs!(ushort) wgetUshortIs;
alias wgetTypeIs!(byte) wgetByteIs;
alias wgetTypeIs!(ubyte) wgetUbyteIs;
alias wgetTypeIs!(int) wgetIntIs;
alias wgetTypeIs!(uint) wgetUintIs;
alias wgetTypeIs!(float) wgetFloatIs;
}
else
{
// Base types
alias getByte wgetByte;
alias getUbyte wgetUbyte;
alias getShort wgetShort;
alias getUshort wgetUshort;
alias getInt wgetInt;
alias getUint wgetUint;
alias getFloat wgetFloat;
// Base types with error checking
alias getByteIs wgetByteIs;
alias getUbyteIs wgetUbyteIs;
alias getShortIs wgetShortIs;
alias getUshortIs wgetUshortIs;
alias getIntIs wgetIntIs;
alias getUintIs wgetUintIs;
alias getFloatIs wgetFloatIs;
}
// Vectors
Vector getVector()
{
Vector v;
getArrayLen!(float)(v.array);
return v;
}
Vector4 getVector4()
{
Vector4 v;
getArrayLen!(float)(v.array);
return v;
}
Vector getVectorIs(float x, float y, float z)
{
Vector v = getVector();
debug(check)
{
Vector u;
u.set(x,y,z);
if(v != u)
warn("Vector mismatch: expected " ~ u.toString ~ ", got " ~ v.toString);
}
return v;
}
debug(verbose)
{
Vector wgetVector()
{
Vector v = getVector();
writefln("vector: ", v.toString);
return v;
}
Vector4 wgetVector4()
{
Vector4 v = getVector4();
writefln("4-vector: ", v.toString);
return v;
}
}
else
{
alias getVector wgetVector;
alias getVector4 wgetVector4;
}
void getMatrix(ref Matrix m)
{
getArrayLen!(float)(m.array);
}
void getTransformation(ref Transformation t)
{
t.pos = getVector();
getMatrix(t.rotation);
t.scale = getFloat/*Is(1)*/;
t.velocity = getVectorIs(0,0,0);
}
}

@ -1,324 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (node.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 nif.node;
import nif.record;
// Tree node
abstract class Node : Named
{
ushort flags;
Transformation trafo;
//Extra properties[];
Property properties[];
Node parent;
// Bounding box info
bool hasBounding;
Vector boundPos;
Matrix boundRot;
Vector boundXYZ;
void setParent(Node p)
{
debug(veryverbose)
writefln("Setting parent of ", toString, " to ", p);
if(parent !is null)
{
char str[] = toString() ~ " already has parent " ~ parent.toString()
~ ", but setting ";
if(p !is null) str ~= p.toString;
else str ~= "to null";
nifFile.warn(str);
}
parent = p;
}
override:
void read()
{
super.read();
flags = nifFile.getUshort();
nifFile.getTransformation(trafo);
debug(verbose)
{
writefln("Flags: %x", flags);
writefln("Transformation:\n", trafo.toString);
writef("Properties: ");
}
properties = nifRegion.allocateT!(Property)(getIndexList());
if(nifFile.getInt != 0)
{
hasBounding = true;
nifFile.getIntIs(1);
boundPos = nifFile.getVector;
nifFile.getMatrix(boundRot);
boundXYZ = nifFile.getVector;
//if(name != "Bounding Box") nifFile.warn("Node name is not 'Bounding Box'");
debug(verbose)
{
writefln("Bounding box: ", boundPos.toString);
writefln(boundRot.toString);
writefln("XYZ: ", boundXYZ.toString);
}
}
}
void sortOut(Record[] list)
{
super.sortOut(list);
lookupList!(Property)(list,properties);
}
}
class NiCamera : Node
{
float left, right, top, bottom, near, far,
vleft, vright, vtop, vbottom, LOD;
override:
void read()
{
super.read();
left = nifFile.getFloat;
right = nifFile.getFloat;
top = nifFile.getFloat;
bottom = nifFile.getFloat;
near = nifFile.getFloat;
far = nifFile.getFloat;
vleft = nifFile.getFloat;
vright = nifFile.getFloat;
vtop = nifFile.getFloat;
vbottom = nifFile.getFloat;
LOD = nifFile.getFloat;
nifFile.getIntIs(-1);
nifFile.getIntIs(0);
debug(verbose)
{
writefln("Camera frustrum:");
writefln(" Left: ", left);
writefln(" Right: ", right);
writefln(" Top: ", top);
writefln(" Bottom: ", bottom);
writefln(" Near: ", near);
writefln(" Far: ", far);
writefln("View port:");
writefln(" Left: ", vleft);
writefln(" Right: ", vright);
writefln(" Top: ", vtop);
writefln(" Bottom: ", vbottom);
writefln("LOD adjust: ", LOD);
}
}
}
class NiAutoNormalParticles : Node
{
NiAutoNormalParticlesData data;
override:
void read()
{
super.read();
debug(check)
{
if(flags & 0xffff-6)
nifFile.warn("Unknown flags");
}
debug(verbose) writef("Particle Data ");
getIndex();
nifFile.getIntIs(-1);
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiAutoNormalParticlesData)(list);
debug(check)
{
if(castCheck!(NiParticleSystemController)(controller)
&& castCheck!(NiBSPArrayController)(controller))
nifFile.warn("In " ~ toString ~ ": did not expect controller "
~ controller.toString);
}
}
}
class NiRotatingParticles : Node
{
NiRotatingParticlesData data;
override:
void read()
{
super.read();
debug(check)
{
if(flags & 0xffff-6)
nifFile.warn("Unknown flags");
}
//nifFile.getIntIs(0);
debug(verbose) writef("Particle Data ");
getIndex();
nifFile.getIntIs(-1);
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiRotatingParticlesData)(list);
debug(check)
{
if(castCheck!(NiParticleSystemController)(controller)
&& castCheck!(NiBSPArrayController)(controller) )
nifFile.warn("In " ~ toString ~ ": did not expect controller "
~ controller.toString);
}
//castWarn!(NiParticleSystemController)(controller);
}
}
class NiTriShape : Node
{
NiTriShapeData data;
NiSkinInstance skin;
override:
void read()
{
super.read();
debug(verbose)
{
// If this is correct, it suggests how one might decode
// other flags. Check if this one is correct in sortOut().
if(flags&0x40) writefln("Mesh has no normals?");
}
debug(check)
{
if(flags & (0xffff-0x47))
nifFile.warn("Unknown flags were set");
}
//nifFile.getIntIs(0);
debug(verbose) writef("Mesh index: ");
getIndex();
debug(verbose) writef("Skin index: ");
getIndex();
}
void sortOut(Record[] list)
{
super.sortOut(list);
data = lookup!(NiTriShapeData)(list);
skin = lookup!(NiSkinInstance)(list);
debug(check)
{
if(data is null) nifFile.warn("Data missing from NiTriShape");
if(castCheck!(NiGeomMorpherController)(controller)
&& castCheck!(NiUVController)(controller)
&& castCheck!(NiVisController)(controller) )
nifFile.warn("In " ~ toString ~ ": did not expect controller "
~ controller.toString);
if((data.normals.length == 0) && (flags & 0x40 == 0) ||
(data.normals.length != 0) && (flags & 0x40))
nifFile.warn("0x40 present but normals missing, or vice versa");
}
}
}
class NiNode : Node
{
Node children[];
Effect effects[];
override:
void read()
{
super.read();
debug(verbose)
{
if(flags & 1) writefln(" 0x01 Hidden");
if(flags & 2) writefln(" 0x02 Collision detection?");
if(flags & 4) writefln(" 0x04 Collision detection2 ?");
if(flags & 8) writefln(" 0x08 Unknown but common");
if(flags & 0x20) writefln(" 0x20 Unknown");
if(flags & 0x40) writefln(" 0x40 Unknown");
if(flags & 0x80) writefln(" 0x80 Unknown");
}
debug(check)
if(flags & 0x10) nifFile.warn("Unknown flags were set");
debug(verbose) writef("Child nodes: ");
children = nifRegion.allocateT!(Node)(getIndexList());
debug(verbose) writef("Effects: ");
effects = nifRegion.allocateT!(Effect)(getIndexList());
}
void sortOut(Record[] list)
{
super.sortOut(list);
lookupList!(Node)(list,children);
lookupList!(Effect)(list,effects);
if(castCheck!(NiKeyframeController)(controller)
&& castCheck!(NiVisController)(controller)
&& castCheck!(NiPathController)(controller))
nifFile.warn("In " ~ toString ~ ": did not expect controller "
~ controller.toString);
foreach(Node n; children)
n.setParent(this);
}
}

@ -1,313 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (property.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 nif.property;
import nif.record;
abstract class Property : Named
{
// The meaning of these depends on the actual property type.
ushort flags;
override:
void read()
{
super.read();
flags = nifFile.getUshort;
debug(verbose) writefln("Flags: %x", flags);
}
}
// Check the specs on this one again
class NiTexturingProperty : Property
{
/* Apply mode:
0 - replace
1 - decal
2 - modulate
3 - hilight // These two are for PS2 only?
4 - hilight2
*/
int apply;
struct Texture
{
bool inUse;
NiSourceTexture texture;
/* Clamp mode
0 - clampS clampT
1 - clampS wrapT
2 - wrapS clampT
3 - wrapS wrapT
*/
/* Filter:
0 - nearest
1 - bilinear
2 - trilinear
3, 4, 5 - who knows
*/
int clamp, set, filter;
short ps2L, ps2K, unknown2;
void read(NiTexturingProperty caller)
{
if(nifFile.getInt == 0)
{
debug(verbose) writefln("No texture");
return;
}
inUse = 1;
debug(verbose) writef(" Texture ");
caller.getIndex();
clamp = nifFile.getIntIs(0,1,2,3);
filter = nifFile.getIntIs(0,1,2);
set = nifFile.getInt;
// The combination 1222, 322, 212 was used in a bump map
// NIF. Might just be bogus numbers, I should probably allow all
// values here since we ignore them anyway.
ps2L = nifFile.getShortIs(0,1222);
ps2K = nifFile.getShortIs(-75,-2,322);
debug(verbose)
{
writefln(" Clamp ", clamp);
writefln(" Filter ", filter);
writefln(" Set? ", set);
writefln(" ps2L ", ps2L);
writefln(" ps2K ", ps2K);
}
unknown2 = nifFile.wgetShortIs(0,257,212);
}
}
/*
* The textures in this list is as follows:
*
* 0 - Base texture
* 1 - Dark texture
* 2 - Detail texture
* 3 - Gloss texture (never used?)
* 4 - Glow texture
* 5 - Bump map texture
* 6 - Decal texture
*/
Texture[7] textures;
override:
void read()
{
super.read();
if(flags > 1) nifFile.warn("Unknown flags");
apply = nifFile.getInt;
debug(verbose) writefln("Apply mode: ", apply);
nifFile.getIntIs(7);
textures[0].read(this); // Base
textures[1].read(this); // Dark
textures[2].read(this); // Detail
nifFile.getIntIs(0); // Gloss
textures[4].read(this); // Glow
textures[5].read(this); // Bump map
if(textures[5].inUse)
{
float lumaScale = nifFile.wgetFloat;
float lumaOffset = nifFile.wgetFloat;
Vector4 lumaMatrix = nifFile.wgetVector4;
}
textures[6].read(this); // Decal
}
void sortOut(Record[] list)
{
super.sortOut(list);
foreach(ref Texture t; textures)
if(t.inUse) t.texture = lookup!(NiSourceTexture)(list);
}
}
class NiMaterialProperty : Property
{
Vector ambient, diffuse, specular, emissive;
float glossiness, alpha;
override:
void read()
{
super.read();
if(flags != 1) nifFile.warn("Unknown flags");
ambient = nifFile.getVector;
diffuse = nifFile.getVector;
specular = nifFile.getVector;
emissive = nifFile.getVector;
glossiness = nifFile.getFloat;
alpha = nifFile.getFloat; // Use Alpha Property when this is not 1
debug(verbose)
{
writefln("Ambient: ", ambient.toString);
writefln("Diffuse: ", diffuse.toString);
writefln("Specular: ", specular.toString);
writefln("Emissive: ", emissive.toString);
writefln("Glossiness: ", glossiness);
writefln("Alpha: ", alpha);
}
}
}
class NiVertexColorProperty : Property
{
/* Vertex mode:
0 - source ignore
1 - source emmisive
2 - source amb diff
Lighting mode
0 - lighting emmisive
1 - lighting emmisive ambient/diffuse
*/
int vertmode, lightmode;
override:
void read()
{
super.read();
if(flags != 0) nifFile.warn("Unknown flags");
vertmode = nifFile.getIntIs(0,1,2);
lightmode = nifFile.getIntIs(0,1);
debug(verbose)
{
writefln("Vertex mode: ", vertmode);
writefln("Lighting mode: ", lightmode);
}
}
}
alias NiWireframeProperty NiDitherProperty;
alias NiWireframeProperty NiSpecularProperty;
class NiWireframeProperty : Property
{
override:
void read()
{
super.read();
if(flags != 1) nifFile.warn("Unknown flags");
}
}
class NiShadeProperty : Property
{
override:
void read()
{
super.read();
if(flags != 0) nifFile.warn("Unknown flags");
}
}
class NiAlphaProperty : Property
{
/*
In NiAlphaProperty, the flags have the following meaning.
Bit 0 : alpha blending enable
Bits 1-4 : source blend mode
Bits 5-8 : destination blend mode
Bit 9 : alpha test enable
Bit 10-12 : alpha test mode
Bit 13 : no sorter flag ( disables triangle sorting )
blend modes (glBlendFunc):
0000 GL_ONE
0001 GL_ZERO
0010 GL_SRC_COLOR
0011 GL_ONE_MINUS_SRC_COLOR
0100 GL_DST_COLOR
0101 GL_ONE_MINUS_DST_COLOR
0110 GL_SRC_ALPHA
0111 GL_ONE_MINUS_SRC_ALPHA
1000 GL_DST_ALPHA
1001 GL_ONE_MINUS_DST_ALPHA
1010 GL_SRC_ALPHA_SATURATE
test modes (glAlphaFunc):
000 GL_ALWAYS
001 GL_LESS
010 GL_EQUAL
011 GL_LEQUAL
100 GL_GREATER
101 GL_NOTEQUAL
110 GL_GEQUAL
111 GL_NEVER
Taken from:
http://niftools.sourceforge.net/doc/nif/NiAlphaProperty.html
Right now we only use standard alpha blending (see the Ogre code
that sets it up) and it appears that this is the only blending
used in the original game. Bloodmoon (along with several mods) do
however use other settings, such as discarding pixel values with
alpha < 1.0. This is faster because we don't have to mess with the
depth stuff like we did for blending. And OGRE has settings for
this too.
*/
// Tested against when certain flags are set (see above.)
ubyte threshold;
override:
void read()
{
super.read();
ubyte b = nifFile.getUbyte;
debug(verbose) writefln("Unknown byte: ", b);
}
}
class NiZBufferProperty : Property
{
override:
void read()
{
super.read();
debug(verbose)
{
if(flags&1) writefln(" 0x01 ZTest");
if(flags&2) writefln(" 0x02 ZWrite");
}
if(flags & 0xfc) nifFile.warn("Unknown flags");
}
}

@ -1,302 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (record.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 nif.record;
public
{
import util.regions;
import nif.base;
import nif.controller;
import nif.controlled;
import nif.node;
import nif.data;
import nif.extra;
import nif.property;
import nif.effect;
import nif.niffile;
import nif.misc;
import std.string;
}
// Base class for all NIF records
abstract class Record
{
protected:
// List of dependency indices. We throw this away after the entire
// file is loaded and the dependencies have been converted to object
// pointers.
RegionBuffer!(int) depList;
debug(statecheck)
{
// An internal 'state', this check is only intended for
// debugging purposes (hence the surrounding debug block ;-)
int state;
/* 0 - nothing has been done
* 1 - record has been read
* 2 - sortOut has been called
* 3 - check has been called
*/
}
public:
// Allocate objects of this class (and all subclasses) in the
// nifRegion. This means we can allocate and deallocate the entire
// nif tree very quickly, without involving the GC, and with zero
// chance of memory leaks or heap fragmentation.
new(uint sz)
{
return nifRegion.allocate(sz).ptr;
}
delete(void *p) { assert(0); }
debug(statecheck)
final void finalCheck()
{
debug(veryverbose)
writefln("Final check on ", this);
assert(state==3);
}
// Read record data from file f. All indices are stored temporarily
// as integers, as they appear in the file.
void read()
{
// Allocate the dependency list. 50 should be enough entries.
depList = nifRegion.getBuffer!(int)(0,50);
debug(veryverbose)
writefln("Reading ", this, " at offset %x", nifFile.position);
debug(statecheck) assert(state++ == 0);
}
// Sort out dependencies between records. Called after all records
// have been read from file. Paramter 'list' contains all records in
// the order they appear in the file. Used to convert integer
// indices into object pointers, and checking that all types are
// correct. Can also be used for data checks than only require this
// objects dependencies.
void sortOut(Record[] list)
{
debug(veryverbose) writefln("Sorting out ", this);
debug(statecheck) assert(state++ == 1);
}
// Consistancy check. Called after all dependencies have been
// sorted. Can be used for checking that vertex counts are the same
// in different records, etc. It checks the depList array.
void check()
{
debug(veryverbose) writefln("Consistancy check on ", this);
// Check that all dependencies have been resolved. delList is
// successively sliced down to zero when indices are looked up.
assert(depList.length == 0);
debug(statecheck) assert(state++ == 2);
}
// Convert an integer record index to an object pointer of type T.
template lookup(T: Record)
{
T lookup(Record[] list)
{
// Get the next dependency from the list
int i = depList[0];
depList = depList[1..depList.length()];
debug(verbose)
{
T t = lookupCast!(T)(i, list);
debug(veryverbose)
{
writef(" Resolved ", i, " to ");
if(t is null) writefln("(null)");
else writefln(t);
}
return t;
}
else
return lookupCast!(T)(i, list);
}
}
template lookupList(T: Record)
{
void lookupList(Record[] list, T[] deps)
{
foreach(ref T t; deps)
t = lookup!(T)(list);
}
}
// Reads an index (int) and adds it to the list of
// dependencies. These can be returned later by lookup().
void getIndex()
{
depList ~= nifFile.getInt;
debug(verbose) writefln("Index ", depList[depList.length()-1]);
}
int getIndexList()
{
int[] l = nifFile.getInts;
int num;
// Only add non-null references
foreach(int i; l)
if(i != -1)
{
depList ~= i;
num++;
}
//depList ~= l;
debug(verbose)
{
writef("Index list: ");
foreach(int i; l)
writef(i, " ");
writefln("(%d kept)", num);
}
return num;
}
}
// Lookup with casting and error checking.
template lookupCast(T: Record)
{
T lookupCast(int i, Record[] list)
{
if(i == -1) return null;
if(i < 0 || i >= list.length)
nifFile.fail(format("Record index %d out of bounds (got %d records.)",
i, list.length));
Record r = list[i];
if(r is null)
{
nifFile.warn("Referenced an unknown record type");
return null;
}
T t = cast(T)r;
if(t is null)
nifFile.fail("Cannot convert " ~ r.toString() ~ " to " ~
(cast(TypeInfo_Class)typeid(T)).info.name);
return t;
}
}
template castCheck(T: Record)
{
bool castCheck(Record r)
{
if(r !is null && cast(T)r is null) return true;
else return false;
}
}
template castWarn(T: Record)
{
debug(check)
{
void castWarn(Record r)
{
if(castCheck!(T)(r))
nifFile.warn("Could not cast " ~ r.toString() ~ " to " ~
(cast(TypeInfo_Class)typeid(T)).info.name);
}
}
else
void castWarn(Record r) {};
}
class NiSkinInstance : Record
{
NiSkinData data;
NiNode root;
NiNode bones[];
override:
void read()
{
super.read();
debug(verbose) writef("Skin data ");
getIndex();
debug(verbose) writef("Scene root ");
getIndex();
debug(verbose) writef("Bone ");
bones = nifRegion.allocateT!(NiNode)(getIndexList());
}
void sortOut(Record[] l)
{
super.sortOut(l);
data = lookup!(NiSkinData)(l);
root = lookup!(NiNode)(l);
lookupList!(NiNode)(l,bones);
}
void check()
{
super.check();
debug(check) if(data !is null)
{
if(bones.length != data.weights.length)
nifFile.warn(format("NiSkinInstance has ", bones.length,
" bones, but NiSkinData has ", data.weights.length));
/*
int meshCount = root.getFirstMesh(r).data.vertices.length/3;
foreach(int i, NiSkinData.Weight[] wl; data.weights)
{
if(bones[i] is null)
r.warn("Bone object missing!");
if(meshCount < data.weights[i].length)
r.warn(format("More skin weights than vertices in bone %d (%d < %d)",
i, meshCount, data.weights[i].length));
foreach(ref NiSkinData.Weight w; data.weights[i])
if(w.vertex >= meshCount)
r.warn("Skin weight vertex index out of bounds");
}
*/
}
}
}
Loading…
Cancel
Save