MDL

#11
I've corrected some info in the header structure in a previous post. I think all the info is understood except for the first byte which seems to be 0 for non-animated MDLs, 1 for models with skeletal like animation (including treasure chests) exclusively and 4 for MDLs with deformer/stop-motion type animation (eg. stone face / KF1 boss baddy) only. I can't think of any models that combine the two.

Many of the monsters have broken joints, but some like the golems are smooth. I don't know if the MDLs can actually do skinned (vertex weighted) animations of if the stop-motion (deformer based) type of animation is being applied. It may be skinning is supported but the modelers were too lazy to set it up for all of the monster.

I've noticed the basic layout of the MDL files is pretty close to the PSOne HMD (high-level all in one) format though radically simplified. Using the PSOne TIM format seems pretty inappropriate. It really doesn't make sense for PC hardware, so I'm guessing the MDL format was just cobbled together from available code both so the PSOne data could be transferred over simply and because that was probably what the programmers knew / had tools for. The CP files seem to just hold the raw signal processing data that modulates the animations.

Since the MDL files look like HMD files that probably indicates the Shadow Tower files were also kept in HMD format on the game disc. Which I think would make things easier because it would mean all the info for a particular monster etc should be in one place / structured.


EDITED: Yeah the golems that appear to have a skeletal walk appear to use the deformer animation type exclusively (instead of skeleton animation) ...Probably combining the two isn't kosher even though according to the format it looks possible, Som may never have implemented it. Something to test perhaps.
Reply

#12
Quote:If you have any simple MDO files you've prepared, if you attach them to a post like you did before I can find them later when I need them.

Here is a perfect one for core testing simple 4 verts easy to manipulate but gives away the structure of basic MDO and now if you don't mind counting and I mean massive counting you can access ANY vert or line and move it therfore manualy changing it's shape......... once I know where the model data is, in an MDL one will be able to alter prexisting mdls to give them a new look.WITH out breaking the model........ Please forgive the cramped -ness of my model here , imagine a big one:) ‎  Anyway as you can see I have this plane001 MDO 98 percent mapped out still a few blocks left to place but , feel free to run your own test they are easy with the mdo mapped................ ‎  this shows my work is conclusive.......also som uses mili meters on verticies..... gotta love the metric system:) ...........Happy modeling............................ML


Attached Files Thumbnail(s)
   

.mdo   plane001.mdo (Size: 252 bytes / Downloads: 113)
Reply

#13
It will make a million times more sense if you draw your boxes in the hexidecimal viewport. That data inspector thing looks pretty handy. I just jockey between the the Windows calculator to make sure I have the numbers right. If you try making a selection box around the area you want to count your hexeditor will probably do the counting for you.

The text display on the right hand column is really just supplemental. You don't want to pay any mind to it unless your file actually has text.


EDITED: Thanks for the millimeters hint, I wasn't really sure what the metric is. Do you know anything about the way Som interprets the uv components? The texture coordinates that is. Playing with the MDL files I've noticed you can indeed make the texture repeat.

The texture coordinates in the MDL files are 8bits for the U and 8bits for the V (or S/T or whatever) ...I don't know if they're signed or not, or if they are supposed to map from 0.0 to 1.0/-1.0 or what. I just plan on monkeying with them until I get it right once I'm able to load up a texture mapped model.
Reply

#14
BREAKTHROUGH.............I have succesfully done MDO injection...... I took a cube mdo I made, then made a plane mdo, this time I have the plane with 8 points (trust me there was a reason for 8). I then took the cubes mdo and isolated the cubes data...... Replaced it with the plane's data....... then erased all the remaining data........ and POW now I have a plane WITHOUT re doing the whole thing ..... more good news is.....SOM actually has a little brain......... I moved the verticies of the plane (now that is has 8) I moved specificly the top left one to 40 centimeters below the default........ and amazinly Som parented the other verticies that are related to that point.... and moved them automaticly, and did not move just the one....... trust me it's great news, if you don't know why already I will tell you......... So with all that in mind I have proven cool things.........A you DON'T have to be an alien to read hex code....... B my INJECTION therory is solid.... And C once I can inject in to MDL files the animation WILL move the new verticies of the NEW model.........So Holy, if you can map me a simple mdl WITH animation like object 0004 , I will be able to test my therory...........let me know if you can , since you said you have the structure mapped ......... ML


Attached Files Thumbnail(s)
   
Reply

#15
I'm just working on importing/saving the files so that you will be able to take any model files you find/make with your favorite editor and convert those to Som files or vice versa. Below is the code I've written so far if that helps you understand what I'm up to.

[code=SomFileHelper.h]
/*
Open Asset Import Library (ASSIMP)
----------------------------------------------------------------------

Copyright © 2006-2008, ASSIMP Development Team
All rights reserved.

Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:

* Redistributions of source code must retain the above
‎  copyright notice, this list of conditions and the
‎  following disclaimer.

* Redistributions in binary form must reproduce the above
‎  copyright notice, this list of conditions and the
‎  following disclaimer in the documentation and/or other
‎  materials provided with the distribution.

* Neither the name of the ASSIMP team, nor the names of its
‎  contributors may be used to endorse or promote products
‎  derived from this software without specific prior
‎  written permission of the ASSIMP Development Team.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

----------------------------------------------------------------------
*/


//
//! @file Definition of in-memory structures for the HL2 MDL file format
// ‎  and for the HalfLife text format (SMD)
//
// The specification has been taken from various sources on the internet.


#ifndef AI_SWORDOFMOONLIGHT_H_INC
#define AI_SWORDOFMOONLIGHT_H_INC

#include "./../include/Compiler/pushpack1.h"

namespace Assimp_x //! experimental
{
struct aiScene2;
}

namespace Assimp{

namespace MDL{

struct Header_SOM
{
//! 16 bytes
uint8_t unknown00; //! perhaps bitwise flags
uint8_t num_anims; //! reference frame animation
uint8_t num_stops; //! stop-motion like animation
uint8_t num_skins; //! texture images
uint8_t num_parts; //! one or more required
uint8_t unknown03; //! perhaps always one
int32_t off_anims; //! dword anim/stop/skin block offset
int16_t unknown04; //! perhaps always zero
int16_t add_anims; //! dword sizeof anims block
int16_t add_stops; //! dword sizeof stops block

//! this is the actual order of the data blocks in the file
int32_t offset(int fourcc, int pt=0)const
{
switch(fourcc)
{ ‎ 
case 'base': return 4+7*num_parts;

case 'face': return 4+AI_BE(parts[pt].off_faces);
case 'vert': return 4+AI_BE(parts[pt].off_verts);
case 'norm': return 4+AI_BE(parts[pt].off_norms);

case 'anim': return 4+AI_BE(off_anims);
case 'stop': return 4+AI_BE(off_anims)
‎  ‎  +AI_BE(add_anims);
case 'skin': return 4+AI_BE(off_anims)
‎  ‎  +AI_BE(add_anims)
+AI_BE(add_stops);

default: return 0x7FFFFFFF;
}
}

struct //! 28 bytes (these may actually be 16bit)
{
int32_t off_verts; //! dword offset to vertex block
int32_t num_verts; //!
int32_t off_norms; //! dword offset to normal block
int32_t num_norms; //!
int32_t off_faces; //! dword offset to face block
int32_t num_faces; //!
int32_t reserve00; //! probably always zero
}parts[1] PACK_STRUCT;

} PACK_STRUCT;

union Packet_SOM //! 4 bytes
{
struct{ uint16_t lo, hi; };

struct{ uint8_t s, t, u, v; };

} PACK_STRUCT;

struct Bitmap_SOM //Playstation TIM format
{
//! 8 bytes
uint8_t ‎  ‎  ‎  id; ‎  //! historically always 0x10
uint8_t version; ‎  //! historically always zero
int16_t ‎  ‎  ‎  :16; //! reserved
uint8_t ‎  pmode:3; ‎  //! data block bits per pixel
uint8_t ‎  ‎  cf:1; ‎  //! CLUT (color lookup table) present
uint8_t ‎  ‎  ‎  :4; ‎  //! reserved
‎  ‎  uint8_t ‎  ‎  ‎  :8; ‎  //! reserved

typedef struct
{
//! 12 bytes
uint32_t bnum; //! sizeof block in bytes
uint16_t dx; ‎  //! should not be non-zero
uint16_t dy; ‎  //! should not be non-zero
uint16_t w; ‎  ‎  //! width in 16bit units
uint16_t h; ‎  ‎  //! height

/* Data/CLUT entries
* depends on the following settings for pmode
* 0x00: 4bits per texel lookup into CLUT (up to 16x16)
* 0x01: 8bits per texel lookup into CLUT (up to 256x256)
* 0x02: 5bits per rgb plus highest order transparency mode bit
* 0x03: 2/3rds of a 24-bit rgb triplet (3 entries per two texels)
* 0x04: mixed mode (interpretation requires external info)
*/
union
{
uint16_t data[1];
uint16_t clut[1];
};

}BLOCK PACK_STRUCT;

const BLOCK *data_block()const
{
const BLOCK *out = clut_block();

if(!out) return (const BLOCK*)(this+1);

return (const BLOCK*)((const char*)out+AI_BE(out->bnum));
}
const BLOCK *clut_block()const
{
return (const BLOCK*)(cf?this+1:NULL);
}

} PACK_STRUCT;

} // end namespace MDL

class SomFileHelper
{
public:

bool detect(const MDL::Header_SOM*)const;

const int32_t *offset(const MDL::Header_SOM *in, int fourcc, int pt=0)const;

bool unpack(const MDL::Header_SOM*,
const MDL::Packet_SOM**, int fourcc, int pt=0)const;
bool bitmap(const MDL::Header_SOM*,
const MDL::Bitmap_SOM**)const;

template<typename t>
const t *offset(const MDL::Header_SOM *in, const t**out, int cc, int pt=0)const
{
const t *off = (const t*)offset(in,cc,pt); if(out) *out = off; return off;
}
template<typename t>
bool unpack(const MDL::Header_SOM *in, const t **p, int fourcc, int pt=0)const
{
return unpack(in,(const MDL::Packet_SOM**)p,fourcc);
} ‎ 
template<typename t>
bool bitmap(const MDL::Header_SOM *in, const t **p)const
{
return bitmap(in,(const MDL::Bitmap_SOM**)p);
} ‎ 

void sizecheck(const void *szPos)const
{
if(!szPos||(const unsigned char*)szPos>pcEof)
{
throw new ImportErrorException("Invalid Sword of Moonlight MDL file. The file is too small or contains invalid data");
}
}

SomFileHelper(const void *eof, aiScene *out=0);

~SomFileHelper(); //flushes pAssimp


private:

mutable aiScene *pAssimp;

mutable Assimp_x::aiScene2 *pAssimp2;

const void *pcEof;
};

} // end namespace Assimp

#include "./../include/Compiler/poppack1.h"

#endif // ! AI_SWORDOFMOONLIGHT_H_INC
[/code]
Reply

#16
[code=SomFileHelper.cpp]
/** @file Implementation of the Sword of Moonlight MDL/MDO helper routines */

#include "AssimpPCH.h"

#ifndef max
#define max(a, b) ‎  (((a) > (b)) ? (a) : (b))
#endif

// internal headers
#include "SomFileHelper.h"

using namespace Assimp;
using namespace Assimp_x;

namespace Assimp_x{ //! experimental

enum aiParameterType
{
aiParameterType_VERTEX ‎  = 0x1,

aiParameterType_NORMAL ‎  = 0x2,

aiParameterType_COLOR ‎  ‎  = 0x4,

//! or aiParameterType_TEXTURECOORD?
aiParameterType_TEXTURECOORDS = 0x8,

_aiParameterType_Force32Bit = 0x9fffffff
}; //! enum aiParameterType

enum aiFaceFeature
{
aiFaceFeature_NORMAL = 0x1,

aiFaceFeature_COLOR ‎  = 0x2,

_aiFaceFeature_Force32Bit = 0x9fffffff
}; //! enum aiFaceFeature

struct aiFace2 : public aiFace
{
/** Bitwise combination of the members of the #aiFaceFeature enum.
* aiFaceFeature_NORMAL: mIndices[0] is face normal index
* aiFaceFeature_COLOR: ‎  mIndices[1] is face color index
*/
unsigned int mFaceFeatures;

/** Bitwise combination of the members of the #aiParameterType enum.
* This specifies whether the corresponding index looks in the mIndices
* array when the bits are not set or into another aiFace2 when set.
*/
unsigned int mParameterModes; ‎ 

//! one parameter exists per corner per type
unsigned int mNumCorners;

//! offset between like parameter indices
unsigned int mStride;

/** first vertex index into mIndices.
* mIndices[0] is reserved for the face normal.
* Therefore if mVertices, mNormals, etc, is 0
* (and the corresponding mParameterModes bit is unset)
* the parameter data is taken not to exist.
*/
unsigned int mVertices;

//! tangents and bitangents overlap
unsigned int mNormals;

//! color channels overlap
unsigned int mColors;

//! uv channels overlap
unsigned int mTextureCoords;

//! overrides mesh mMaterialIndex member
unsigned int mMaterialIndex;

#ifdef __cplusplus

//! Default constructor
aiFace2()
{
mFaceFeatures = 0x00000000;
mParameterModes = 0x00000000;

mNumCorners = mStride = 0;

mVertices = mNormals = mColors = mTextureCoords = 0;

mMaterialIndex = 0;
}
#endif // __cplusplus
};

struct aiMesh2 : public aiMesh
{
//! tangents and bitangents overlap
unsigned int mNumNormals;

//! color channels overlap
unsigned int mNumColors;

//! uv channels overlap
unsigned int mNumTextureCoords;

C_STRUCT aiFace2* mFaces2;

/** Number of mFaces2 materials
* Used to pad aiScene::mNumMeshes
* so aiNode::mMeshes can reference
* both aiMeshes and aiMeshes2.
*/
unsigned int mNumMaterials;

/** Parameters shared across meshes
* If set this should be a mesh in the
* scene mMeshes2 member. To see how
* it works see GetPosition() below.
* Not meant to be recursive.
*/
C_STRUCT aiMesh2* mSharedParameters;

#define _AIMESH2_GETPARAMETER(Ps,Ns)\
\
if(mSharedParameters)\
{\
if(pIndex<mSharedParameters->mNum##Ns)\
{\
if(mSharedParameters->m##Ps==NULL) return false;\
\
*pOut = mSharedParameters->m##Ps[pIndex]; return true;\
}\
else pIndex-=mSharedParameters->mNum##Ns;\
}\
else if(m##Ps==NULL||pIndex>=mNum##Ns) return false;\
\
*pOut = m##Ps[pIndex]; return true;


bool GetPosition(unsigned int pIndex, aiVector3D* pOut)const
{ _AIMESH2_GETPARAMETER(Vertices,Vertices) }

bool GetNormal(unsigned int pIndex, aiVector3D* pOut)const
{ _AIMESH2_GETPARAMETER(Normals,Normals) }

bool GetTangent(unsigned int pIndex, aiVector3D* pOut)const
{ _AIMESH2_GETPARAMETER(Tangents,Normals) }

bool GetBitangent(unsigned int pIndex, aiVector3D* pOut)const
{ _AIMESH2_GETPARAMETER(Bitangents,Normals) }

bool GetVertexColor(unsigned int pChannel, unsigned int pIndex, aiColor4D* pOut)const
{
if(pChannel>=AI_MAX_NUMBER_OF_COLOR_SETS) return false;

_AIMESH2_GETPARAMETER(Colors[pChannel],Colors)
}

//! GetTextureCoords or GetTextureCoord?
bool GetTextureCoord(unsigned int pChannel, unsigned int pIndex, aiVector3D* pOut)const
{
if(pChannel>=AI_MAX_NUMBER_OF_TEXTURECOORDS) return false;

_AIMESH2_GETPARAMETER(TextureCoords[pChannel],TextureCoords)
}

#undef _AIMESH2_GETPARAMETER

/** Find face for shared parameter indices
* The shared faces are logically mapped to the parameter space
* but do not constitute part of the mesh. That is they are for
* referencing parameters only.
*/
aiFace2 *FindFace2(unsigned int pIndex)const
{
if(mSharedParameters)
{
if(pIndex<mSharedParameters->mNumFaces)
{
if(mSharedParameters->mFaces2==NULL) return NULL;

return mSharedParameters->mFaces2+pIndex;
}
else pIndex-=mSharedParameters->mNumFaces;
}
else if(pIndex>mNumFaces||!mFaces2==NULL) return NULL;

return mFaces2+pIndex;
}

#ifdef __cplusplus

//! Default constructor. Initializes all members to 0
aiMesh2()
{
mNumNormals = mNumColors = mNumTextureCoords = 0;

mFaces2 = NULL; mNumMaterials = 0;

mSharedParameters = NULL;
}

~aiMesh2()
{
if(mFaces2) delete [] mFaces2;
}
#endif // __cplusplus

#define _AIMESH2_HASPARAMETERS(TEST,F)\
\
if(TEST) return true; if(mSharedParameters==NULL) return false;\
\
return mSharedParameters->Has##F;

inline bool HasPositions()const
{ _AIMESH2_HASPARAMETERS(mVertices!=NULL&&mNumVertices>0,Positions()) }

//! instead of this use hasFaces2()
inline bool HasFaces()const{ return false; }

inline bool HasFaces2()const
{ _AIMESH2_HASPARAMETERS(mFaces2!=NULL&&mNumFaces>0,Faces2()) }

inline bool HasNormals()const
{ _AIMESH2_HASPARAMETERS(mNormals!=NULL&&mNumNormals>0,Normals()) }

inline bool HasTangentsAndBitangents()const
{ _AIMESH2_HASPARAMETERS(mTangents!=NULL&&mBitangents!=NULL&&mNumNormals>0,TangentsAndBitangents()) }

inline bool HasVertexColors(unsigned int pIndex)const
{
if(pIndex>=AI_MAX_NUMBER_OF_COLOR_SETS) return false;

_AIMESH2_HASPARAMETERS(mColors[pIndex]!=NULL&&mNumColors>0,VertexColors(pIndex))
}

inline bool HasTextureCoords(unsigned int pIndex)const
{
if(pIndex>=AI_MAX_NUMBER_OF_TEXTURECOORDS) return false;

_AIMESH2_HASPARAMETERS(mTextureCoords[pIndex]!=NULL&&mNumTextureCoords>0,TextureCoords(pIndex))
}

#undef _AIMESH2_HASPARAMETERS
}; ‎ 

#define AI_SCENE_FLAGS2_INCOMPLETE 0x1

struct aiScene2 : public aiScene
{
unsigned int mFlags2;

/** The array of meshes.
*
* Use the indices given in the aiNode structure to access
* this array. The array is mNumMeshes in size.
*/
C_STRUCT aiMesh2** mMeshes2;

/**Populates aiScene members of pScene with own data and deletes self
* Equivalent to PopulateSceneSublevel() when out is 'this'.
*/
aiScene *TransferSceneDownLevel(aiScene *out = NULL);

/*Populates aiScene base members reflecting aiScene2 members*/
bool PopulateSceneSubLevel()
{
return TransferSceneDownLevel(this)!=NULL?true:false;
}

#ifdef __cplusplus
/** Constructor */
aiScene2(aiScene *cp) : aiScene(*cp)
{
mFlags2 = 0x00000000; mMeshes2 = NULL;
}
#endif // __cplusplus
};

} //end namespace Assimp_x

class DownLevelProcess
{
struct ProcessMeshHelper2;

public:

static int ProcessMesh2(const aiMesh2 *pIn, aiMesh** pOut, int iMaxOut)
{
return ProcessMeshHelper2(pIn,pOut,iMaxOut);
}

private:

struct ProcessMeshHelper2
{
/** The input will be decomposed into separate
* materials upto iMaxOut count. Any remaining meshes
* will not be carried over.
*/
ProcessMeshHelper2(const aiMesh2 *pIn, aiMesh** pOut, int iMaxOut);

~ProcessMeshHelper2();

inline operator int(){ return iCompleted; }

protected:

struct Parameters
{
union
{
//! omitted indices are presently -1
unsigned int mIndices[5];

struct //! for sake of clarity
{
unsigned int mMaterial;
unsigned int mVertex;
unsigned int mNormal;
unsigned int mColor;
unsigned int mTextureCoords; //or mTextureCoord?
};
};

bool mIsUnique;
bool mIsAssigned;

unsigned int mNewIndex;

Parameters *mMoreByVertexIndex;

inline bool operator==(const Parameters &o)
{
return memcmp(mIndices,o.mIndices,5*sizeof(int))==0;
}
inline bool operator!=(const Parameters &o)
{
return memcmp(mIndices,o.mIndices,5*sizeof(int))!=0;
}

Parameters()
{
mMaterial = 0;

for(int i=1;i<5;i++) mIndices[i] = -1;

mIsUnique = mIsAssigned = false;

mNewIndex = -1;
}

};

struct Material
{
aiMesh *mAssignedSubMesh;

unsigned int mMaterialIndex;

unsigned int mNumFacesApplicable;

Material *mNextInLoop;

Material(unsigned int iMatIndex, Material *pNext = NULL)
{
mAssignedSubMesh = NULL;

mMaterialIndex = iMatIndex;

mNextInLoop = pNext?pNext:this;

mNumFacesApplicable = 0;
}
};

unsigned int mNumBuffered;

Parameters* mBuffer;

unsigned int mNumIndexed;

Parameters** mIndex;

Material* mMaterials; //STL might be safer

Material* GetMaterial(unsigned int iMatIndex)
{
if(mMaterials==NULL) return mMaterials = new Material(iMatIndex);

if(mMaterials->mMaterialIndex==iMatIndex) return mMaterials;

Material *pOut = mMaterials->mNextInLoop;

while(pOut!=mMaterials&&pOut->mMaterialIndex!=iMatIndex)
pOut = pOut->mNextInLoop;

if(pOut==mMaterials)
pOut = pOut->mNextInLoop = new Material(iMatIndex,mMaterials);

//order could be optimized for meshes with many materials
return mMaterials = pOut;
}

Parameters* AddParameters(Parameters *pParams)
{
//! do not add to index if position parameter is omitted
if((signed)pParams->mVertex==-1) return pParams;

for(Parameters *pIter=mIndex[pParams->mVertex];
pIter&&pIter->mIsUnique;pIter++)
{
if(pIter->mIsUnique==false) break; //reached duplicate so this is unique

if(*pParams==*pIter)
{
while(pIter->mMoreByVertexIndex&&
pIter->mMoreByVertexIndex->mIsUnique)
pIter = pIter->mMoreByVertexIndex;

ai_assert(pParams->mIsUnique==false);

pParams->mMoreByVertexIndex = pIter->mMoreByVertexIndex;

return pIter->mMoreByVertexIndex = pParams;
}
}

pParams->mIsUnique = true;

pParams->mMoreByVertexIndex = mIndex[pParams->mVertex];

return mIndex[pParams->mVertex] = pParams;
}

const aiMesh2* pInput;

aiMesh** pOutput;

int iMaxOutput; ‎ 
int iCompleted;
};
};

DownLevelProcess::
ProcessMeshHelper2::ProcessMeshHelper2(const aiMesh2 *pIn, aiMesh** pOut, int iMaxOut)
{
memset(this,0x00,sizeof(ProcessMeshHelper2));

if(pIn==NULL||pOut==NULL||iMaxOut<=0) return;

pInput = pIn; pOutput = pOut; iMaxOutput = iMaxOut;

mNumIndexed = pIn->mNumVertices; mNumBuffered = 0;

for(unsigned int i=0;i<pIn->mNumFaces;i++)
mNumBuffered+=pIn->mFaces2[i].mNumCorners;

if(pIn->mSharedParameters)
{
mNumIndexed+=pIn->mSharedParameters->mNumVertices;

for(unsigned int i=0;i<pIn->mSharedParameters->mNumFaces;i++)
mNumBuffered+=pIn->mSharedParameters->mFaces2[i].mNumCorners;
}

mBuffer = new ProcessMeshHelper2::Parameters[mNumBuffered];
mIndex = new ProcessMeshHelper2::Parameters*[mNumIndexed];

memset(mBuffer,0x00,mNumBuffered*sizeof(ProcessMeshHelper2::Parameters*));
memset(mIndex,0x00,mNumIndexed*sizeof(void*));

unsigned int iCurParameters = 0;

for(unsigned int i=0;i<pIn->mNumFaces;i++)
{
ProcessMeshHelper2::Parameters *pParams = mBuffer+iCurParameters;

const aiFace2* pFace = pIn->mFaces2+i; //! MATERIAL

GetMaterial(pFace->mMaterialIndex)->mNumFacesApplicable++;

int iNumCorners = pFace->mNumCorners;

for(unsigned int j=0;j<iNumCorners;j++)
pParams[j].mMaterial = pFace->mMaterialIndex;

#define _AI_SAFE_FINDFACE2(Ps)\
{ pFace = pIn->FindFace2(pFace->m##Ps); ai_assert(pFace&&pFace->mNumCorners==iNumCorners); }

pFace = pIn->mFaces2+i; //! VERTICES

if(pFace->mParameterModes&aiParameterType_VERTEX) _AI_SAFE_FINDFACE2(Vertices)

if(pFace->mVertices!=0) for(unsigned int j=0;j<iNumCorners;j++)
pParams[j].mVertex = pFace->mIndices[pFace->mVertices+pFace->mStride*j];

pFace = pIn->mFaces2+i; //! NORMALS

if(pFace->mParameterModes&aiParameterType_NORMAL) _AI_SAFE_FINDFACE2(Normals)

if(pFace->mFaceFeatures&aiFaceFeature_NORMAL)
for(unsigned int j=0;j<iNumCorners;j++) pParams[j].mNormal = pFace->mIndices[0];
else if(pFace->mNormals!=0) for(unsigned int j=0;j<iNumCorners;j++)
pParams[j].mNormal = pFace->mIndices[pFace->mNormals+pFace->mStride*j];

pFace = pIn->mFaces2+i; //! COLORS

if(pFace->mParameterModes&aiParameterType_COLOR) _AI_SAFE_FINDFACE2(Colors)

if(pFace->mParameterModes&aiParameterType_COLOR)
for(unsigned int j=0;j<iNumCorners;j++) pParams[j].mColor = pFace->mIndices[1];
else if(pFace->mColors!=0) for(unsigned int j=0;j<iNumCorners;j++)
pParams[j].mColor = pFace->mIndices[pFace->mColors+pFace->mStride*j];

pFace = pIn->mFaces2+i; //! TEXTURECOORDS

if(pFace->mParameterModes&aiParameterType_TEXTURECOORDS) _AI_SAFE_FINDFACE2(TextureCoords)

if(pFace->mTextureCoords!=0) for(unsigned int j=0;j<iNumCorners;j++)
pParams[j].mTextureCoords = pFace->mIndices[pFace->mTextureCoords+pFace->mStride*j];

#undef _AI_SAFE_FINDFACE2

for(unsigned int j=0;j<iNumCorners;j++) AddParameters(pParams+j);

iCurParameters+=iNumCorners;
}

if(mMaterials==NULL) GetMaterial(pIn->mMaterialIndex);

Material *pIter = mMaterials;

for(unsigned int i=0;i<iMaxOut;i++)
{
pIter->mAssignedSubMesh = pOut[i] = new aiMesh;

pOut[i]->mPrimitiveTypes = pIn->mPrimitiveTypes;

pOut[i]->mFaces = new aiFace[pIter->mNumFacesApplicable];

for(unsigned int j=0;j<AI_MAX_NUMBER_OF_COLOR_SETS;j++)
pOut[i]->mNumUVComponents[j] = pIn->mNumUVComponents[j];

for(unsigned int j=0;j<AI_MAX_NUMBER_OF_TEXTURECOORDS;j++)
pOut[i]->mNumUVComponents[j] = pIn->mNumUVComponents[j];

pOut[i]->mMaterialIndex = pIter->mMaterialIndex;

unsigned int iNumVertices = 0;

for(unsigned int j=0;j<mNumBuffered;j++)
if(mBuffer[j].mMaterial==pIter->mMaterialIndex&&
mBuffer[j].mIsUnique==true)
iNumVertices++;

if(pIn->HasPositions())
pOut[i]->mVertices = new aiVector3D[iNumVertices];
if(pIn->HasNormals())
pOut[i]->mNormals = new aiVector3D[iNumVertices];
if(pIn->HasTangentsAndBitangents())
{ pOut[i]->mTangents = new aiVector3D[iNumVertices];
pOut[i]->mBitangents = new aiVector3D[iNumVertices]; }

int iNumChs = pIn->GetNumColorChannels();

for(unsigned int j=0;j<iNumChs;j++)
pOut[i]->mColors[j] = new aiColor4D[iNumVertices];

iNumChs = pIn->GetNumUVChannels();

for(unsigned int j=0;j<iNumChs;j++)
pOut[i]->mTextureCoords[j] = new aiVector3D[iNumVertices];

if(pIter->mNextInLoop==mMaterials) break;
}

//! Break out the non-unique vertices
for(unsigned int j=0;j<mNumBuffered;j++)
if(mBuffer[j].mIsUnique==true&&
mBuffer[j].mMoreByVertexIndex!=NULL&&
mBuffer[j].mMoreByVertexIndex->mIsUnique==false)
mBuffer[j].mMoreByVertexIndex = NULL;

iCurParameters = 0;

for(unsigned int i=0;i<pIn->mNumFaces;i++)
{
const aiFace2* pFace = pIn->mFaces2+i;

aiMesh *pSubMesh = GetMaterial(pFace->mMaterialIndex)->mAssignedSubMesh;
aiFace* pNewFace = pSubMesh->mFaces+pSubMesh->mNumFaces;

ProcessMeshHelper2::Parameters *pParams = mBuffer+iCurParameters;

iCurParameters+=pFace->mNumCorners;

if((signed)pParams->mVertex==-1) continue;

pNewFace->mIndices = new unsigned int[pNewFace->mNumIndices];

for(unsigned int j=0;j<pFace->mNumCorners;j++)
{
Parameters *pCmp = mIndex[pParams[j].mVertex];

while(pCmp&&*pCmp!=pParams[j]) pCmp++; ai_assert(pCmp!=NULL);

if(pCmp->mIsAssigned==false)
{
int iThisVert = pSubMesh->mNumVertices++;

if(pSubMesh->mVertices)
ai_assert(pIn->GetPosition(pCmp->mVertex,pSubMesh->mVertices+iThisVert));
if(pSubMesh->mNormals)
ai_assert(pIn->GetNormal(pCmp->mNormal,pSubMesh->mNormals+iThisVert));
if(pSubMesh->mTangents)
ai_assert(pIn->GetTangent(pCmp->mNormal,pSubMesh->mTangents+iThisVert));
if(pSubMesh->mBitangents)
ai_assert(pIn->GetBitangent(pCmp->mNormal,pSubMesh->mBitangents+iThisVert));

int iNumChs = pIn->GetNumColorChannels();

for(unsigned int k=0;k<iNumChs;k++)
ai_assert(pIn->GetVertexColor(k,pCmp->mColor,pSubMesh->mColors[k]+iThisVert));

iNumChs = pIn->GetNumUVChannels();

for(unsigned int k=0;k<iNumChs;k++)
ai_assert(pIn->GetTextureCoord(k,pCmp->mTextureCoords,
pSubMesh->mTextureCoords[k]+iThisVert));
pCmp->mNewIndex = iThisVert;
pCmp->mIsAssigned = true;
}

pNewFace->mIndices[j] = pCmp->mNewIndex;
}

pNewFace->mNumIndices = pFace->mNumCorners;
pSubMesh->mNumFaces++;
}

while(pOut[iCompleted]&&++iCompleted<iMaxOut);
}

DownLevelProcess::
ProcessMeshHelper2::~ProcessMeshHelper2()
{
if(mBuffer) delete [] mBuffer;
if(mIndex) delete [] mIndex;

if(mMaterials==NULL) return;

Material *pIter = mMaterials->mNextInLoop;

while(pIter!=mMaterials)
{
Material *pDtor = pIter; pIter = pIter->mNextInLoop;

delete pDtor;
}

delete mMaterials;
}

aiScene *aiScene2::TransferSceneDownLevel(aiScene *out)
{
//TODO: validate mMeshes fitness / mMeshes2

unsigned int n = mNumMeshes; aiMesh2 **m2 = mMeshes2;

aiMesh** m = NULL; if(n) m = new aiMesh*[n];

memset(m,0x00,sizeof(void*)*n);

unsigned int *mats = NULL;

for(unsigned int i=0,j=0;i<n;i++) if(m2[i])
{
for(j=1;j<m2[i]->mNumMaterials;j++)

if(j>=n||mMeshes2[j]!=NULL) goto failure;

if(DownLevelProcess::ProcessMesh2(m2[i],m+i,j)==0)
goto failure;
}

goto success;

failure:

for(unsigned int i=0;i<n;i++) if(m[i]) delete m[i];

if(m) delete [] m; if(mats) delete [] mats;

return NULL;

success:

//! same as PopulateSceneSublevel()
if(out==this) return this;

if(out==NULL) out = new aiScene;

mNumMeshes = 0; mMeshes2 = NULL;

memcpy(out,this,sizeof(aiScene));
memcpy(this,0x00,sizeof(aiScene));

for(unsigned int i=0;i<n;i++) if(m2[i]) delete m2[i];

delete [] m2; delete this;

out->mMeshes = m; out->mNumMeshes = n;

return out;
}

SomFileHelper::SomFileHelper(const void *eof, aiScene *out)
{
pcEof = eof; pAssimp = out;

pAssimp2 = NULL; if(pAssimp==NULL) return;

pAssimp2 = new aiScene2(out);
}

SomFileHelper::~SomFileHelper()
{
if(pAssimp==NULL) return;

pAssimp2->TransferSceneDownLevel(pAssimp);
} ‎ 

bool SomFileHelper::detect(const MDL::Header_SOM *in)const
{
#ifdef NDEBUG
return false; //work in progress
#endif

ai_assert(pcEof!=NULL);

if(!in->num_parts)
{
if(pAssimp) throw new ImportErrorException("[Sword of Moonlight MDL] A MDL must contain at least one model");

return false;
}

if(!pAssimp) return true;

ai_assert(pAssimp2->mNumMeshes==0);

pAssimp2->mNumMeshes = 1+AI_BE(in->num_parts)*AI_BE(in->num_skins);

pAssimp2->mMeshes2 = new aiMesh2*[pAssimp2->mNumMeshes];

memset(pAssimp2->mMeshes2,0x00,sizeof(void*)*pAssimp2->mNumMeshes);

MaterialHelper *pMatDefault = new MaterialHelper;

const int iMode = (int)aiShadingMode_Gouraud;

pMatDefault->AddProperty<int>(&iMode,1,AI_MATKEY_SHADING_MODEL);

pAssimp2->mNumMaterials = max(AI_BE(in->num_skins),1);

pAssimp2->mMaterials = new aiMaterial*[pAssimp2->mNumMaterials];

for(unsigned int i=0;i<pAssimp2->mNumMaterials;i++)

pAssimp2->mMaterials[i] = pMatDefault;

return true; //TODO: flesh out
}

const int32_t *SomFileHelper::offset(const MDL::Header_SOM *in, int cc, int pt)const
{
return (const int32_t*)(in?(const char*)in+in->offset(cc,pt)*4:NULL);
}

bool SomFileHelper::unpack(const MDL::Header_SOM *in,
‎  const MDL::Packet_SOM **inout, int cc, int pt)const
{
ai_assert(pAssimp&&pcEof);

const MDL::Packet_SOM *p = *inout;

pAssimp2->mFlags2|=AI_SCENE_FLAGS2_INCOMPLETE;

unsigned int iPart = 1+pt*AI_BE(in->num_skins);

ai_assert(pAssimp2!=NULL&&pAssimp2->mNumMeshes>=iPart);

aiMesh2 *pPart = pAssimp2->mMeshes2[iPart];

if(pPart==NULL) switch(cc)
{
case 'face': case 'vert': case 'norm':

pPart = pAssimp2->mMeshes2[iPart] = new aiMesh2;

pPart->mPrimitiveTypes =
aiPrimitiveType_TRIANGLE|aiPrimitiveType_POLYGON;

pPart->mSharedParameters = pAssimp2->mMeshes2[0];

pPart->mNumMaterials = AI_BE(in->num_skins);


}

switch(cc)
{
case 'base': //! shared parameters block (uv components more or less)
{
int iShared = AI_BE(p->lo); if(iShared==0) return false; //block is empty

sizecheck((const char*)p+4+iShared*12); //! expected end of block

ai_assert(pAssimp2->mNumMeshes==0&&pAssimp2->mMeshes2!=NULL);

int iCurMaterial = AI_BE(p->hi); int iCurIndex = 0;

aiMesh2 *pShared = pAssimp2->mMeshes2[0] = new aiMesh2; //shared mesh

pShared->mNumMaterials = 1; pShared->mMaterialIndex = iCurMaterial;

pShared->mNumUVComponents[0] = 2;

pShared->mTextureCoords[0] = new aiVector3D[pShared->mNumTextureCoords=iShared*4];

for(int i=0;i<iShared;i++) if((++p)->hi==0)
{
aiFace2 *pFace2 = pShared->mFaces2+i;

pFace2->mMaterialIndex = iCurMaterial;

pFace2->mIndices = new unsigned int[pFace2->mNumIndices=5];

memset(pFace2->mIndices,0x00,5*sizeof(int));

pFace2->mNumCorners = 4; pFace2->mStride = 1;

pFace2->mTextureCoords = 1;

pShared->mTextureCoords[0][iCurIndex] = aiVector3D(p->s,p->t,0.0f); //lo

pFace2->mIndices[1] = iCurIndex++; p++;

pShared->mTextureCoords[0][iCurIndex] = aiVector3D(p->s,p->t,0.0f); //lo

//! Note that the hi 16bits here house potentially meaningful flags

pFace2->mIndices[2] = iCurIndex++; p++; ‎ 

pShared->mTextureCoords[0][iCurIndex] = aiVector3D(p->s,p->t,0.0f); //lo

pFace2->mIndices[3] = iCurIndex++;

pShared->mTextureCoords[0][iCurIndex] = aiVector3D(p->u,p->v,0.0f); //hi

pFace2->mIndices[4] = iCurIndex++;
}
else
{
DefaultLogger::get()->warn("Sword of Moonlight MDL shared texture mapping block not as expected. Block left incomplete");

return false;
}

break;
}
case 'face': //! face packets block
{
int iFaces = AI_BE(in->parts[pt].num_faces);

if(iFaces==0||p->lo==0) return false; //faces absent

pPart->mFaces2 = new aiFace2[pPart->mNumFaces=iFaces];

for(int i=0;i<iFaces;i++) if((++p+1)->hi==0)
{
aiFace2 *pFace2 = pPart->mFaces2+i;




//TODO: tomorrow.........................


}

break;
}
case 'vert': //! vertex packets block
{
int iVerts = AI_BE(in->parts[pt].num_verts);

if(iVerts==0) return false; //vertices absent

sizecheck((const char*)p+iVerts*8); //expected end of block

pPart->mVertices = new aiVector3D[pPart->mNumVertices=iVerts];

for(int i=0;i<iVerts;i++) if((p+1)->hi==0)
{
pPart->mVertices[i].x = (int16_t)AI_BE(p->lo);
pPart->mVertices[i].y = (int16_t)AI_BE(p->hi); p++;
pPart->mVertices[i].z = (int16_t)AI_BE(p->lo);
}
else
{
DefaultLogger::get()->warn("Sword of Moonlight MDL high 16bits of 2nd packet in vertex block non-zero. Block left incomplete");

return false;
}

break;
}
case 'norm': //! normal packets block
{
int iNorms = AI_BE(in->parts[pt].num_norms);

if(iNorms==0) return false; //normals absent

sizecheck((const char*)p+iNorms*8); //expected end of block

pPart->mNormals = new aiVector3D[pPart->mNumNormals=iNorms];

for(int i=0;i<iNorms;i++) if((p+1)->hi==0)
{
pPart->mNormals[i].x = (int16_t)AI_BE(p->lo);
pPart->mNormals[i].y = (int16_t)AI_BE(p->hi); p++;
pPart->mNormals[i].z = (int16_t)AI_BE(p->lo);
}
else
{
DefaultLogger::get()->warn("Sword of Moonlight MDL high 16bits of 2nd packet in normals block non-zero. Block left incomplete");

return false;
}

break;
}
case 'anim': //! reference frame (skeletal) animation block
{
if(in->num_anims==0||in->add_anims==0) return false; //animation absent

return false; //unimplemented thus far
}
case 'stop': //! stop-motion (deformer) animation block
{
if(in->num_stops==0||in->add_stops==0) return false; //animation absent

return false; //unimplemented thus far
}
default: return false;
}

pAssimp2->mFlags2&=~AI_SCENE_FLAGS2_INCOMPLETE;

*inout = p; return false;
}

static void TIMpixelmode2(aiTexel *inout, uint16_t pm2)
{
inout->r = (pm2&0x1F)*8; inout->g = ((pm2&0x3E0)>>8)*8;

inout->b = ((pm2&0x7C00)>>8)*8; inout->a = 0;
}

bool SomFileHelper::bitmap(const MDL::Header_SOM *in, const MDL::Bitmap_SOM **inout)const
{
ai_assert(pAssimp&&pcEof);

if(in->num_skins==0) return false;

const MDL::Bitmap_SOM *p = *inout;

if(p->id!=0x10)
{
DefaultLogger::get()->warn("Sword of Moonlight MDL texture appears not to be TIM bitmap format. Ignoring further texture data");

return false;
}

const MDL::Bitmap_SOM::BLOCK *clut = p->clut_block();
const MDL::Bitmap_SOM::BLOCK *data = p->data_block();

if(p->pmode<0x02&&clut==0)
{
DefaultLogger::get()->warn("Sword of Moonlight MDL texture appears to contain paletted data but is missing a color lookup table. Ignoring further texture data");

return false;
}

sizecheck((const char*)data+AI_BE(data->bnum));
sizecheck((const char*)data+2*AI_BE(data->w)*AI_BE(data->h)+12);

if(clut) sizecheck((const char*)clut+AI_BE(clut->bnum));
if(clut) sizecheck((const char*)clut+2*AI_BE(clut->w)*AI_BE(clut->h)+12);

if(pAssimp2->mNumTextures>=in->num_skins)
{
DefaultLogger::get()->warn("Sword of Moonlight MDL texture appears to have exceeded the ammount indicated by the header. Ignoring further texture data");

return false;
}

if(!pAssimp2->mNumTextures) pAssimp2->mTextures = new aiTexture*[in->num_skins];

aiTexture *q = pAssimp2->mTextures[pAssimp2->mNumTextures] = new aiTexture;

float fWidth = AI_BE(data->w);

switch(p->pmode)
{
case 0x00: fWidth*=4.0f; break;
case 0x01: fWidth*=2.0f; break;
case 0x02: ‎  ‎  ‎  ‎  ‎  ‎  ‎  break;
case 0x03:
case 0x04: fWidth = fWidth/3.0f*2.0f;
}

if(fWidth-int(fWidth)!=0.0f)
{
DefaultLogger::get()->warn("Sword of Moonlight MDL texture width appears not to comply with TIM Pixel mode specification. Ignoring further textures");

return false;
}

q->mWidth = (unsigned int)fWidth; q->mHeight = AI_BE(data->h);

aiTexel *d = q->pcData = new aiTexel[q->mWidth*q->mHeight];

if(p->pmode==0x04) DefaultLogger::get()->warn("Sword of Moonlight MDL texture appears to be \"mixed\" mode. Interpreting as 24bit color");

int h = AI_BE(data->h), w = AI_BE(data->w);

for(int i=0;i<h;i++) for(int j=0;j<w;j++)
{
uint16_t e = data->data[i*w+j]; AI_SWAP2(e);

switch(p->pmode)
{
case 0x00:

TIMpixelmode2(d++,clut->data[(e&&0x000F)>>0]);
TIMpixelmode2(d++,clut->data[(e&&0x00F0)>>4]);
TIMpixelmode2(d++,clut->data[(e&&0x0F00)>>8]);
TIMpixelmode2(d++,clut->data[(e&&0xF000)>>12]); break;

case 0x01:

TIMpixelmode2(d++,clut->data[(e&&0x00FF)>>0]);
TIMpixelmode2(d++,clut->data[(e&&0xFF00)>>8]); break;

case 0x02: TIMpixelmode2(d,e); break;

case 0x03: case 0x04:

d->r = e&0xFF; d->g = (e&0xFF00)>>8;

e = data->data[i*w+++j]; AI_SWAP2(e);

d->b = e&0xFF; d->a = 0;

++d->r = (e&0xFF00)>>8;

e = data->data[i*w+++j]; AI_SWAP2(e);

d->g = e&0xFF; d->b = (e&0xFF00)>>8;

d->a = 0; d++;
}
}

pAssimp2->mNumTextures++;

int next = sizeof(MDL::Bitmap_SOM)+data->bnum;

if(clut) next+=clut->bnum;

*(char**)inout+=next;

if(pAssimp2->mNumTextures>=in->num_skins) return false;

return true;
}
[/code]


EDITED: I was getting a maximum of something like 300000 characters (plus or minus a figure) so I was going to add the cpp file as an attachment but the second shot (minus just the license header) little did I know must've gone thru.

Let Todd decide if the character limit seems practical or not.
Reply

#17
I think I understand pretty much everything about the skeleton style animation (haven't looked at the gumby animation yet) however the encoding of the actual data is so wacky it may take me a while to sort out all of the bits.

I'm assuming I will figure it out eventually after much experimentation. But I'm thinking it might be more generous to go ahead and slap something together that would let people could start customizing some MDL files (I also noticed the SFX folder has many mdl files for magic explosions etc)

Basically I know enough so to swap out one graphic in the MDL file for another. So you could say take a skeleton MDL file and give it a new weapon, or replace its head, or totally swap out all its parts for a new kind of monster. But you'd be stuck with the same animations (which includes the distances from one joint to the next)

I can also make inanimate MDL files from scratch (and of course export the graphics)
Reply

#18
Almost there.... after one last night of compulsive staring / hair pulling a few leaps of insight came my way. It's safe to say it's just a matter of time now for the first round of animation.

I have every bit in its place. Still a bit fuzzy as to just how to interpret a couple things but that should sort itself out. Have not really looked at the .cp files yet. I know for treasure chests they're zeroed out (so probably have no effect other than keeping things from crashing) ...the .cp layout is very simple, but I gotta admit I can only guess what they're good for (I'm thinking they control animation sequencing; looping, branching, that sort of thing)
Reply

#19
It turned out the last leg of the soft animation data was a lot trickier than I'd expected, but I was able to finally sort it out.

I was also able to hand edit the BOX1.MDL file to make it work like a soft animation treasure chest. One prob that gave me a lot of trouble was the BOX1.MDL file did not have a corresponding .cp file. So Som would crash. So maybe .cp is required for animated mdl files. I think the wooden chest .cp works pretty much as a dummy .cp file. I just copied that. I still don't think the .cp files effect animation though they might define the control points also, so one is needed maybe to at least be clear there are no control points (which apparently aren't considered for inanimate files)

Moving along.... one potential chink in Som's armor is there may not be a way to setup a non-cyclical soft animation. There are still some flags I do not yet fully understand however I can't think of a single model in Som's repertoire that needs a non-cyclical soft animation so it may not be implemented, and at the least if it is, finding an example of how to do it might be impossible. The only objects that use non-cyclical animations are treasure chests and doors and basically anything that acts like that.

The basic problem is the hypothetical chest opens, then when the closing animation starts it must revert back to it's original pose before the closing animation starts. Hopefully there is a pose flag that basically jumps straight to an animation frame, but the normal behavior is to interpolate (blend) into the first frame and so on which just isn't good enough. It's possible some NPCs/monsters idle in an animation not based on their original "bind" pose. If so that would necessitate such a pose flag.

You can remove the closing frame and the chest will just stay open indefinitely. It might close if you reload the map but probably not. If you really needed a soft door/chest to open/close you could still use two different objects and flip them to the correct effect.

A soft animation door/chest anyway would be pretty freaky. More Shadow Tower's style perhaps.


Last major hurdle are the .cp files. They look pretty simple thankfully.


EDITED: Another soft animation limitation -- again, flags may exist -- is I don't see a way to animate the lighting normals. So like if your model had a hand palm down with a shadow on the palm (because a light is above) and the animation flipped the hand over, the shadow would still be on the palm even though it's now facing the light.
Reply

#20
I have the .cp files in the bag. They do serve the exact function I had last speculated...

Basically there are these things embedded in the MDL files that look like little pairs of coloured triangles.

I think cyan triangles mark the center of the model. So for like walking animations they move. If there is no cyan triangle (I don't think this one comes in pairs) then coordinate 0,0,0 or the root animation channel is used.

Next are blue triangles which come in pairs generally one beneath each foot for biped creatures, not sure how non-bipeds would work out.

Red triangles are for attack elements. A pair forms a sword. They are also used to define where magic shoots from and in what direction. I reckon the size of the triangles might also come into play, but the .cp files only tracks the centers of each triangle throughout each animation.

I'm pretty sure I've also seen purple triangles. The colours themselves are probably for reference only.

So yeah basically, like I just said^ the .cp files are a database of the centers of the "control points" throughout each step of the animation.

I don't think it's necessary to interact with .cp files. The .prf files may reference control points directly. Somehow anyway the attack control points must be associated with attacking and only activated during particular attack animations or else you'd always be damaged by getting too close to monsters' implements of attack.


I'd like to document everything somehow, but I can't find any open standards for file format documentation or non-proprietary XML markup. So since no one else is interested in working with the files afaik I will wait until I can decide upon the best means of doing so.


PS: For some reason many of the doors have an awful lot of control points lined up almost like track lighting. I have no clue why that is necessary.
Reply





Users browsing this thread:
6 Guest(s)